pax_global_header00006660000000000000000000000064142006531110014503gustar00rootroot0000000000000052 comment=9451052e820a6bb47c89a5598c9cd357b735c760 undertow-2.2.16.Final/000077500000000000000000000000001420065311100144725ustar00rootroot00000000000000undertow-2.2.16.Final/.github/000077500000000000000000000000001420065311100160325ustar00rootroot00000000000000undertow-2.2.16.Final/.github/workflows/000077500000000000000000000000001420065311100200675ustar00rootroot00000000000000undertow-2.2.16.Final/.github/workflows/ci.yml000066400000000000000000000117511420065311100212120ustar00rootroot00000000000000# This workflow will build a Java project with Maven # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven name: Undertow CI on: pull_request: types: [opened, synchronize, reopened, ready_for_review] jobs: build-all: name: Compile (no tests) with JDK 8 runs-on: ubuntu-latest steps: - uses: n1hility/cancel-previous-runs@v2 with: token: ${{ secrets.GITHUB_TOKEN }} - uses: actions/cache@v1 with: path: ~/.m2/repository key: m2-${{ hashFiles('**/pom.xml') }} restore-keys: | m2- - uses: actions/checkout@v2 - name: Set up JDK 8 uses: joschi/setup-jdk@v2 with: java-version: 8 - name: Print Version run: mvn -v - name: Build run: mvn -U -B -fae -DskipTests -Dfindbugs clean install - name: Tar Maven Repo shell: bash run: tar -czf maven-repo.tgz -C ~ .m2/repository - name: Persist Maven Repo uses: actions/upload-artifact@v1 with: name: maven-repo path: maven-repo.tgz - uses: actions/upload-artifact@v2 if: failure() with: name: surefire-reports-build path: | **/surefire*-reports/*.txt **/*.dump* test-matrix: name: JDK ${{ matrix.jdk }} - ${{ matrix.module }} - ${{ matrix.os }} runs-on: ${{ matrix.os }} needs: build-all strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] module: [core, servlet, websockets-jsr] jdk: [8, 11] steps: - name: Update hosts - linux if: matrix.os == 'ubuntu-latest' run: | cat /etc/hosts sudo bash -c "echo '127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4' > /etc/hosts" sudo bash -c "echo '::1 localhost localhost.localdomain localhost6 localhost6.localdomain6' >> /etc/hosts" sudo sysctl -w fs.file-max=2097152 - name: Update hosts - windows if: matrix.os == 'windows-latest' run: | type %SystemRoot%\System32\drivers\etc\hosts echo '127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4' > %SystemRoot%\System32\drivers\etc\hosts echo '::1 localhost localhost.localdomain localhost6 localhost6.localdomain6' >> %SystemRoot%\System32\drivers\etc\hosts shell: cmd - uses: n1hility/cancel-previous-runs@v2 with: token: ${{ secrets.GITHUB_TOKEN }} - name: Host information run: | hostname || true - uses: actions/checkout@v2 - name: Download Maven Repo uses: actions/download-artifact@v1 with: name: maven-repo path: . - name: Extract Maven Repo shell: bash run: tar -xzf maven-repo.tgz -C ~ - name: Set up JDK ${{ matrix.java }} uses: joschi/setup-jdk@v2 with: java-version: ${{ matrix.jdk }} - name: Print Version run: mvn -v - name: Run Tests run: mvn -U -B -fae test -Pproxy '-DfailIfNoTests=false' -pl ${{ matrix.module }} - uses: actions/upload-artifact@v2 if: failure() with: name: surefire-reports-${{ matrix.jdk }}-${{ matrix.module }}-${{ matrix.os }} path: | **/surefire*-reports/*.txt **/*.dump* test-matrix-ipv6: name: JDK ${{ matrix.jdk }} - ipv6 - ${{ matrix.module }} ${{ matrix.proxy }} - ${{ matrix.os }} runs-on: ${{ matrix.os }} needs: build-all strategy: fail-fast: false matrix: os: [ubuntu-latest] module: [core, servlet, websockets-jsr] proxy: ['-Pproxy', ''] jdk: [11] steps: - name: Update hosts - linux if: matrix.os == 'ubuntu-latest' run: | sudo bash -c "echo '127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4' > /etc/hosts" sudo bash -c "echo '::1 localhost localhost.localdomain localhost6 localhost6.localdomain6' >> /etc/hosts" sudo sysctl -w fs.file-max=2097152 - uses: n1hility/cancel-previous-runs@v2 with: token: ${{ secrets.GITHUB_TOKEN }} - name: Host information run: | hostname || true - uses: actions/checkout@v2 - name: Download Maven Repo uses: actions/download-artifact@v1 with: name: maven-repo path: . - name: Extract Maven Repo shell: bash run: tar -xzf maven-repo.tgz -C ~ - name: Set up JDK ${{ matrix.java }} uses: joschi/setup-jdk@v2 with: java-version: ${{ matrix.jdk }} - name: Print Version run: mvn -v - name: Run Tests run: mvn -U -B -fae test ${{ matrix.proxy }} '-DfailIfNoTests=false' -pl ${{ matrix.module }} -Dtest.ipv6=true - uses: actions/upload-artifact@v2 if: failure() with: name: surefire-reports-${{ matrix.jdk }}-ipv6-${{ matrix.module }}${{ matrix.proxy }}-${{ matrix.os }} path: | **/surefire*-reports/*.txt **/*.dump* undertow-2.2.16.Final/.gitignore000066400000000000000000000001751420065311100164650ustar00rootroot00000000000000target *.iml .idea .settings .project .classpath *~ local-test out lib bin dependency-reduced-pom.xml hotspot.log .directory undertow-2.2.16.Final/LICENSE.txt000066400000000000000000000261361420065311100163250ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. undertow-2.2.16.Final/README.md000066400000000000000000000007531420065311100157560ustar00rootroot00000000000000Undertow ======== Undertow is a Java web server based on non-blocking IO. It consists of a few different parts: * A core HTTP server that supports both blocking and non-blocking IO * A Servlet 4.0 implementation * A JSR-356 compliant web socket implementation Website: http://undertow.io Issues: https://issues.jboss.org/browse/UNDERTOW Project Lead: Flavia Rainone Mailing List: undertow-dev@lists.jboss.org http://lists.jboss.org/mailman/listinfo/undertow-dev undertow-2.2.16.Final/SECURITY.md000066400000000000000000000007421420065311100162660ustar00rootroot00000000000000# Security Policy ## Supported Versions The following versions of Undertow are supported with security updates: | Version | Supported | | ------- | ------------------ | | < 2.0 | :x: | | 2.0.x | :white_check_mark: | | 2.1.x | :x: | | > 2.2.x | :white_check_mark: | ## Reporting a Vulnerability If you find a vulnerability, send an email to secalert@redhat.com. To expedite things, you can copy Flavia Rainone (frainone@redhat.com). undertow-2.2.16.Final/benchmarks/000077500000000000000000000000001420065311100166075ustar00rootroot00000000000000undertow-2.2.16.Final/benchmarks/README.md000066400000000000000000000005411420065311100200660ustar00rootroot00000000000000# Undertow Benchmarks ## JMH Benchmarks use the [JMH harness](https://openjdk.java.net/projects/code-tools/jmh/). ## Running ```bash mvn install java -jar benchmarks/target/undertow-benchmarks.jar ``` Alternatively benchmarks may be run from the IDE using a JMH plugin like [this one for idea](https://plugins.jetbrains.com/plugin/7529-jmh-plugin).undertow-2.2.16.Final/benchmarks/pom.xml000066400000000000000000000066531420065311100201360ustar00rootroot00000000000000 4.0.0 io.undertow undertow-parent 2.2.16.Final undertow-benchmarks 2.2.16.Final Undertow Benchmarks io.undertow undertow-core org.jboss.logmanager jboss-logmanager org.openjdk.jmh jmh-core org.openjdk.jmh jmh-generator-annprocess provided org.apache.httpcomponents httpclient compile undertow-benchmarks src/main/java **/*.java src/main/resources org.apache.maven.plugins maven-jar-plugin org.openjdk.jmh.Main org.apache.maven.plugins maven-shade-plugin package shade undertow-2.2.16.Final/benchmarks/src/000077500000000000000000000000001420065311100173765ustar00rootroot00000000000000undertow-2.2.16.Final/benchmarks/src/main/000077500000000000000000000000001420065311100203225ustar00rootroot00000000000000undertow-2.2.16.Final/benchmarks/src/main/java/000077500000000000000000000000001420065311100212435ustar00rootroot00000000000000undertow-2.2.16.Final/benchmarks/src/main/java/io/000077500000000000000000000000001420065311100216525ustar00rootroot00000000000000undertow-2.2.16.Final/benchmarks/src/main/java/io/undertow/000077500000000000000000000000001420065311100235215ustar00rootroot00000000000000undertow-2.2.16.Final/benchmarks/src/main/java/io/undertow/benchmarks/000077500000000000000000000000001420065311100256365ustar00rootroot00000000000000undertow-2.2.16.Final/benchmarks/src/main/java/io/undertow/benchmarks/BenchmarkUtils.java000066400000000000000000000025441420065311100314210ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.benchmarks; import java.io.IOException; import java.io.InputStream; /** * Utility functionality used by benchmarks. * * @author Carter Kozak */ final class BenchmarkUtils { private static final byte[] GARBAGE_BUFFER = new byte[16 * 1024]; /** Consumes the {@link InputStream}, returning the number of bytes read. */ static long length(InputStream stream) throws IOException { long total = 0; while (true) { int read = stream.read(GARBAGE_BUFFER); if (read == -1) { return total; } total += read; } } private BenchmarkUtils() {} } undertow-2.2.16.Final/benchmarks/src/main/java/io/undertow/benchmarks/SimpleBenchmarkState.java000066400000000000000000000125141420065311100325510ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.benchmarks; import io.undertow.Handlers; import io.undertow.Undertow; import io.undertow.UndertowOptions; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.BlockingHandler; import io.undertow.util.Headers; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; import org.xnio.Options; import org.xnio.SslClientAuthMode; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * @author Carter Kozak */ @State(Scope.Benchmark) public class SimpleBenchmarkState { private static final int PORT = 4433; @SuppressWarnings("unused") // Set by JMH @Param({"HTTP", "HTTPS"}) private ListenerType listenerType; private Undertow undertow; private CloseableHttpClient client; private String baseUri; @Setup public final void before() { Undertow.Builder builder = Undertow.builder() .setIoThreads(4) .setWorkerThreads(64) .setServerOption(UndertowOptions.SHUTDOWN_TIMEOUT, 10000) .setSocketOption(Options.SSL_CLIENT_AUTH_MODE, SslClientAuthMode.NOT_REQUESTED) .setHandler(Handlers.routing() /* Responds with N bytes where N is the value of the "size" query parameter. */ .get("/blocking", new BlockingHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { String value = exchange.getQueryParameters().get("size").getFirst(); int bytes = Integer.parseInt(value); exchange.getResponseHeaders() .put(Headers.CONTENT_TYPE, "application/octet-stream") .put(Headers.CONTENT_LENGTH, value); OutputStream out = exchange.getOutputStream(); for (int i = 0; i < bytes; i++) { out.write(1); } } })) /* Responds with the the string value of the number of bytes received. */ .post("/blocking", new BlockingHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { InputStream stream = exchange.getInputStream(); long length = BenchmarkUtils.length(stream); String stringValue = Long.toString(length); exchange.getResponseHeaders() .put(Headers.CONTENT_TYPE, "text/plain") .put(Headers.CONTENT_LENGTH, stringValue.length()); exchange.getResponseSender().send(stringValue); } }))); switch (listenerType) { case HTTP: builder.addHttpListener(PORT, "0.0.0.0"); break; case HTTPS: builder.addHttpsListener(PORT, "0.0.0.0", TLSUtils.newServerContext()); break; default: throw new IllegalStateException("Unknown protocol: " + listenerType); } undertow = builder.build(); undertow.start(); client = HttpClients.custom() .disableConnectionState() .disableAutomaticRetries() .setSSLContext(TLSUtils.newClientContext()) .setMaxConnPerRoute(100) .setMaxConnTotal(100) .build(); baseUri = (listenerType == ListenerType.HTTP ? "http" : "https") + "://localhost:" + PORT; } @TearDown public final void after() throws IOException { if (undertow != null) { undertow.stop(); undertow = null; } if (client != null) { client.close(); client = null; } } public CloseableHttpClient client() { return client; } public String getBaseUri() { return baseUri; } public enum ListenerType {HTTP, HTTPS} } undertow-2.2.16.Final/benchmarks/src/main/java/io/undertow/benchmarks/SimpleBenchmarks.java000066400000000000000000000110551420065311100317320ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.benchmarks; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.InputStreamEntity; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.concurrent.TimeUnit; /** * @author Carter Kozak */ @Measurement(iterations = 3, time = 3) @Warmup(iterations = 3, time = 3) @Fork(1) @Threads(32) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) public class SimpleBenchmarks { @Benchmark public void benchmarkBlockingEmptyGet(SimpleBenchmarkState state) throws IOException { try (CloseableHttpResponse response = state.client() .execute(new HttpGet(state.getBaseUri() + "/blocking?size=0"))) { validateLength(response, 0L); } } @Benchmark public void benchmarkBlockingLargeGet(SimpleBenchmarkState state) throws IOException { try (CloseableHttpResponse response = state.client() .execute(new HttpGet(state.getBaseUri() + "/blocking?size=256000"))) { validateLength(response, 256000L); } } @Benchmark public void benchmarkBlockingEmptyPost(SimpleBenchmarkState state) throws IOException { try (CloseableHttpResponse response = state.client() .execute(new HttpPost(state.getBaseUri() + "/blocking"))) { String result = asString(validate(response).getEntity()); if (!"0".equals(result)) { throw new IllegalStateException("expected 0, was " + result); } } } @Benchmark public void benchmarkBlockingLargePost(SimpleBenchmarkState state) throws IOException { HttpPost post = new HttpPost(state.getBaseUri() + "/blocking"); post.setEntity(new InputStreamEntity(new StubInputStream(256000))); try (CloseableHttpResponse response = state.client().execute(post)) { String result = asString(validate(response).getEntity()); if (!"256000".equals(result)) { throw new IllegalStateException("expected 256000, was " + result); } } } private String asString(HttpEntity entity) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); entity.writeTo(baos); return baos.toString("UTF-8"); } private void validateLength(HttpResponse response, long expectedLength) throws IOException { long length = BenchmarkUtils.length(validate(response).getEntity().getContent()); if (length != expectedLength) { throw new IllegalStateException("Unexpected length " + length); } } private T validate(T response) { int status = response.getStatusLine().getStatusCode(); if (status != 200) { throw new IllegalStateException("Unexpected status code " + status); } return response; } private static final class StubInputStream extends InputStream { private int bytes; StubInputStream(int bytes) { this.bytes = bytes; } @Override public int read() { if (bytes <= 0) { return -1; } bytes--; return 1; } @Override public int available() { return bytes; } } } undertow-2.2.16.Final/benchmarks/src/main/java/io/undertow/benchmarks/TLSUtils.java000066400000000000000000000110451420065311100301650ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.benchmarks; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import java.io.IOException; import java.io.InputStream; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; /** * Helper utility to create {@link SSLContext} instances. * * @author Carter Kozak */ final class TLSUtils { private static final String SERVER_KEY_STORE = "server.keystore"; private static final String SERVER_TRUST_STORE = "server.truststore"; private static final String CLIENT_KEY_STORE = "client.keystore"; private static final String CLIENT_TRUST_STORE = "client.truststore"; private static final char[] STORE_PASSWORD = "password".toCharArray(); static SSLContext newServerContext() { try { KeyStore keyStore = loadKeyStore(SERVER_KEY_STORE); KeyStore trustStore = loadKeyStore(SERVER_TRUST_STORE); return createSSLContext(keyStore, trustStore); } catch (IOException e) { throw new RuntimeException("Failed to create server SSLContext", e); } } static SSLContext newClientContext() { try { KeyStore keyStore = loadKeyStore(CLIENT_KEY_STORE); KeyStore trustStore = loadKeyStore(CLIENT_TRUST_STORE); return createSSLContext(keyStore, trustStore); } catch (IOException e) { throw new RuntimeException("Failed to create client SSLContext", e); } } private static KeyStore loadKeyStore(final String name) throws IOException { try (InputStream stream = TLSUtils.class.getClassLoader().getResourceAsStream(name)) { if (stream == null) { throw new RuntimeException("Could not load keystore"); } try { KeyStore loadedKeystore = KeyStore.getInstance("JKS"); loadedKeystore.load(stream, STORE_PASSWORD); return loadedKeystore; } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException e) { throw new IOException("Unable to load KeyStore " + name, e); } } } private static SSLContext createSSLContext(final KeyStore keyStore, final KeyStore trustStore) throws IOException { KeyManager[] keyManagers; try { KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, STORE_PASSWORD); keyManagers = keyManagerFactory.getKeyManagers(); } catch (NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException e) { throw new IOException("Unable to initialise KeyManager[]", e); } TrustManager[] trustManagers; try { TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(trustStore); trustManagers = trustManagerFactory.getTrustManagers(); } catch (NoSuchAlgorithmException | KeyStoreException e) { throw new IOException("Unable to initialise TrustManager[]", e); } try { SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); sslContext.init(keyManagers, trustManagers, null); return sslContext; } catch (NoSuchAlgorithmException | KeyManagementException e) { throw new IOException("Unable to create and initialise the SSLContext", e); } } private TLSUtils() {} } undertow-2.2.16.Final/benchmarks/src/main/resources/000077500000000000000000000000001420065311100223345ustar00rootroot00000000000000undertow-2.2.16.Final/benchmarks/src/main/resources/client.keystore000066400000000000000000000042031420065311100254000ustar00rootroot00000000000000client+)W'l`bLr R߈nQ ٿuЮճ7T߂{n($Ez(f!LS@؃ 'WKVPO)ν#y* 2 &E*f +.Gjd "o'3_g\Uh:iޠߣruL uGII59zGV@3S%0 DZ(hފ3f?d  -'06m0x҃Fc^ğs!!`tܟ*^^ׄSn?*fg `t U,i;}_φڧC vocMN )eDG^ȼ|]K͖rwhtmc|ti*+*X.509:060P0  *H  0]1 0 UGB10 UState1 0 UCity1 0 U Org1 0 U OU10U Test Client0 130119142920Z 230117142920Z0]1 0 UGB10 UState1 0 UCity1 0 U Org1 0 U OU10U Test Client0"0  *H 0 \3f J$ ܙ6j<&@0_X-D)\ds\5exNbmå[ߒ4;E$y6?W3=5j^^S?Е34/߅:=$D!u 4:< 50wQ@0u)r*zVy$ F)S4֮pq$Kk,(Q0; 0  *H  \.USB:(p P0Y`,|: ͮicO,/8_aq6:Non쮏0hTcB~b`${e̯ІP\(QejEhS ISN d\vvfughn )CWb_K`δڕK-kE202|fSbZ kECf^I.tP$Ip}yJundertow-2.2.16.Final/benchmarks/src/main/resources/client.truststore000066400000000000000000000015651420065311100260010ustar00rootroot00000000000000serverW6i/$g)"~Rq>6"$~ \}kVJkƬT`)z拣4lMz50  *H  c;gS3;KmOn/TϜSJ;'Lm;hr=l,VꙜaqW?Mӧ!͓ 3$y\m9;kr&Iy )GS[}*'MH#oW΢ #j2(ɼAn lX-hǓ JmUWPtNSTY8os$С@"чo.`D,1hUDWA^@VG܅ q̗undertow-2.2.16.Final/benchmarks/src/main/resources/logging.properties000066400000000000000000000030661420065311100261050ustar00rootroot00000000000000 # # JBoss, Home of Professional Open Source. # Copyright 2012 Red Hat, Inc., and individual contributors # as indicated by the @author tags. # # 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. # # Additional logger names to configure (root logger is always configured) loggers=org.xnio.listener,org.xnio.ssl,org.apache,io.undertow.util.TestHttpClient # Root logger configuration logger.level=${test.level:ERROR} logger.handlers=CONSOLE # Console handler configuration handler.CONSOLE=org.jboss.logmanager.handlers.ConsoleHandler handler.CONSOLE.properties=autoFlush,target handler.CONSOLE.target=SYSTEM_ERR handler.CONSOLE.level=ALL handler.CONSOLE.autoFlush=true handler.CONSOLE.formatter=PATTERN # The log format pattern formatter.PATTERN=org.jboss.logmanager.formatters.PatternFormatter formatter.PATTERN.properties=pattern formatter.PATTERN.pattern=%d{HH:mm:ss,SSS} %-5p (%t) [%c] <%F:%L> %m%n logger.org.xnio.listener.level=DEBUG logger.org.xnio.ssl.level=DEBUG logger.org.apache.level=WARN logger.org.apache.useParentHandlers=false logger.io.undertow.util.TestHttpClient.level=WARN undertow-2.2.16.Final/benchmarks/src/main/resources/server.keystore000066400000000000000000000042001420065311100254250ustar00rootroot00000000000000server@&y,˪Ŕ ?^b+ɁZ1jGˠq'D{uѪ  ~,j6 9fjN% zjډ.7 bFg5 /dpB`hxLΤ<"[}NBq ײEՈӉjQW`Hr&hI[ȏ2'DSleBTGޙ$ԢX-A>kQ)|k,nK~6zsurǚG[3w%$A;3VN,sH1ͤbM !Ĵ@_Ao۫[0@QX "?gC]"Ϊ, ZFɰ| ?'0O\/څ?F)b6GN\9ld"8 l~Ŷ5bWquP[Ngnf1r'=EKOQnᲀX5½v\3=gUWѨaEWq^lf $7NG;:U}WHbpb|v K5/Fly(YADKO-]*s@ϕǗK¡7*1 b8'!]P'_")Ɲ*? QXsX%F&0}D?MdajGmZE>S|4} (p ct*X.5096020Ph0  *H  0[1 0 UGB10 UState1 0 UCity1 0 U Org1 0 U OU10U localhost0 130119142752Z 230117142752Z0[1 0 UGB10 UState1 0 UCity1 0 U Org1 0 U OU10U localhost0"0  *H 0 fДq./[(v툞zJCZdFGibD}ypC FqT =ҚN ;~:sY#qGHvEPCqڷ"Pؕ%Hd|}s-F~cNSk<mH?y\$$>W6i/$g)"~Rq>6"$~ \}kVJkƬT`)z拣4lMz50  *H  c;gS3;KmOn/TϜSJ;'Lm;hr=l,VꙜaqW?Mӧ!͓ 3$y\m9;kr&Iy )GS[}*'MH#oW΢ #j2(ɼAn lX-hǓ JmUWPtNSTY8os$С@"чo.`D,1hUDWA^@VONoEm/G-X䲧undertow-2.2.16.Final/benchmarks/src/main/resources/server.truststore000066400000000000000000000016471420065311100260320ustar00rootroot000000000000004cn=test client, ou=ou, o=org, l=city, st=state, c=gb=> 3X.509:060P0  *H  0]1 0 UGB10 UState1 0 UCity1 0 U Org1 0 U OU10U Test Client0 130119142920Z 230117142920Z0]1 0 UGB10 UState1 0 UCity1 0 U Org1 0 U OU10U Test Client0"0  *H 0 \3f J$ ܙ6j<&@0_X-D)\ds\5exNbmå[ߒ4;E$y6?W3=5j^^S?Е34/߅:=$D!u 4:< 50wQ@0u)r*zVy$ F)S4֮pq$Kk,(Q0; 0  *H  \.USB:(p P0Y`,|: ͮicO,/8_aq6:Non쮏0hTcB~b`${e̯ІP\(QejEhS ISN d\vvfughn )CWb_K`δڕK-kE202|fSbZ kECf^I.tPe$SA7Ahundertow-2.2.16.Final/core/000077500000000000000000000000001420065311100154225ustar00rootroot00000000000000undertow-2.2.16.Final/core/pom.xml000066400000000000000000000660101420065311100167420ustar00rootroot00000000000000 4.0.0 io.undertow undertow-parent 2.2.16.Final io.undertow undertow-core 2.2.16.Final Undertow Core INFO false false false false false false 8192 io.undertow undertow-parser-generator provided org.jboss.logging jboss-logging org.jboss.logging jboss-logging-processor provided org.jboss.xnio xnio-api org.jboss.xnio xnio-nio runtime org.jboss.threads jboss-threads org.eclipse.jetty.alpn alpn-api provided io.netty netty-all test com.twitter hpack test junit junit test org.apache.directory.server apacheds-all test org.apache.httpcomponents httpclient test org.apache.httpcomponents httpmime test org.easymock easymock test org.jboss.logmanager jboss-logmanager test com.h2database h2 test org.wildfly.openssl wildfly-openssl ${version.org.wildfly.openssl} test src/main/resources true src/test/resources src/test/java **/*.java org.apache.felix maven-bundle-plugin generate-manifest manifest io.undertow.*;version=${project.version};-noimport:=true org.eclipse.jetty.*;resolution:=optional;version="[1,2)", !org.xnio._private, org.xnio.*;version="[3.8,4)", !., !sun.*, * org.apache.maven.plugins maven-jar-plugin test-jar test-jar tests ${project.build.outputDirectory}/META-INF/MANIFEST.MF org.bitstrings.maven.plugins dependencypath-maven-plugin 1.1.1 set-all set org.apache.maven.plugins maven-surefire-plugin true reversealphabetical ${ajp} ${proxy} ${dump} ${https} ${openssl} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${org.wildfly.openssl.path} ${jacoco.agent.argLine} ${surefire.system.args} ${libraryPath} mac mac /usr/local/opt/openssl/lib openssl test.openssl -Djava.library.path=${java.library.path} jetty-alpn jetty-alpn org.mortbay.jetty.alpn alpn-boot ${version.org.mortbay.jetty.alpn} test org.apache.maven.plugins maven-surefire-plugin true reversealphabetical true true ${dump} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false true ${project.build.directory}/surefire-proxy-reports proxy org.apache.maven.plugins maven-surefire-plugin proxy test test true reversealphabetical true ${dump} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${project.build.directory}/surefire-proxy-reports proxy-ajp test test true reversealphabetical true ${dump} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${project.build.directory}/surefire-ajp-reports proxy-https test test true reversealphabetical true ${dump} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${project.build.directory}/surefire-https-reports proxy-h2 test test true reversealphabetical true ${dump} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${project.build.directory}/surefire-h2-reports proxy-h2c test test true reversealphabetical true ${dump} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${project.build.directory}/surefire-h2c-reports proxy-h2c-upgrade test test true reversealphabetical true ${dump} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${project.build.directory}/surefire-h2c-upgrade-reports jdk9 9 ${project.basedir}/src/main/java9 ${project.build.directory}/classes-java9 org.apache.maven.plugins maven-jar-plugin true org.apache.maven.plugins maven-antrun-plugin compile-java9 compile run org.apache.maven.plugins maven-checkstyle-plugin check-style compile checkstyle check-style-java9 compile checkstyle ${java9.sourceDirectory} org.apache.maven.plugins maven-resources-plugin copy-resources prepare-package copy-resources ${project.build.outputDirectory}/META-INF/versions/9 ${java9.build.outputDirectory} false undertow-2.2.16.Final/core/src/000077500000000000000000000000001420065311100162115ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/000077500000000000000000000000001420065311100171355ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/000077500000000000000000000000001420065311100200565ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/000077500000000000000000000000001420065311100204655ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/000077500000000000000000000000001420065311100223345ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/Handlers.java000066400000000000000000000555131420065311100247500ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow; import io.undertow.attribute.ExchangeAttribute; import io.undertow.predicate.Predicate; import io.undertow.predicate.PredicateParser; import io.undertow.predicate.PredicatesHandler; import io.undertow.server.HttpHandler; import io.undertow.server.JvmRouteHandler; import io.undertow.server.RoutingHandler; import io.undertow.server.handlers.SetErrorHandler; import io.undertow.server.handlers.AccessControlListHandler; import io.undertow.server.handlers.LearningPushHandler; import io.undertow.server.handlers.DateHandler; import io.undertow.server.handlers.DisableCacheHandler; import io.undertow.server.handlers.ExceptionHandler; import io.undertow.server.handlers.GracefulShutdownHandler; import io.undertow.server.handlers.HttpContinueAcceptingHandler; import io.undertow.server.handlers.HttpContinueReadHandler; import io.undertow.server.handlers.HttpTraceHandler; import io.undertow.server.handlers.IPAddressAccessControlHandler; import io.undertow.server.handlers.NameVirtualHostHandler; import io.undertow.server.handlers.PathHandler; import io.undertow.server.handlers.PathTemplateHandler; import io.undertow.server.handlers.PredicateContextHandler; import io.undertow.server.handlers.PredicateHandler; import io.undertow.server.handlers.ProxyPeerAddressHandler; import io.undertow.server.handlers.RedirectHandler; import io.undertow.server.handlers.RequestDumpingHandler; import io.undertow.server.handlers.RequestLimit; import io.undertow.server.handlers.RequestLimitingHandler; import io.undertow.server.handlers.ResponseRateLimitingHandler; import io.undertow.server.handlers.SetAttributeHandler; import io.undertow.server.handlers.SetHeaderHandler; import io.undertow.server.handlers.URLDecodingHandler; import io.undertow.server.handlers.builder.PredicatedHandler; import io.undertow.server.handlers.proxy.ProxyClient; import io.undertow.server.handlers.proxy.ProxyHandler; import io.undertow.server.handlers.resource.ResourceHandler; import io.undertow.server.handlers.resource.ResourceManager; import io.undertow.server.handlers.sse.ServerSentEventConnectionCallback; import io.undertow.server.handlers.sse.ServerSentEventHandler; import io.undertow.websockets.WebSocketConnectionCallback; import io.undertow.websockets.WebSocketProtocolHandshakeHandler; import java.util.List; import java.util.concurrent.TimeUnit; /** * Utility class with convenience methods for dealing with handlers * * @author Stuart Douglas */ public class Handlers { /** * Creates a new path handler, with the default handler specified * * @param defaultHandler The default handler * @return A new path handler */ public static PathHandler path(final HttpHandler defaultHandler) { return new PathHandler(defaultHandler); } /** * Creates a new path handler * * @return A new path handler */ public static PathHandler path() { return new PathHandler(); } /** * * @return a new path template handler */ public static PathTemplateHandler pathTemplate() { return new PathTemplateHandler(); } /** * * @param rewriteQueryParams If the query params should be rewritten * @return The routing handler */ public static RoutingHandler routing(boolean rewriteQueryParams) { return new RoutingHandler(rewriteQueryParams); } /** * * @return a new routing handler */ public static RoutingHandler routing() { return new RoutingHandler(); } /** * * @param rewriteQueryParams If the query params should be rewritten * @return The path template handler */ public static PathTemplateHandler pathTemplate(boolean rewriteQueryParams) { return new PathTemplateHandler(rewriteQueryParams); } /** * Creates a new virtual host handler * * @return A new virtual host handler */ public static NameVirtualHostHandler virtualHost() { return new NameVirtualHostHandler(); } /** * Creates a new virtual host handler using the provided default handler * * @return A new virtual host handler */ public static NameVirtualHostHandler virtualHost(final HttpHandler defaultHandler) { return new NameVirtualHostHandler().setDefaultHandler(defaultHandler); } /** * Creates a new virtual host handler that uses the provided handler as the root handler for the given hostnames. * * @param hostHandler The host handler * @param hostnames The host names * @return A new virtual host handler */ public static NameVirtualHostHandler virtualHost(final HttpHandler hostHandler, String... hostnames) { NameVirtualHostHandler handler = new NameVirtualHostHandler(); for (String host : hostnames) { handler.addHost(host, hostHandler); } return handler; } /** * Creates a new virtual host handler that uses the provided handler as the root handler for the given hostnames. * * @param defaultHandler The default handler * @param hostHandler The host handler * @param hostnames The host names * @return A new virtual host handler */ public static NameVirtualHostHandler virtualHost(final HttpHandler defaultHandler, final HttpHandler hostHandler, String... hostnames) { return virtualHost(hostHandler, hostnames).setDefaultHandler(defaultHandler); } /** * @param sessionHandler The web socket session handler * @return The web socket handler */ public static WebSocketProtocolHandshakeHandler websocket(final WebSocketConnectionCallback sessionHandler) { return new WebSocketProtocolHandshakeHandler(sessionHandler); } /** * @param sessionHandler The web socket session handler * @param next The handler to invoke if the web socket connection fails * @return The web socket handler */ public static WebSocketProtocolHandshakeHandler websocket(final WebSocketConnectionCallback sessionHandler, final HttpHandler next) { return new WebSocketProtocolHandshakeHandler(sessionHandler, next); } /** * A handler for server sent events * * * @param callback The server sent events callback * @return A new server sent events handler */ public static ServerSentEventHandler serverSentEvents(ServerSentEventConnectionCallback callback) { return new ServerSentEventHandler(callback); } /** * A handler for server sent events * * @return A new server sent events handler */ public static ServerSentEventHandler serverSentEvents() { return new ServerSentEventHandler(); } /** * Return a new resource handler * * @param resourceManager The resource manager to use * @return A new resource handler */ public static ResourceHandler resource(final ResourceManager resourceManager) { return new ResourceHandler(resourceManager).setDirectoryListingEnabled(false); } /** * Returns a new redirect handler * * @param location The redirect location * @return A new redirect handler */ public static RedirectHandler redirect(final String location) { return new RedirectHandler(location); } /** * Returns a new HTTP trace handler. This handler will handle HTTP TRACE * requests as per the RFC. *

* WARNING: enabling trace requests may leak information, in general it is recommended that * these be disabled for security reasons. * * @param next The next handler in the chain * @return A HTTP trace handler */ public static HttpTraceHandler trace(final HttpHandler next) { return new HttpTraceHandler(next); } /** * Returns a new HTTP handler that sets the Date: header. * * This is no longer necessary, as it is handled by the connectors directly. * * @param next The next handler in the chain * @return A new date handler */ @Deprecated public static DateHandler date(final HttpHandler next) { return new DateHandler(next); } /** * Returns a new predicate handler, that will delegate to one of the two provided handlers based on the value of the * provided predicate. * * @param predicate The predicate * @param trueHandler The handler that will be executed if the predicate is true * @param falseHandler The handler that will be exected if the predicate is false * @return A new predicate handler * @see Predicate * @see io.undertow.predicate.Predicates */ public static PredicateHandler predicate(final Predicate predicate, final HttpHandler trueHandler, final HttpHandler falseHandler) { return new PredicateHandler(predicate, trueHandler, falseHandler); } /** * @param next The next handler * @return a handler that sets up a new predicate context */ public static HttpHandler predicateContext(HttpHandler next) { return new PredicateContextHandler(next); } public static PredicatesHandler predicates(final List handlers, HttpHandler next) { final PredicatesHandler predicatesHandler = new PredicatesHandler(next); for(PredicatedHandler handler : handlers) { predicatesHandler.addPredicatedHandler(handler); } return predicatesHandler; } /** * Returns a handler that sets a response header * * @param next The next handler in the chain * @param headerName The name of the header * @param headerValue The header value * @return A new set header handler */ public static SetHeaderHandler header(final HttpHandler next, final String headerName, final String headerValue) { return new SetHeaderHandler(next, headerName, headerValue); } /** * Returns a handler that sets a response header * * @param next The next handler in the chain * @param headerName The name of the header * @param headerValue The header value * @return A new set header handler */ public static SetHeaderHandler header(final HttpHandler next, final String headerName, final ExchangeAttribute headerValue) { return new SetHeaderHandler(next, headerName, headerValue); } /** * Returns a new handler that can allow or deny access to a resource based on IP address * * @param next The next handler in the chain * @param defaultAllow Determine if a non-matching address will be allowed by default * @return A new IP access control handler */ public static final IPAddressAccessControlHandler ipAccessControl(final HttpHandler next, boolean defaultAllow) { return new IPAddressAccessControlHandler(next).setDefaultAllow(defaultAllow); } /** * Returns a new handler that can allow or deny access to a resource based an at attribute of the exchange * * @param next The next handler in the chain * @param defaultAllow Determine if a non-matching user agent will be allowed by default * @return A new user agent access control handler */ public static final AccessControlListHandler acl(final HttpHandler next, boolean defaultAllow, ExchangeAttribute attribute) { return new AccessControlListHandler(next, attribute).setDefaultAllow(defaultAllow); } /** * A handler that automatically handles HTTP 100-continue responses, by sending a continue * response when the first attempt is made to read from the request channel. * * @param next The next handler in the chain * @return A new continue handler */ public static final HttpContinueReadHandler httpContinueRead(final HttpHandler next) { return new HttpContinueReadHandler(next); } /** * Returns a handler that sends back a HTTP 100 continue response if the given predicate resolves to true. * * This handler differs from the one returned by {@link #httpContinueRead(io.undertow.server.HttpHandler)} in * that it will eagerly send the response, and not wait for the first read attempt. * * @param next The next handler * @param accept The predicate used to determine if the request should be accepted * @return The accepting handler */ public static final HttpContinueAcceptingHandler httpContinueAccepting(final HttpHandler next, final Predicate accept) { return new HttpContinueAcceptingHandler(next, accept); } /** * Returns a handler that sends back a HTTP 100 continue response to all requests. * * This handler differs from the one returned by {@link #httpContinueRead(io.undertow.server.HttpHandler)} in * that it will eagerly send the response, and not wait for the first read attempt. * * @param next The next handler * @return The accepting handler */ public static final HttpContinueAcceptingHandler httpContinueAccepting(final HttpHandler next) { return new HttpContinueAcceptingHandler(next); } /** * A handler that will decode the URL, query parameters and to the specified charset. *

* If you are using this handler you must set the {@link io.undertow.UndertowOptions#DECODE_URL} parameter to false. *

* This is not as efficient as using the parsers built in UTF-8 decoder. Unless you need to decode to something other * than UTF-8 you should rely on the parsers decoding instead. * * @param next The next handler in the chain * @param charset The charset to decode to * @return a new url decoding handler */ public static final URLDecodingHandler urlDecoding(final HttpHandler next, final String charset) { return new URLDecodingHandler(next, charset); } /** * Returns an attribute setting handler that can be used to set an arbitrary attribute on the exchange. * This includes functions such as adding and removing headers etc. * * @param next The next handler * @param attribute The attribute to set, specified as a string presentation of an {@link io.undertow.attribute.ExchangeAttribute} * @param value The value to set, specified an a string representation of an {@link io.undertow.attribute.ExchangeAttribute} * @param classLoader The class loader to use to parser the exchange attributes * @return The handler */ public static SetAttributeHandler setAttribute(final HttpHandler next, final String attribute, final String value, final ClassLoader classLoader) { return new SetAttributeHandler(next, attribute, value, classLoader); } /** * Creates the set of handlers that are required to perform a simple rewrite. * @param condition The rewrite condition * @param target The rewrite target if the condition matches * @param next The next handler * @return */ public static HttpHandler rewrite(final String condition, final String target, final ClassLoader classLoader, final HttpHandler next) { return predicateContext(predicate(PredicateParser.parse(condition, classLoader), setAttribute(next, "%R", target, classLoader), next)); } /** * Returns a new handler that decodes the URL and query parameters into the specified charset, assuming it * has not already been done by the connector. For this handler to take effect the parameter * {@link UndertowOptions#DECODE_URL} must have been set to false. * * @param charset The charset to decode * @param next The next handler * @return A handler that decodes the URL */ public static HttpHandler urlDecodingHandler(final String charset, final HttpHandler next) { return new URLDecodingHandler(next, charset); } /** * Returns a new handler that can be used to wait for all requests to finish before shutting down the server gracefully. * * @param next The next http handler * @return The graceful shutdown handler */ public static GracefulShutdownHandler gracefulShutdown(HttpHandler next) { return new GracefulShutdownHandler(next); } /** * Returns a new handler that sets the peer address based on the X-Forwarded-For and * X-Forwarded-Proto header * @param next The next http handler * @return The handler */ public static ProxyPeerAddressHandler proxyPeerAddress(HttpHandler next) { return new ProxyPeerAddressHandler(next); } /** * Handler that appends the JVM route to the session cookie * @param sessionCookieName The session cookie name * @param jvmRoute The JVM route to append * @param next The next handler * @return The handler */ public static JvmRouteHandler jvmRoute(final String sessionCookieName, final String jvmRoute, HttpHandler next) { return new JvmRouteHandler(next, sessionCookieName, jvmRoute); } /** * Returns a handler that limits the maximum number of requests that can run at a time. * * @param maxRequest The maximum number of requests * @param queueSize The maximum number of queued requests * @param next The next handler * @return The handler */ public static RequestLimitingHandler requestLimitingHandler(final int maxRequest, final int queueSize, HttpHandler next) { return new RequestLimitingHandler(maxRequest, queueSize, next); } /** * Returns a handler that limits the maximum number of requests that can run at a time. * * @param requestLimit The request limit object that can be shared between handlers, to apply the same limits across multiple handlers * @param next The next handler * @return The handler */ public static RequestLimitingHandler requestLimitingHandler(final RequestLimit requestLimit, HttpHandler next) { return new RequestLimitingHandler(requestLimit, next); } /** * Returns a handler that can act as a load balancing reverse proxy. * * @param proxyClient The proxy client to use to connect to the remote server * @param maxRequestTime The maximum amount of time a request can be in progress before it is forcibly closed * @param next The next handler to invoke if the proxy client does not know how to proxy the request * @return The proxy handler */ public static ProxyHandler proxyHandler(ProxyClient proxyClient, int maxRequestTime, HttpHandler next) { return ProxyHandler.builder().setProxyClient(proxyClient).setNext(next).setMaxRequestTime(maxRequestTime).build(); } /** * Returns a handler that can act as a load balancing reverse proxy. * * @param proxyClient The proxy client to use to connect to the remote server * @param next The next handler to invoke if the proxy client does not know how to proxy the request * @return The proxy handler */ public static ProxyHandler proxyHandler(ProxyClient proxyClient, HttpHandler next) { return ProxyHandler.builder().setProxyClient(proxyClient).setNext(next).build(); } /** * Returns a handler that can act as a load balancing reverse proxy. * * @param proxyClient The proxy client to use to connect to the remote server * @return The proxy handler */ public static ProxyHandler proxyHandler(ProxyClient proxyClient) { return ProxyHandler.builder().setProxyClient(proxyClient).build(); } /** * Handler that sets the headers that disable caching of the response * @param next The next handler * @return The handler */ public static HttpHandler disableCache(final HttpHandler next) { return new DisableCacheHandler(next); } /** * Returns a handler that dumps requests to the log for debugging purposes. * * @param next The next handler * @return The request dumping handler */ public static HttpHandler requestDump(final HttpHandler next) { return new RequestDumpingHandler(next); } /** * Returns a handler that maps exceptions to additional handlers * @param next The next handler * @return The exception handler */ public static ExceptionHandler exceptionHandler(final HttpHandler next) { return new ExceptionHandler(next); } /** * * A handler that limits the download speed to a set number of bytes/period * * @param next The next handler * @param bytes The number of bytes per time period * @param time The time period * @param timeUnit The units of the time period */ public static ResponseRateLimitingHandler responseRateLimitingHandler(HttpHandler next, int bytes,long time, TimeUnit timeUnit) { return new ResponseRateLimitingHandler(next, bytes, time, timeUnit); } /** * Creates a handler that automatically learns which resources to push based on the referer header * * @param maxEntries The maximum number of entries to store * @param maxAge The maximum age of the entries * @param next The next handler * @return A caching push handler */ public static LearningPushHandler learningPushHandler(int maxEntries, int maxAge, HttpHandler next) { return new LearningPushHandler(maxEntries, maxAge, next); } /** * A handler that sets response code but continues the exchange so the servlet's * error page can be returned. * * @param responseCode The response code to set * @param next The next handler * @return A Set Error handler */ public static SetErrorHandler setErrorHandler(int responseCode, HttpHandler next) { return new SetErrorHandler(next, responseCode); } /** * Creates a handler that automatically learns which resources to push based on the referer header * * @param maxEntries The maximum number of entries to store * @param next The next handler * @return A caching push handler */ public static LearningPushHandler learningPushHandler(int maxEntries, HttpHandler next) { return new LearningPushHandler(maxEntries, -1, next); } private Handlers() { } public static void handlerNotNull(final HttpHandler handler) { if (handler == null) { throw UndertowMessages.MESSAGES.handlerCannotBeNull(); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/Undertow.java000066400000000000000000000675521420065311100250250ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow; import io.undertow.connector.ByteBufferPool; import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.server.ConnectorStatistics; import io.undertow.server.DefaultByteBufferPool; import io.undertow.server.HttpHandler; import io.undertow.server.OpenListener; import io.undertow.server.protocol.ajp.AjpOpenListener; import io.undertow.server.protocol.http.AlpnOpenListener; import io.undertow.server.protocol.http.HttpOpenListener; import io.undertow.server.protocol.http2.Http2OpenListener; import io.undertow.server.protocol.http2.Http2UpgradeHandler; import io.undertow.server.protocol.proxy.ProxyProtocolOpenListener; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.Option; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.StreamConnection; import org.xnio.Xnio; import org.xnio.XnioWorker; import org.xnio.channels.AcceptingChannel; import org.xnio.ssl.JsseSslUtils; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import java.io.IOException; import java.net.Inet4Address; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; /** * Convenience class used to build an Undertow server. *

* * @author Stuart Douglas */ public final class Undertow { private final int bufferSize; private final int ioThreads; private final int workerThreads; private final boolean directBuffers; private final List listeners = new ArrayList<>(); private volatile List listenerInfo; private final HttpHandler rootHandler; private final OptionMap workerOptions; private final OptionMap socketOptions; private final OptionMap serverOptions; /** * Will be true when a {@link XnioWorker} instance was NOT provided to the {@link Builder}. * When true, a new worker will be created during {@link Undertow#start()}, * and shutdown when {@link Undertow#stop()} is called. *

* Will be false when a {@link XnioWorker} instance was provided to the {@link Builder}. * When false, the provided {@link #worker} will be used instead of creating a new one in {@link Undertow#start()}. * Also, when false, the {@link #worker} will NOT be shutdown when {@link Undertow#stop()} is called. */ private final boolean internalWorker; private ByteBufferPool byteBufferPool; private XnioWorker worker; private Executor sslEngineDelegatedTaskExecutor; private List> channels; private Xnio xnio; private Undertow(Builder builder) { this.byteBufferPool = builder.byteBufferPool; this.bufferSize = byteBufferPool != null ? byteBufferPool.getBufferSize() : builder.bufferSize; this.directBuffers = byteBufferPool != null ? byteBufferPool.isDirect() : builder.directBuffers; this.ioThreads = builder.ioThreads; this.workerThreads = builder.workerThreads; this.listeners.addAll(builder.listeners); this.rootHandler = builder.handler; this.worker = builder.worker; this.sslEngineDelegatedTaskExecutor = builder.sslEngineDelegatedTaskExecutor; this.internalWorker = builder.worker == null; this.workerOptions = builder.workerOptions.getMap(); this.socketOptions = builder.socketOptions.getMap(); this.serverOptions = builder.serverOptions.getMap(); } /** * @return A builder that can be used to create an Undertow server instance */ public static Builder builder() { return new Builder(); } public synchronized void start() { UndertowLogger.ROOT_LOGGER.infof("starting server: %s", Version.getFullVersionString()); xnio = Xnio.getInstance(Undertow.class.getClassLoader()); channels = new ArrayList<>(); try { if (internalWorker) { worker = xnio.createWorker(OptionMap.builder() .set(Options.WORKER_IO_THREADS, ioThreads) .set(Options.CONNECTION_HIGH_WATER, 1000000) .set(Options.CONNECTION_LOW_WATER, 1000000) .set(Options.WORKER_TASK_CORE_THREADS, workerThreads) .set(Options.WORKER_TASK_MAX_THREADS, workerThreads) .set(Options.TCP_NODELAY, true) .set(Options.CORK, true) .addAll(workerOptions) .getMap()); } OptionMap socketOptions = OptionMap.builder() .set(Options.WORKER_IO_THREADS, worker.getIoThreadCount()) .set(Options.TCP_NODELAY, true) .set(Options.REUSE_ADDRESSES, true) .set(Options.BALANCING_TOKENS, 1) .set(Options.BALANCING_CONNECTIONS, 2) .set(Options.BACKLOG, 1000) .addAll(this.socketOptions) .getMap(); OptionMap serverOptions = OptionMap.builder() .set(UndertowOptions.NO_REQUEST_TIMEOUT, 60 * 1000) .addAll(this.serverOptions) .getMap(); ByteBufferPool buffers = this.byteBufferPool; if (buffers == null) { buffers = new DefaultByteBufferPool(directBuffers, bufferSize, -1, 4); } listenerInfo = new ArrayList<>(); for (ListenerConfig listener : listeners) { UndertowLogger.ROOT_LOGGER.debugf("Configuring listener with protocol %s for interface %s and port %s", listener.type, listener.host, listener.port); final HttpHandler rootHandler = listener.rootHandler != null ? listener.rootHandler : this.rootHandler; OptionMap socketOptionsWithOverrides = OptionMap.builder().addAll(socketOptions).addAll(listener.overrideSocketOptions).getMap(); if (listener.type == ListenerType.AJP) { AjpOpenListener openListener = new AjpOpenListener(buffers, serverOptions); openListener.setRootHandler(rootHandler); final ChannelListener finalListener; if (listener.useProxyProtocol) { finalListener = new ProxyProtocolOpenListener(openListener, null, buffers, OptionMap.EMPTY); } else { finalListener = openListener; } ChannelListener> acceptListener = ChannelListeners.openListenerAdapter(finalListener); AcceptingChannel server = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(listener.host), listener.port), acceptListener, socketOptionsWithOverrides); server.resumeAccepts(); channels.add(server); listenerInfo.add(new ListenerInfo("ajp", server.getLocalAddress(), openListener, null, server)); } else { OptionMap undertowOptions = OptionMap.builder().set(UndertowOptions.BUFFER_PIPELINED_DATA, true).addAll(serverOptions).getMap(); boolean http2 = serverOptions.get(UndertowOptions.ENABLE_HTTP2, false); if (listener.type == ListenerType.HTTP) { HttpOpenListener openListener = new HttpOpenListener(buffers, undertowOptions); HttpHandler handler = rootHandler; if (http2) { handler = new Http2UpgradeHandler(handler); } openListener.setRootHandler(handler); final ChannelListener finalListener; if (listener.useProxyProtocol) { finalListener = new ProxyProtocolOpenListener(openListener, null, buffers, OptionMap.EMPTY); } else { finalListener = openListener; } ChannelListener> acceptListener = ChannelListeners.openListenerAdapter(finalListener); AcceptingChannel server = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(listener.host), listener.port), acceptListener, socketOptionsWithOverrides); server.resumeAccepts(); channels.add(server); listenerInfo.add(new ListenerInfo("http", server.getLocalAddress(), openListener, null, server)); } else if (listener.type == ListenerType.HTTPS) { OpenListener openListener; HttpOpenListener httpOpenListener = new HttpOpenListener(buffers, undertowOptions); httpOpenListener.setRootHandler(rootHandler); if (http2) { AlpnOpenListener alpn = new AlpnOpenListener(buffers, undertowOptions, httpOpenListener); Http2OpenListener http2Listener = new Http2OpenListener(buffers, undertowOptions); http2Listener.setRootHandler(rootHandler); alpn.addProtocol(Http2OpenListener.HTTP2, http2Listener, 10); alpn.addProtocol(Http2OpenListener.HTTP2_14, http2Listener, 7); openListener = alpn; } else { openListener = httpOpenListener; } UndertowXnioSsl xnioSsl; if (listener.sslContext != null) { xnioSsl = new UndertowXnioSsl(xnio, OptionMap.create(Options.USE_DIRECT_BUFFERS, true), listener.sslContext, sslEngineDelegatedTaskExecutor); } else { OptionMap.Builder builder = OptionMap.builder() .addAll(socketOptionsWithOverrides); if (!socketOptionsWithOverrides.contains(Options.SSL_PROTOCOL)) { builder.set(Options.SSL_PROTOCOL, "TLSv1.2"); } xnioSsl = new UndertowXnioSsl( xnio, OptionMap.create(Options.USE_DIRECT_BUFFERS, true), JsseSslUtils.createSSLContext(listener.keyManagers, listener.trustManagers, new SecureRandom(), builder.getMap()), sslEngineDelegatedTaskExecutor); } AcceptingChannel sslServer; if (listener.useProxyProtocol) { ChannelListener> acceptListener = ChannelListeners.openListenerAdapter(new ProxyProtocolOpenListener(openListener, xnioSsl, buffers, socketOptionsWithOverrides)); sslServer = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(listener.host), listener.port), (ChannelListener) acceptListener, socketOptionsWithOverrides); } else { ChannelListener> acceptListener = ChannelListeners.openListenerAdapter(openListener); sslServer = xnioSsl.createSslConnectionServer(worker, new InetSocketAddress(Inet4Address.getByName(listener.host), listener.port), (ChannelListener) acceptListener, socketOptionsWithOverrides); } sslServer.resumeAccepts(); channels.add(sslServer); listenerInfo.add(new ListenerInfo("https", sslServer.getLocalAddress(), openListener, xnioSsl, sslServer)); } } } } catch (Exception e) { if(internalWorker && worker != null) { worker.shutdownNow(); } throw new RuntimeException(e); } } public synchronized void stop() { UndertowLogger.ROOT_LOGGER.infof("stopping server: %s", Version.getFullVersionString()); if (channels != null) { for (AcceptingChannel channel : channels) { IoUtils.safeClose(channel); } channels = null; } /* * Only shutdown the worker if it was created during start() */ if (internalWorker && worker != null) { Integer shutdownTimeoutMillis = serverOptions.get(UndertowOptions.SHUTDOWN_TIMEOUT); worker.shutdown(); try { if (shutdownTimeoutMillis == null) { worker.awaitTermination(); } else { if (!worker.awaitTermination(shutdownTimeoutMillis, TimeUnit.MILLISECONDS)) { worker.shutdownNow(); } } } catch (InterruptedException e) { worker.shutdownNow(); Thread.currentThread().interrupt(); throw new RuntimeException(e); } worker = null; } xnio = null; listenerInfo = null; } public Xnio getXnio() { return xnio; } public XnioWorker getWorker() { return worker; } public List getListenerInfo() { if (listenerInfo == null) { throw UndertowMessages.MESSAGES.serverNotStarted(); } return Collections.unmodifiableList(listenerInfo); } public enum ListenerType { HTTP, HTTPS, AJP } private static class ListenerConfig { final ListenerType type; final int port; final String host; final KeyManager[] keyManagers; final TrustManager[] trustManagers; final SSLContext sslContext; final HttpHandler rootHandler; final OptionMap overrideSocketOptions; final boolean useProxyProtocol; private ListenerConfig(final ListenerType type, final int port, final String host, KeyManager[] keyManagers, TrustManager[] trustManagers, HttpHandler rootHandler) { this.type = type; this.port = port; this.host = host; this.keyManagers = keyManagers; this.trustManagers = trustManagers; this.rootHandler = rootHandler; this.sslContext = null; this.overrideSocketOptions = OptionMap.EMPTY; this.useProxyProtocol = false; } private ListenerConfig(final ListenerType type, final int port, final String host, SSLContext sslContext, HttpHandler rootHandler) { this.type = type; this.port = port; this.host = host; this.rootHandler = rootHandler; this.keyManagers = null; this.trustManagers = null; this.sslContext = sslContext; this.overrideSocketOptions = OptionMap.EMPTY; this.useProxyProtocol = false; } private ListenerConfig(final ListenerBuilder listenerBuilder) { this.type = listenerBuilder.type; this.port = listenerBuilder.port; this.host = listenerBuilder.host; this.rootHandler = listenerBuilder.rootHandler; this.keyManagers = listenerBuilder.keyManagers; this.trustManagers = listenerBuilder.trustManagers; this.sslContext = listenerBuilder.sslContext; this.overrideSocketOptions = listenerBuilder.overrideSocketOptions; this.useProxyProtocol = listenerBuilder.useProxyProtocol; } } public static final class ListenerBuilder { ListenerType type; int port; String host; KeyManager[] keyManagers; TrustManager[] trustManagers; SSLContext sslContext; HttpHandler rootHandler; OptionMap overrideSocketOptions = OptionMap.EMPTY; boolean useProxyProtocol; public ListenerBuilder setType(ListenerType type) { this.type = type; return this; } public ListenerBuilder setPort(int port) { this.port = port; return this; } public ListenerBuilder setHost(String host) { this.host = host; return this; } public ListenerBuilder setKeyManagers(KeyManager[] keyManagers) { this.keyManagers = keyManagers; return this; } public ListenerBuilder setTrustManagers(TrustManager[] trustManagers) { this.trustManagers = trustManagers; return this; } public ListenerBuilder setSslContext(SSLContext sslContext) { this.sslContext = sslContext; return this; } public ListenerBuilder setRootHandler(HttpHandler rootHandler) { this.rootHandler = rootHandler; return this; } public ListenerBuilder setOverrideSocketOptions(OptionMap overrideSocketOptions) { this.overrideSocketOptions = overrideSocketOptions; return this; } public ListenerBuilder setUseProxyProtocol(boolean useProxyProtocol) { this.useProxyProtocol = useProxyProtocol; return this; } } public static final class Builder { private int bufferSize; private int ioThreads; private int workerThreads; private boolean directBuffers; private final List listeners = new ArrayList<>(); private HttpHandler handler; private XnioWorker worker; private Executor sslEngineDelegatedTaskExecutor; private ByteBufferPool byteBufferPool; private final OptionMap.Builder workerOptions = OptionMap.builder(); private final OptionMap.Builder socketOptions = OptionMap.builder(); private final OptionMap.Builder serverOptions = OptionMap.builder(); private Builder() { ioThreads = Math.max(Runtime.getRuntime().availableProcessors(), 2); workerThreads = ioThreads * 8; long maxMemory = Runtime.getRuntime().maxMemory(); //smaller than 64mb of ram we use 512b buffers if (maxMemory < 64 * 1024 * 1024) { //use 512b buffers directBuffers = false; bufferSize = 512; } else if (maxMemory < 128 * 1024 * 1024) { //use 1k buffers directBuffers = true; bufferSize = 1024; } else { //use 16k buffers for best performance //as 16k is generally the max amount of data that can be sent in a single write() call directBuffers = true; bufferSize = 1024 * 16 - 20; //the 20 is to allow some space for protocol headers, see UNDERTOW-1209 } } public Undertow build() { return new Undertow(this); } @Deprecated public Builder addListener(int port, String host) { listeners.add(new ListenerConfig(ListenerType.HTTP, port, host, null, null, null)); return this; } @Deprecated public Builder addListener(int port, String host, ListenerType listenerType) { listeners.add(new ListenerConfig(listenerType, port, host, null, null, null)); return this; } public Builder addListener(ListenerBuilder listenerBuilder) { listeners.add(new ListenerConfig(listenerBuilder)); return this; } public Builder addHttpListener(int port, String host) { listeners.add(new ListenerConfig(ListenerType.HTTP, port, host, null, null, null)); return this; } public Builder addHttpsListener(int port, String host, KeyManager[] keyManagers, TrustManager[] trustManagers) { listeners.add(new ListenerConfig(ListenerType.HTTPS, port, host, keyManagers, trustManagers, null)); return this; } public Builder addHttpsListener(int port, String host, SSLContext sslContext) { listeners.add(new ListenerConfig(ListenerType.HTTPS, port, host, sslContext, null)); return this; } public Builder addAjpListener(int port, String host) { listeners.add(new ListenerConfig(ListenerType.AJP, port, host, null, null, null)); return this; } public Builder addHttpListener(int port, String host, HttpHandler rootHandler) { listeners.add(new ListenerConfig(ListenerType.HTTP, port, host, null, null, rootHandler)); return this; } public Builder addHttpsListener(int port, String host, KeyManager[] keyManagers, TrustManager[] trustManagers, HttpHandler rootHandler) { listeners.add(new ListenerConfig(ListenerType.HTTPS, port, host, keyManagers, trustManagers, rootHandler)); return this; } public Builder addHttpsListener(int port, String host, SSLContext sslContext, HttpHandler rootHandler) { listeners.add(new ListenerConfig(ListenerType.HTTPS, port, host, sslContext, rootHandler)); return this; } public Builder addAjpListener(int port, String host, HttpHandler rootHandler) { listeners.add(new ListenerConfig(ListenerType.AJP, port, host, null, null, rootHandler)); return this; } public Builder setBufferSize(final int bufferSize) { this.bufferSize = bufferSize; return this; } @Deprecated public Builder setBuffersPerRegion(final int buffersPerRegion) { return this; } public Builder setIoThreads(final int ioThreads) { this.ioThreads = ioThreads; return this; } public Builder setWorkerThreads(final int workerThreads) { this.workerThreads = workerThreads; return this; } public Builder setDirectBuffers(final boolean directBuffers) { this.directBuffers = directBuffers; return this; } public Builder setHandler(final HttpHandler handler) { this.handler = handler; return this; } public Builder setServerOption(final Option option, final T value) { serverOptions.set(option, value); return this; } public Builder setSocketOption(final Option option, final T value) { socketOptions.set(option, value); return this; } public Builder setWorkerOption(final Option option, final T value) { workerOptions.set(option, value); return this; } /** * When null (the default), a new {@link XnioWorker} will be created according * to the various worker-related configuration (ioThreads, workerThreads, workerOptions) * when {@link Undertow#start()} is called. * Additionally, this newly created worker will be shutdown when {@link Undertow#stop()} is called. *
*

* When non-null, the provided {@link XnioWorker} will be reused instead of creating a new {@link XnioWorker} * when {@link Undertow#start()} is called. * Additionally, the provided {@link XnioWorker} will NOT be shutdown when {@link Undertow#stop()} is called. * Essentially, the lifecycle of the provided worker must be maintained outside of the {@link Undertow} instance. */ public Builder setWorker(XnioWorker worker) { this.worker = worker; return this; } public Builder setSslEngineDelegatedTaskExecutor(Executor sslEngineDelegatedTaskExecutor) { this.sslEngineDelegatedTaskExecutor = sslEngineDelegatedTaskExecutor; return this; } public Builder setByteBufferPool(ByteBufferPool byteBufferPool) { this.byteBufferPool = byteBufferPool; return this; } } public static class ListenerInfo { private final String protcol; private final SocketAddress address; private final OpenListener openListener; private final UndertowXnioSsl ssl; private final AcceptingChannel channel; private volatile boolean suspended = false; public ListenerInfo(String protcol, SocketAddress address, OpenListener openListener, UndertowXnioSsl ssl, AcceptingChannel channel) { this.protcol = protcol; this.address = address; this.openListener = openListener; this.ssl = ssl; this.channel = channel; } public String getProtcol() { return protcol; } public SocketAddress getAddress() { return address; } public SSLContext getSslContext() { if(ssl == null) { return null; } return ssl.getSslContext(); } public void setSslContext(SSLContext sslContext) { if(ssl != null) { //just ignore it if this is not a SSL listener ssl.updateSSLContext(sslContext); } } public synchronized void suspend() { suspended = true; channel.suspendAccepts(); CountDownLatch latch = new CountDownLatch(1); //the channel may be in the middle of an accept, we need to close from the IO thread channel.getIoThread().execute(new Runnable() { @Override public void run() { try { openListener.closeConnections(); } finally { latch.countDown(); } } }); try { latch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } } public synchronized void resume() { suspended = false; channel.resumeAccepts(); } public boolean isSuspended() { return suspended; } public ConnectorStatistics getConnectorStatistics() { return openListener.getConnectorStatistics(); } public void setSocketOption(Optionoption, T value) throws IOException { channel.setOption(option, value); } public void setServerOptions(OptionMap options) { openListener.setUndertowOptions(options); } @Override public String toString() { return "ListenerInfo{" + "protcol='" + protcol + '\'' + ", address=" + address + ", sslContext=" + getSslContext() + '}'; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/UndertowLogger.java000066400000000000000000000534631420065311100261610ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow; import io.undertow.client.ClientConnection; import io.undertow.protocols.ssl.SslConduit; import io.undertow.server.HttpServerExchange; import io.undertow.server.ServerConnection; import io.undertow.server.handlers.sse.ServerSentEventConnection; import io.undertow.util.HeaderMap; import io.undertow.util.HttpString; import org.jboss.logging.BasicLogger; import org.jboss.logging.Logger; import org.jboss.logging.annotations.Cause; import org.jboss.logging.annotations.LogMessage; import org.jboss.logging.annotations.Message; import org.jboss.logging.annotations.MessageLogger; import org.xnio.channels.ReadTimeoutException; import org.xnio.channels.WriteTimeoutException; import org.xnio.ssl.SslConnection; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.URI; import java.nio.file.Path; import java.security.GeneralSecurityException; import java.util.Date; import java.util.List; import java.util.concurrent.RejectedExecutionException; import static org.jboss.logging.Logger.Level.DEBUG; import static org.jboss.logging.Logger.Level.ERROR; import static org.jboss.logging.Logger.Level.INFO; import static org.jboss.logging.Logger.Level.WARN; /** * log messages start at 5000 * * @author Stuart Douglas */ @MessageLogger(projectCode = "UT") public interface UndertowLogger extends BasicLogger { UndertowLogger ROOT_LOGGER = Logger.getMessageLogger(UndertowLogger.class, UndertowLogger.class.getPackage().getName()); UndertowLogger CLIENT_LOGGER = Logger.getMessageLogger(UndertowLogger.class, ClientConnection.class.getPackage().getName()); UndertowLogger PREDICATE_LOGGER = Logger.getMessageLogger(UndertowLogger.class, UndertowLogger.class.getPackage().getName() + ".predicate"); UndertowLogger REQUEST_LOGGER = Logger.getMessageLogger(UndertowLogger.class, UndertowLogger.class.getPackage().getName() + ".request"); UndertowLogger SESSION_LOGGER = Logger.getMessageLogger(UndertowLogger.class, UndertowLogger.class.getPackage().getName() + ".session"); UndertowLogger SECURITY_LOGGER = Logger.getMessageLogger(UndertowLogger.class, UndertowLogger.class.getPackage().getName() + ".request.security"); UndertowLogger PROXY_REQUEST_LOGGER = Logger.getMessageLogger(UndertowLogger.class, UndertowLogger.class.getPackage().getName() + ".proxy"); UndertowLogger REQUEST_DUMPER_LOGGER = Logger.getMessageLogger(UndertowLogger.class, UndertowLogger.class.getPackage().getName() + ".request.dump"); /** * Logger used for IO exceptions. Generally these should be suppressed, because they are of little interest, and it is easy for an * attacker to fill up the logs by intentionally causing IO exceptions. */ UndertowLogger REQUEST_IO_LOGGER = Logger.getMessageLogger(UndertowLogger.class, UndertowLogger.class.getPackage().getName() + ".request.io"); UndertowLogger ERROR_RESPONSE = Logger.getMessageLogger(UndertowLogger.class, UndertowLogger.class.getPackage().getName() + ".request.error-response"); @LogMessage(level = ERROR) @Message(id = 5001, value = "An exception occurred processing the request") void exceptionProcessingRequest(@Cause Throwable cause); // @LogMessage(level = INFO) // @Message(id = 5002, value = "Exception reading file %s: %s") // void exceptionReadingFile(final Path file, final IOException e); @LogMessage(level = ERROR) @Message(id = 5003, value = "IOException reading from channel") void ioExceptionReadingFromChannel(@Cause IOException e); @LogMessage(level = ERROR) @Message(id = 5005, value = "Cannot remove uploaded file %s") void cannotRemoveUploadedFile(Path file); @LogMessage(level = DEBUG) @Message(id = 5006, value = "Connection from %s terminated as request header was larger than %s") void requestHeaderWasTooLarge(SocketAddress address, int size); @LogMessage(level = DEBUG) @Message(id = 5007, value = "Request was not fully consumed") void requestWasNotFullyConsumed(); @LogMessage(level = DEBUG) @Message(id = 5008, value = "An invalid token '%s' with value '%s' has been received.") void invalidTokenReceived(final String tokenName, final String tokenValue); @LogMessage(level = DEBUG) @Message(id = 5009, value = "A mandatory token %s is missing from the request.") void missingAuthorizationToken(final String tokenName); @LogMessage(level = DEBUG) @Message(id = 5010, value = "Verification of authentication tokens for user '%s' has failed using mechanism '%s'.") void authenticationFailed(final String userName, final String mechanism); @LogMessage(level = ERROR) @Message(id = 5011, value = "Ignoring AJP request with prefix %s") void ignoringAjpRequestWithPrefixCode(byte prefix); @LogMessage(level = DEBUG) @Message(id = 5013, value = "An IOException occurred") void ioException(@Cause IOException e); @LogMessage(level = DEBUG) @Message(id = 5014, value = "Failed to parse request") void failedToParseRequest(@Cause Throwable e); @LogMessage(level = ERROR) @Message(id = 5015, value = "Error rotating access log") void errorRotatingAccessLog(@Cause IOException e); @LogMessage(level = ERROR) @Message(id = 5016, value = "Error writing access log") void errorWritingAccessLog(@Cause IOException e); @LogMessage(level = ERROR) @Message(id = 5017, value = "Unknown variable %s. For the literal percent character use two percent characters: '%%'") void unknownVariable(String token); @LogMessage(level = ERROR) @Message(id = 5018, value = "Exception invoking close listener %s") void exceptionInvokingCloseListener(ServerConnection.CloseListener l, @Cause Throwable e); // @LogMessage(level = Logger.Level.ERROR) // @Message(id = 5019, value = "Cannot upgrade connection") // void cannotUpgradeConnection(@Cause Exception e); @LogMessage(level = ERROR) @Message(id = 5020, value = "Error writing JDBC log") void errorWritingJDBCLog(@Cause Exception e); // @LogMessage(level = Logger.Level.ERROR) // @Message(id = 5021, value = "Proxy request to %s timed out") // void proxyRequestTimedOut(String requestURI); @LogMessage(level = ERROR) @Message(id = 5022, value = "Exception generating error page %s") void exceptionGeneratingErrorPage(@Cause Exception e, String location); @LogMessage(level = ERROR) @Message(id = 5023, value = "Exception handling request to %s") void exceptionHandlingRequest(@Cause Throwable t, String requestURI); @LogMessage(level = ERROR) @Message(id = 5024, value = "Could not register resource change listener for caching resource manager, automatic invalidation of cached resource will not work") void couldNotRegisterChangeListener(@Cause Exception e); // @LogMessage(level = Logger.Level.ERROR) // @Message(id = 5025, value = "Could not initiate SPDY connection and no HTTP fallback defined") // void couldNotInitiateSpdyConnection(); // // @LogMessage(level = INFO) // @Message(id = 5026, value = "Jetty ALPN support not found on boot class path, %s client will not be available.") // void jettyALPNNotFound(String protocol); @LogMessage(level = ERROR) @Message(id = 5027, value = "Timing out request to %s") void timingOutRequest(String requestURI); @LogMessage(level = ERROR) @Message(id = 5028, value = "Proxy request to %s failed") void proxyRequestFailed(String requestURI, @Cause Exception e); // @LogMessage(level = Logger.Level.ERROR) // @Message(id = 5030, value = "Proxy request to %s could not resolve a backend server") // void proxyRequestFailedToResolveBackend(String requestURI); @LogMessage(level = ERROR) @Message(id = 5031, value = "Proxy request to %s could not connect to backend server %s") void proxyFailedToConnectToBackend(String requestURI, URI uri); @LogMessage(level = ERROR) @Message(id = 5032, value = "Listener not making progress on framed channel, closing channel to prevent infinite loop") void listenerNotProgressing(); // @LogMessage(level = Logger.Level.ERROR) // @Message(id = 5033, value = "Failed to initiate HTTP2 connection") // void couldNotInitiateHttp2Connection(); @LogMessage(level = ERROR) @Message(id = 5034, value = "Remote endpoint failed to send initial settings frame in HTTP2 connection, frame type %s") void remoteEndpointFailedToSendInitialSettings(int type); @LogMessage(level = DEBUG) @Message(id = 5035, value = "Closing channel because of parse timeout for remote address %s") void parseRequestTimedOut(java.net.SocketAddress remoteAddress); @LogMessage(level = ERROR) @Message(id = 5036, value = "ALPN negotiation failed for %s and no fallback defined, closing connection") void noALPNFallback(SocketAddress address); /** * Undertow mod_cluster proxy messages */ @LogMessage(level = WARN) @Message(id = 5037, value = "Name of the cookie containing the session id, %s, had been too long and was truncated to: %s") void stickySessionCookieLengthTruncated(String original, String current); @LogMessage(level = DEBUG) @Message(id = 5038, value = "Balancer created: id: %s, name: %s, stickySession: %s, stickySessionCookie: %s, stickySessionPath: %s, stickySessionRemove: %s, stickySessionForce: %s, waitWorker: %s, maxattempts: %s") void balancerCreated(int id, String name, boolean stickySession, String stickySessionCookie, String stickySessionPath, boolean stickySessionRemove, boolean stickySessionForce, int waitWorker, int maxattempts); @LogMessage(level = INFO) @Message(id = 5039, value = "Undertow starts mod_cluster proxy advertisements on %s with frequency %s ms") void proxyAdvertisementsStarted(String address, int frequency); @LogMessage(level = DEBUG) @Message(id = 5040, value = "Gonna send payload:\n%s") void proxyAdvertiseMessagePayload(String payload); @LogMessage(level = ERROR) @Message(id = 5041, value = "Cannot send advertise message. Address: %s") void proxyAdvertiseCannotSendMessage(@Cause Exception e, InetSocketAddress address); @LogMessage(level = DEBUG) @Message(id = 5042, value = "Undertow mod_cluster proxy MCMPHandler created") void mcmpHandlerCreated(); @LogMessage(level = ERROR) @Message(id = 5043, value = "Error in processing MCMP commands: Type:%s, Mess: %s") void mcmpProcessingError(String type, String errString); @LogMessage(level = INFO) @Message(id = 5044, value = "Removing node %s") void removingNode(String jvmRoute); // Aliases intentionally omitted from INFO level. @LogMessage(level = INFO) @Message(id = 5045, value = "Registering context %s, for node %s") void registeringContext(String contextPath, String jvmRoute); // Context path and JVMRoute redundantly logged with DEBUG soa s to provide meaning for aliases. @LogMessage(level = DEBUG) @Message(id = 5046, value = "Registering context %s, for node %s, with aliases %s") void registeringContext(String contextPath, String jvmRoute, List aliases); @LogMessage(level = INFO) @Message(id = 5047, value = "Unregistering context %s, from node %s") void unregisteringContext(String contextPath, String jvmRoute); @LogMessage(level = DEBUG) @Message(id = 5048, value = "Node %s in error") void nodeIsInError(String jvmRoute); @LogMessage(level = DEBUG) @Message(id = 5049, value = "NodeConfig created: connectionURI: %s, balancer: %s, load balancing group: %s, jvmRoute: %s, flushPackets: %s, flushwait: %s, ping: %s," + "ttl: %s, timeout: %s, maxConnections: %s, cacheConnections: %s, requestQueueSize: %s, queueNewRequests: %s") void nodeConfigCreated(URI connectionURI, String balancer, String domain, String jvmRoute, boolean flushPackets, int flushwait, int ping, long ttl, int timeout, int maxConnections, int cacheConnections, int requestQueueSize, boolean queueNewRequests); @LogMessage(level = ERROR) @Message(id = 5050, value = "Failed to process management request") void failedToProcessManagementReq(@Cause Exception e); @LogMessage(level = ERROR) @Message(id = 5051, value = "Failed to send ping response") void failedToSendPingResponse(@Cause Exception e); @LogMessage(level = DEBUG) @Message(id = 5052, value = "Failed to send ping response, node.getJvmRoute(): %s, jvmRoute: %s") void failedToSendPingResponseDBG(@Cause Exception e, String node, String jvmRoute); @LogMessage(level = INFO) @Message(id = 5053, value = "Registering node %s, connection: %s") void registeringNode(String jvmRoute, URI connectionURI); @LogMessage(level = DEBUG) @Message(id = 5054, value = "MCMP processing, key: %s, value: %s") void mcmpKeyValue(HttpString name, String value); @LogMessage(level = DEBUG) @Message(id = 5055, value = "HttpClientPingTask run for connection: %s") void httpClientPingTask(URI connection); @LogMessage(level = DEBUG) @Message(id = 5056, value = "Received node load in STATUS message, node jvmRoute: %s, load: %s") void receivedNodeLoad(String jvmRoute, String loadValue); @LogMessage(level = DEBUG) @Message(id = 5057, value = "Sending MCMP response to destination: %s, HTTP status: %s, Headers: %s, response: %s") void mcmpSendingResponse(InetSocketAddress destination, int status, HeaderMap headers, String response); @LogMessage(level = WARN) @Message(id = 5058, value = "Could not bind multicast socket to %s (%s address): %s; make sure your multicast address is of the same type as the IP stack (IPv4 or IPv6). Multicast socket will not be bound to an address, but this may lead to cross talking (see http://www.jboss.org/community/docs/DOC-9469 for details).") void potentialCrossTalking(InetAddress group, String s, String localizedMessage); @LogMessage(level = WARN) @Message(id = 5060, value = "Predicate %s uses old style square braces to define predicates, which will be removed in a future release. predicate[value] should be changed to predicate(value)") void oldStylePredicateSyntax(String string); @Message(id=5061, value = "More than %s restarts detected, breaking assumed infinite loop") IllegalStateException maxRestartsExceeded(int maxRestarts); @LogMessage(level = ERROR) @Message(id = 5062, value = "Pattern parse error") void extendedAccessLogPatternParseError(@Cause Throwable t); @LogMessage(level = ERROR) @Message(id = 5063, value = "Unable to decode with rest of chars starting: %s") void extendedAccessLogUnknownToken(String token); @LogMessage(level = ERROR) @Message(id = 5064, value = "No closing ) found for in decode") void extendedAccessLogMissingClosing(); @LogMessage(level = ERROR) @Message(id = 5065, value = "The next characters couldn't be decoded: %s") void extendedAccessLogCannotDecode(String chars); @LogMessage(level = ERROR) @Message(id = 5066, value = "X param for servlet request, couldn't decode value: %s") void extendedAccessLogCannotDecodeXParamValue(String value); @LogMessage(level = ERROR) @Message(id = 5067, value = "X param in wrong format. Needs to be 'x-#(...)'") void extendedAccessLogBadXParam(); @LogMessage(level = INFO) @Message(id = 5068, value = "Pattern was just empty or whitespace") void extendedAccessLogEmptyPattern(); @LogMessage(level = ERROR) @Message(id = 5069, value = "Failed to write JDBC access log") void failedToWriteJdbcAccessLog(@Cause Exception e); @LogMessage(level = ERROR) @Message(id = 5070, value = "Failed to write pre-cached file") void failedToWritePreCachedFile(); @LogMessage(level = ERROR) @Message(id = 5071, value = "Undertow request failed %s") void undertowRequestFailed(@Cause Throwable t, HttpServerExchange exchange); @LogMessage(level = WARN) @Message(id = 5072, value = "Thread %s (id=%s) has been active for %s milliseconds (since %s) to serve the same request for %s and may be stuck (configured threshold for this StuckThreadDetectionValve is %s seconds). There is/are %s thread(s) in total that are monitored by this Valve and may be stuck.") void stuckThreadDetected(String threadName, long threadId, long active, Date start, String requestUri, int threshold, int stuckCount, @Cause Throwable stackTrace); @LogMessage(level = WARN) @Message(id = 5073, value = "Thread %s (id=%s) was previously reported to be stuck but has completed. It was active for approximately %s milliseconds. There is/are still %s thread(s) that are monitored by this Valve and may be stuck.") void stuckThreadCompleted(String threadName, long threadId, long active, int stuckCount); @LogMessage(level = ERROR) @Message(id = 5074, value = "Failed to invoke error callback %s for SSE task") void failedToInvokeFailedCallback(ServerSentEventConnection.EventCallback callback, @Cause Exception e); @Message(id = 5075, value = "Unable to resolve mod_cluster management host's address for '%s'") IllegalStateException unableToResolveModClusterManagementHost(String providedHost); @LogMessage(level = ERROR) @Message(id = 5076, value = "SSL read loop detected. This should not happen, please report this to the Undertow developers. Current state %s") void sslReadLoopDetected(SslConduit sslConduit); @LogMessage(level = ERROR) @Message(id = 5077, value = "SSL unwrap buffer overflow detected. This should not happen, please report this to the Undertow developers. Current state %s") void sslBufferOverflow(SslConduit sslConduit); // @LogMessage(level = ERROR) // @Message(id = 5078, value = "ALPN connection failed") // void alpnConnectionFailed(@Cause Exception e); @LogMessage(level = ERROR) @Message(id = 5079, value = "ALPN negotiation on %s failed") void alpnConnectionFailed(SslConnection connection); @LogMessage(level = ERROR) @Message(id = 5080, value = "HttpServerExchange cannot have both async IO resumed and dispatch() called in the same cycle") void resumedAndDispatched(); @LogMessage(level = ERROR) @Message(id = 5081, value = "Response has already been started, cannot proxy request %s") void cannotProxyStartedRequest(HttpServerExchange exchange); @Message(id = 5082, value = "Configured mod_cluster management host address cannot be a wildcard address (%s)!") IllegalArgumentException cannotUseWildcardAddressAsModClusterManagementHost(String providedAddress); @Message(id = 5083, value = "Unexpected end of compressed input") IOException unexpectedEndOfCompressedInput(); @Message(id = 5084, value = "Attempted to write %s bytes however content-length has been set to %s") IOException dataLargerThanContentLength(long totalToWrite, long responseContentLength); @LogMessage(level = DEBUG) @Message(id = 5085, value = "Connection %s for exchange %s was not closed cleanly, forcibly closing connection") void responseWasNotTerminated(ServerConnection connection, HttpServerExchange exchange); @LogMessage(level = ERROR) @Message(id = 5086, value = "Failed to accept SSL request") void failedToAcceptSSLRequest(@Cause Exception e); // @LogMessage(level = ERROR) // @Message(id = 5087, value = "Failed to use the server order") // void failedToUseServerOrder(@Cause ReflectiveOperationException e); @LogMessage(level = ERROR) @Message(id = 5088, value = "Failed to execute ServletOutputStream.closeAsync() on IO thread") void closeAsyncFailed(@Cause IOException e); @Message(id = 5089, value = "Method parameter '%s' cannot be null") IllegalArgumentException nullParameter(String name); @LogMessage(level = ERROR) @Message(id = 5090, value = "Unexpected failure") void handleUnexpectedFailure(@Cause Throwable t); @LogMessage(level = ERROR) @Message(id = 5091, value = "Failed to initialize DirectByteBufferDeallocator") void directBufferDeallocatorInitializationFailed(@Cause Throwable t); @LogMessage(level = DEBUG) @Message(id = 5092, value = "Failed to free direct buffer") void directBufferDeallocationFailed(@Cause Throwable t); @LogMessage(level = DEBUG) @Message(id = 5093, value = "Blocking read timed out") void blockingReadTimedOut(@Cause ReadTimeoutException rte); @LogMessage(level = DEBUG) @Message(id = 5094, value = "Blocking write timed out") void blockingWriteTimedOut(@Cause WriteTimeoutException rte); @LogMessage(level = DEBUG) @Message(id = 5095, value = "SSLEngine delegated task was rejected") void sslEngineDelegatedTaskRejected(@Cause RejectedExecutionException ree); @LogMessage(level = DEBUG) @Message(id = 5096, value = "Authentication failed for digest header %s in %s") void authenticationFailedFor(final String header, final HttpServerExchange exchange, final @Cause Exception e); @LogMessage(level = DEBUG) @Message(id = 5097, value = "Failed to obtain subject for %s") void failedToObtainSubject(final HttpServerExchange exchange, final @Cause GeneralSecurityException e); @LogMessage(level = DEBUG) @Message(id = 5098, value = "GSSAPI negotiation failed for %s") void failedToNegotiateAtGSSAPI(final HttpServerExchange exchange, final @Cause Throwable e); } undertow-2.2.16.Final/core/src/main/java/io/undertow/UndertowMessages.java000066400000000000000000000706311420065311100265050ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow; import java.io.IOException; import java.nio.channels.ClosedChannelException; import java.nio.file.Path; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLPeerUnverifiedException; import io.undertow.server.RequestTooBigException; import io.undertow.server.handlers.form.MultiPartParserDefinition; import io.undertow.util.UrlDecodeException; import org.jboss.logging.Messages; import org.jboss.logging.annotations.Cause; import org.jboss.logging.annotations.Message; import org.jboss.logging.annotations.MessageBundle; import io.undertow.predicate.PredicateBuilder; import io.undertow.protocols.http2.HpackException; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.HttpString; import io.undertow.util.ParameterLimitException; import io.undertow.util.BadRequestException; import org.xnio.channels.ReadTimeoutException; import org.xnio.channels.WriteTimeoutException; /** * @author Stuart Douglas */ @MessageBundle(projectCode = "UT") public interface UndertowMessages { UndertowMessages MESSAGES = Messages.getBundle(UndertowMessages.class); @Message(id = 1, value = "Maximum concurrent requests must be larger than zero.") IllegalArgumentException maximumConcurrentRequestsMustBeLargerThanZero(); @Message(id = 2, value = "The response has already been started") IllegalStateException responseAlreadyStarted(); // id = 3 @Message(id = 4, value = "getResponseChannel() has already been called") IllegalStateException responseChannelAlreadyProvided(); @Message(id = 5, value = "getRequestChannel() has already been called") IllegalStateException requestChannelAlreadyProvided(); // id = 6 // id = 7 @Message(id = 8, value = "Handler cannot be null") IllegalArgumentException handlerCannotBeNull(); @Message(id = 9, value = "Path must be specified") IllegalArgumentException pathMustBeSpecified(); @Message(id = 10, value = "Session is invalid %s") IllegalStateException sessionIsInvalid(String sessionId); @Message(id = 11, value = "Session manager must not be null") IllegalStateException sessionManagerMustNotBeNull(); @Message(id = 12, value = "Session manager was not attached to the request. Make sure that the SessionAttachmentHandler is installed in the handler chain") IllegalStateException sessionManagerNotFound(); @Message(id = 13, value = "Argument %s cannot be null") IllegalArgumentException argumentCannotBeNull(final String argument); // @Message(id = 14, value = "close() called with data still to be flushed. Please call shutdownWrites() and then call flush() until it returns true before calling close()") // IOException closeCalledWithDataStillToBeFlushed(); // // @Message(id = 16, value = "Could not add cookie as cookie handler was not present in the handler chain") // IllegalStateException cookieHandlerNotPresent(); @Message(id = 17, value = "Form value is a file, use getFileItem() instead") IllegalStateException formValueIsAFile(); @Message(id = 18, value = "Form value is a String, use getValue() instead") IllegalStateException formValueIsAString(); // // @Message(id = 19, value = "Connection from %s terminated as request entity was larger than %s") // IOException requestEntityWasTooLarge(SocketAddress address, long size); @Message(id = 20, value = "Connection terminated as request was larger than %s") RequestTooBigException requestEntityWasTooLarge(long size); @Message(id = 21, value = "Session already invalidated") IllegalStateException sessionAlreadyInvalidated(); @Message(id = 22, value = "The specified hash algorithm '%s' can not be found.") IllegalArgumentException hashAlgorithmNotFound(String algorithmName); @Message(id = 23, value = "An invalid Base64 token has been received.") IllegalArgumentException invalidBase64Token(@Cause final IOException cause); @Message(id = 24, value = "An invalidly formatted nonce has been received.") IllegalArgumentException invalidNonceReceived(); @Message(id = 25, value = "Unexpected token '%s' within header.") IllegalArgumentException unexpectedTokenInHeader(final String name); @Message(id = 26, value = "Invalid header received.") IllegalArgumentException invalidHeader(); @Message(id = 27, value = "Could not find session cookie config in the request") IllegalStateException couldNotFindSessionCookieConfig(); // // @Message(id = 28, value = "Session %s already exists") // IllegalStateException sessionAlreadyExists(final String id); @Message(id = 29, value = "Channel was closed mid chunk, if you have attempted to write chunked data you cannot shutdown the channel until after it has all been written.") IOException chunkedChannelClosedMidChunk(); @Message(id = 30, value = "User %s successfully authenticated.") String userAuthenticated(final String userName); @Message(id = 31, value = "User %s has logged out.") String userLoggedOut(final String userName); // // @Message(id = 33, value = "Authentication type %s cannot be combined with %s") // IllegalStateException authTypeCannotBeCombined(String type, String existing); @Message(id = 34, value = "Stream is closed") IOException streamIsClosed(); @Message(id = 35, value = "Cannot get stream as startBlocking has not been invoked") IllegalStateException startBlockingHasNotBeenCalled(); @Message(id = 36, value = "Connection terminated parsing multipart data") IOException connectionTerminatedReadingMultiPartData(); @Message(id = 37, value = "Failed to parse path in HTTP request") RuntimeException failedToParsePath(); @Message(id = 38, value = "Authentication failed, requested user name '%s'") String authenticationFailed(final String userName); @Message(id = 39, value = "Too many query parameters, cannot have more than %s query parameters") BadRequestException tooManyQueryParameters(int noParams); @Message(id = 40, value = "Too many headers, cannot have more than %s header") String tooManyHeaders(int noParams); @Message(id = 41, value = "Channel is closed") ClosedChannelException channelIsClosed(); @Message(id = 42, value = "Could not decode trailers in HTTP request") IOException couldNotDecodeTrailers(); @Message(id = 43, value = "Data is already being sent. You must wait for the completion callback to be be invoked before calling send() again") IllegalStateException dataAlreadyQueued(); @Message(id = 44, value = "More than one predicate with name %s. Builder class %s and %s") IllegalStateException moreThanOnePredicateWithName(String name, Class aClass, Class existing); @Message(id = 45, value = "Error parsing predicated handler string %s:%n%s") IllegalArgumentException errorParsingPredicateString(String reason, String s); @Message(id = 46, value = "The number of cookies sent exceeded the maximum of %s") IllegalStateException tooManyCookies(int maxCookies); @Message(id = 47, value = "The number of parameters exceeded the maximum of %s") ParameterLimitException tooManyParameters(int maxValues); @Message(id = 48, value = "No request is currently active") IllegalStateException noRequestActive(); @Message(id = 50, value = "AuthenticationMechanism Outcome is null") IllegalStateException authMechanismOutcomeNull(); @Message(id = 51, value = "Not a valid IP pattern %s") IllegalArgumentException notAValidIpPattern(String peer); @Message(id = 52, value = "Session data requested when non session based authentication in use") IllegalStateException noSessionData(); @Message(id = 53, value = "Listener %s already registered") IllegalArgumentException listenerAlreadyRegistered(String name); @Message(id = 54, value = "The maximum size %s for an individual file in a multipart request was exceeded") MultiPartParserDefinition.FileTooLargeException maxFileSizeExceeded(long maxIndividualFileSize); @Message(id = 55, value = "Could not set attribute %s to %s as it is read only") String couldNotSetAttribute(String attributeName, String newValue); @Message(id = 56, value = "Could not parse URI template %s, exception at char %s") RuntimeException couldNotParseUriTemplate(String path, int i); @Message(id = 57, value = "Mismatched braces in attribute string %s") RuntimeException mismatchedBraces(String valueString); @Message(id = 58, value = "More than one handler with name %s. Builder class %s and %s") IllegalStateException moreThanOneHandlerWithName(String name, Class aClass, Class existing); // // @Message(id = 59, value = "Invalid syntax %s") // IllegalArgumentException invalidSyntax(String line); // // @Message(id = 60, value = "Error parsing handler string %s:%n%s") // IllegalArgumentException errorParsingHandlerString(String reason, String s); @Message(id = 61, value = "Out of band responses only allowed for 100-continue requests") IllegalArgumentException outOfBandResponseOnlyAllowedFor100Continue(); // // @Message(id = 62, value = "AJP does not support HTTP upgrade") // IllegalStateException ajpDoesNotSupportHTTPUpgrade(); // // @Message(id = 63, value = "File system watcher already started") // IllegalStateException fileSystemWatcherAlreadyStarted(); // // @Message(id = 64, value = "File system watcher not started") // IllegalStateException fileSystemWatcherNotStarted(); @Message(id = 65, value = "SSL must be specified to connect to a https URL") IOException sslWasNull(); @Message(id = 66, value = "Incorrect magic number %s for AJP packet header") IOException wrongMagicNumber(int number); @Message(id = 67, value = "No client cert was provided") SSLPeerUnverifiedException peerUnverified(); @Message(id = 68, value = "Servlet path match failed") IllegalArgumentException servletPathMatchFailed(); @Message(id = 69, value = "Could not parse set cookie header %s") IllegalArgumentException couldNotParseCookie(String headerValue); @Message(id = 70, value = "method can only be called by IO thread") IllegalStateException canOnlyBeCalledByIoThread(); @Message(id = 71, value = "Cannot add path template %s, matcher already contains an equivalent pattern %s") IllegalStateException matcherAlreadyContainsTemplate(String templateString, String templateString1); @Message(id = 72, value = "Failed to decode url %s to charset %s") UrlDecodeException failedToDecodeURL(String s, String enc, @Cause Exception e); @Message(id = 73, value = "Resource change listeners are not supported") IllegalArgumentException resourceChangeListenerNotSupported(); // // @Message(id = 74, value = "Could not renegotiate SSL connection to require client certificate, as client had sent more data") // IllegalStateException couldNotRenegotiate(); @Message(id = 75, value = "Object was freed") IllegalStateException objectWasFreed(); @Message(id = 76, value = "Handler not shutdown") IllegalStateException handlerNotShutdown(); @Message(id = 77, value = "The underlying transport does not support HTTP upgrade") IllegalStateException upgradeNotSupported(); @Message(id = 78, value = "Renegotiation not supported") IOException renegotiationNotSupported(); // // @Message(id = 79, value = "Not a valid user agent pattern %s") // IllegalArgumentException notAValidUserAgentPattern(String userAgent); @Message(id = 80, value = "Not a valid regular expression pattern %s") IllegalArgumentException notAValidRegularExpressionPattern(String pattern); @Message(id = 81, value = "Bad request") BadRequestException badRequest(); @Message(id = 82, value = "Host %s already registered") RuntimeException hostAlreadyRegistered(Object host); @Message(id = 83, value = "Host %s has not been registered") RuntimeException hostHasNotBeenRegistered(Object host); @Message(id = 84, value = "Attempted to write additional data after the last chunk") IOException extraDataWrittenAfterChunkEnd(); @Message(id = 85, value = "Could not generate unique session id") RuntimeException couldNotGenerateUniqueSessionId(); // // @Message(id = 86, value = "SPDY needs to be provided with a heap buffer pool, for use in compressing and decompressing headers.") // IllegalArgumentException mustProvideHeapBuffer(); // // @Message(id = 87, value = "Unexpected SPDY frame type %s") // IOException unexpectedFrameType(int type); @Message(id = 88, value = "SPDY control frames cannot have body content") IOException controlFrameCannotHaveBodyContent(); // @Message(id = 89, value = "SPDY not supported") //// IOException spdyNotSupported(); // // @Message(id = 90, value = "No ALPN implementation available (tried Jetty ALPN and JDK9)") // IOException alpnNotAvailable(); @Message(id = 91, value = "Buffer has already been freed") IllegalStateException bufferAlreadyFreed(); // // @Message(id = 92, value = "A SPDY header was too large to fit in a response buffer, if you want to support larger headers please increase the buffer size") // IllegalStateException headersTooLargeToFitInHeapBuffer(); // @Message(id = 93, value = "A SPDY stream was reset by the remote endpoint") // IOException spdyStreamWasReset(); @Message(id = 94, value = "Blocking await method called from IO thread. Blocking IO must be dispatched to a worker thread or deadlocks will result.") IOException awaitCalledFromIoThread(); @Message(id = 95, value = "Recursive call to flushSenders()") RuntimeException recursiveCallToFlushingSenders(); @Message(id = 96, value = "More data was written to the channel than specified in the content-length") IllegalStateException fixedLengthOverflow(); @Message(id = 97, value = "AJP request already in progress") IllegalStateException ajpRequestAlreadyInProgress(); @Message(id = 98, value = "HTTP ping data must be 8 bytes in length") String httpPingDataMustBeLength8(); @Message(id = 99, value = "Received a ping of size other than 8") String invalidPingSize(); @Message(id = 100, value = "stream id must be zero for frame type %s") String streamIdMustBeZeroForFrameType(int frameType); @Message(id = 101, value = "stream id must not be zero for frame type %s") String streamIdMustNotBeZeroForFrameType(int frameType); // // @Message(id = 102, value = "RST_STREAM received for idle stream") // String rstStreamReceivedForIdleStream(); @Message(id = 103, value = "Http2 stream was reset") IOException http2StreamWasReset(); @Message(id = 104, value = "Incorrect HTTP2 preface") IOException incorrectHttp2Preface(); @Message(id = 105, value = "HTTP2 frame to large") IOException http2FrameTooLarge(); @Message(id = 106, value = "HTTP2 continuation frame received without a corresponding headers or push promise frame") IOException http2ContinuationFrameNotExpected(); @Message(id = 107, value = "Huffman encoded value in HPACK headers did not end with EOS padding") HpackException huffmanEncodedHpackValueDidNotEndWithEOS(); @Message(id = 108, value = "HPACK variable length integer encoded over too many octects, max is %s") HpackException integerEncodedOverTooManyOctets(int maxIntegerOctets); @Message(id = 109, value = "Zero is not a valid header table index") HpackException zeroNotValidHeaderTableIndex(); @Message(id = 110, value = "Cannot send 100-Continue, getResponseChannel() has already been called") IOException cannotSendContinueResponse(); @Message(id = 111, value = "Parser did not make progress") IOException parserDidNotMakeProgress(); @Message(id = 112, value = "Only client side can call createStream, if you wish to send a PUSH_PROMISE frame use createPushPromiseStream instead") IOException headersStreamCanOnlyBeCreatedByClient(); @Message(id = 113, value = "Only the server side can send a push promise stream") IOException pushPromiseCanOnlyBeCreatedByServer(); @Message(id = 114, value = "Invalid IP access control rule %s. Format is: [ip-match] allow|deny") IllegalArgumentException invalidAclRule(String rule); @Message(id = 115, value = "Server received PUSH_PROMISE frame from client") IOException serverReceivedPushPromise(); @Message(id = 116, value = "CONNECT not supported by this connector") IllegalStateException connectNotSupported(); @Message(id = 117, value = "Request was not a CONNECT request") IllegalStateException notAConnectRequest(); @Message(id = 118, value = "Cannot reset buffer, response has already been commited") IllegalStateException cannotResetBuffer(); @Message(id = 119, value = "HTTP2 via prior knowledge failed") IOException http2PriRequestFailed(); @Message(id = 120, value = "Out of band responses are not allowed for this connector") IllegalStateException outOfBandResponseNotSupported(); @Message(id = 121, value = "Session was rejected as the maximum number of sessions (%s) has been hit") IllegalStateException tooManySessions(int maxSessions); @Message(id = 122, value = "CONNECT attempt failed as target proxy returned %s") IOException proxyConnectionFailed(int responseCode); @Message(id = 123, value = "MCMP message %s rejected due to suspicious characters") RuntimeException mcmpMessageRejectedDueToSuspiciousCharacters(String data); @Message(id = 124, value = "renegotiation timed out") IllegalStateException rengotiationTimedOut(); @Message(id = 125, value = "Request body already read") IllegalStateException requestBodyAlreadyRead(); @Message(id = 126, value = "Attempted to do blocking IO from the IO thread. This is prohibited as it may result in deadlocks") IllegalStateException blockingIoFromIOThread(); @Message(id = 127, value = "Response has already been sent") IllegalStateException responseComplete(); @Message(id = 128, value = "Remote peer closed connection before all data could be read") IOException couldNotReadContentLengthData(); @Message(id = 129, value = "Failed to send after being safe to send") IllegalStateException failedToSendAfterBeingSafe(); @Message(id = 130, value = "HTTP reason phrase was too large for the buffer. Either provide a smaller message or a bigger buffer. Phrase: %s") IllegalStateException reasonPhraseToLargeForBuffer(String phrase); @Message(id = 131, value = "Buffer pool is closed") IllegalStateException poolIsClosed(); @Message(id = 132, value = "HPACK decode failed") HpackException hpackFailed(); @Message(id = 133, value = "Request did not contain an Upgrade header, upgrade is not permitted") IllegalStateException notAnUpgradeRequest(); @Message(id = 134, value = "Authentication mechanism %s requires property %s to be set") IllegalStateException authenticationPropertyNotSet(String name, String header); @Message(id = 135, value = "renegotiation failed") IllegalStateException rengotiationFailed(); @Message(id = 136, value = "User agent charset string must have an even number of items, in the form pattern,charset,pattern,charset,... Instead got: %s") IllegalArgumentException userAgentCharsetMustHaveEvenNumberOfItems(String supplied); @Message(id = 137, value = "Could not find the datasource called %s") IllegalArgumentException datasourceNotFound(String ds); @Message(id = 138, value = "Server not started") IllegalStateException serverNotStarted(); @Message(id = 139, value = "Exchange already complete") IllegalStateException exchangeAlreadyComplete(); @Message(id = 140, value = "Initial SSL/TLS data is not a handshake record") SSLHandshakeException notHandshakeRecord(); @Message(id = 141, value = "Initial SSL/TLS handshake record is invalid") SSLHandshakeException invalidHandshakeRecord(); @Message(id = 142, value = "Initial SSL/TLS handshake spans multiple records") SSLHandshakeException multiRecordSSLHandshake(); @Message(id = 143, value = "Expected \"client hello\" record") SSLHandshakeException expectedClientHello(); @Message(id = 144, value = "Expected server hello") SSLHandshakeException expectedServerHello(); @Message(id = 145, value = "Too many redirects") IOException tooManyRedirects(@Cause IOException exception); @Message(id = 146, value = "HttpServerExchange cannot have both async IO resumed and dispatch() called in the same cycle") IllegalStateException resumedAndDispatched(); @Message(id = 147, value = "No host header in a HTTP/1.1 request") IOException noHostInHttp11Request(); @Message(id = 148, value = "Invalid HPack encoding. First byte: %s") HpackException invalidHpackEncoding(byte b); @Message(id = 149, value = "HttpString is not allowed to contain newlines. value: %s") IllegalArgumentException newlineNotSupportedInHttpString(String value); @Message(id = 150, value = "Pseudo header %s received after receiving normal headers. Pseudo headers must be the first headers in a HTTP/2 header block.") String pseudoHeaderInWrongOrder(HttpString header); @Message(id = 151, value = "Expected to receive a continuation frame") String expectedContinuationFrame(); @Message(id = 152, value = "Incorrect frame size") String incorrectFrameSize(); @Message(id = 153, value = "Stream id not registered") IllegalStateException streamNotRegistered(); @Message(id = 154, value = "Mechanism %s returned a null result from sendChallenge()") NullPointerException sendChallengeReturnedNull(AuthenticationMechanism mechanism); @Message(id = 155, value = "Framed channel body was set when it was not ready for flush") IllegalStateException bodyIsSetAndNotReadyForFlush(); @Message(id = 156, value = "Invalid GZIP header") IOException invalidGzipHeader(); @Message(id = 157, value = "Invalid GZIP footer") IOException invalidGZIPFooter(); @Message(id = 158, value = "Response of length %s is too large to buffer") IllegalStateException responseTooLargeToBuffer(Long length); // // @Message(id = 159, value = "Max size must be larger than one") // IllegalArgumentException maxSizeMustBeLargerThanOne(); @Message(id = 161, value = "HTTP/2 header block is too large") String headerBlockTooLarge(); @Message(id = 162, value = "An invalid SameSite attribute [%s] is specified. It must be one of %s") IllegalArgumentException invalidSameSiteMode(String mode, String validAttributes); @Message(id = 163, value = "Invalid token %s") IllegalArgumentException invalidToken(byte c); @Message(id = 164, value = "Request contained invalid headers") IllegalArgumentException invalidHeaders(); @Message(id = 165, value = "Invalid character %s in request-target") String invalidCharacterInRequestTarget(char next); @Message(id = 166, value = "Pooled object is closed") IllegalStateException objectIsClosed(); @Message(id = 167, value = "More than one host header in request") IOException moreThanOneHostHeader(); @Message(id = 168, value = "An invalid character [ASCII code: %s] was present in the cookie value") IllegalArgumentException invalidCookieValue(String value); @Message(id = 169, value = "An invalid domain [%s] was specified for this cookie") IllegalArgumentException invalidCookieDomain(String value); @Message(id = 170, value = "An invalid path [%s] was specified for this cookie") IllegalArgumentException invalidCookiePath(String value); @Message(id = 173, value = "An invalid control character [%s] was present in the cookie value or attribute") IllegalArgumentException invalidControlCharacter(String value); @Message(id = 174, value = "An invalid escape character in cookie value") IllegalArgumentException invalidEscapeCharacter(); @Message(id = 175, value = "Invalid Hpack index %s") HpackException invalidHpackIndex(int index); @Message(id = 178, value = "Buffer pool is too small, min size is %s") IllegalArgumentException bufferPoolTooSmall(int minSize); @Message(id = 179, value = "Invalid PROXY protocol header") IOException invalidProxyHeader(); @Message(id = 180, value = "PROXY protocol header exceeded max size of 107 bytes") IOException headerSizeToLarge(); @Message(id = 181, value = "HTTP/2 trailers too large for single buffer") RuntimeException http2TrailerToLargeForSingleBuffer(); @Message(id = 182, value = "Ping not supported") IOException pingNotSupported(); @Message(id = 183, value = "Ping timed out") IOException pingTimeout(); @Message(id = 184, value = "Stream limit exceeded") IOException streamLimitExceeded(); @Message(id = 185, value = "Invalid IP address %s") IOException invalidIpAddress(String addressString); @Message(id = 186, value = "Invalid TLS extension") SSLException invalidTlsExt(); @Message(id = 187, value = "Not enough data") SSLException notEnoughData(); @Message(id = 188, value = "Empty host name in SNI extension") SSLException emptyHostNameSni(); @Message(id = 189, value = "Duplicated host name of type %s") SSLException duplicatedSniServerName(int type); @Message(id = 190, value = "No context for SSL connection") SSLException noContextForSslConnection(); @Message(id = 191, value = "Default context cannot be null") IllegalStateException defaultContextCannotBeNull(); @Message(id = 192, value = "Form value is a in-memory file, use getFileItem() instead") IllegalStateException formValueIsInMemoryFile(); @Message(id = 193, value = "Character decoding failed. Parameter [%s] with value [%s] has been ignored. Note: further occurrences of Parameter errors will be logged at DEBUG level.") String failedToDecodeParameterValue(String parameter, String value, @Cause Exception e); @Message(id = 194, value = "Character decoding failed. Parameter with name [%s] has been ignored. Note: further occurrences of Parameter errors will be logged at DEBUG level.") String failedToDecodeParameterName(String parameter, @Cause Exception e); @Message(id = 195, value = "Chunk size too large") IOException chunkSizeTooLarge(); @Message(id = 196, value = "Session with id %s already exists") IllegalStateException sessionWithIdAlreadyExists(String sessionID); @Message(id = 197, value = "Blocking read timed out after %s nanoseconds.") ReadTimeoutException blockingReadTimedOut(long timeoutNanoseconds); @Message(id = 198, value = "Blocking write timed out after %s nanoseconds.") WriteTimeoutException blockingWriteTimedOut(long timeoutNanoseconds); @Message(id = 199, value = "Read timed out after %s milliseconds.") ReadTimeoutException readTimedOut(long timeoutMilliseconds); @Message(id = 200, value = "Failed to replace hash output stream ") SSLException failedToReplaceHashOutputStream(@Cause Exception e); @Message(id = 201, value = "Failed to replace hash output stream ") RuntimeException failedToReplaceHashOutputStreamOnWrite(@Cause Exception e); @Message(id = 202, value = "Failed to initialize path manager for '%s' path.") RuntimeException failedToInitializePathManager(String path, @Cause IOException ioe); @Message(id = 203, value = "Invalid ACL entry") IllegalArgumentException invalidACLAddress(@Cause Exception e); @Message(id = 204, value = "Out of flow control window: no WINDOW_UPDATE received from peer within %s miliseconds") IOException noWindowUpdate(long timeoutMiliseconds); @Message(id = 205, value = "Path is not a directory '%s'") IOException pathNotADirectory(Path path); @Message(id = 206, value = "Path '%s' is not a directory") IOException pathElementIsRegularFile(Path path); } undertow-2.2.16.Final/core/src/main/java/io/undertow/UndertowOptions.java000066400000000000000000000402771420065311100263740ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow; import org.xnio.Option; /** * @author Stuart Douglas */ public class UndertowOptions { /** * The maximum size in bytes of a http request header. */ public static final Option MAX_HEADER_SIZE = Option.simple(UndertowOptions.class, "MAX_HEADER_SIZE", Integer.class); /** * The default size we allow for the HTTP header. */ public static final int DEFAULT_MAX_HEADER_SIZE = 1024 * 1024; /** * The default maximum size of the HTTP entity body. */ public static final Option MAX_ENTITY_SIZE = Option.simple(UndertowOptions.class, "MAX_ENTITY_SIZE", Long.class); /** * The default maximum size of the HTTP entity body when using the mutiltipart parser. Generall this will be larger than {@link #MAX_ENTITY_SIZE}. * * If this is not specified it will be the same as {@link #MAX_ENTITY_SIZE}. */ public static final Option MULTIPART_MAX_ENTITY_SIZE = Option.simple(UndertowOptions.class, "MULTIPART_MAX_ENTITY_SIZE", Long.class); /** * We do not have a default upload limit */ public static final long DEFAULT_MAX_ENTITY_SIZE = -1; /** * If we should buffer pipelined requests. Defaults to false. */ public static final Option BUFFER_PIPELINED_DATA = Option.simple(UndertowOptions.class, "BUFFER_PIPELINED_DATA", Boolean.class); /** * The idle timeout in milliseconds after which the channel will be closed. * * If the underlying channel already has a read or write timeout set the smaller of the two values will be used * for read/write timeouts. * */ public static final Option IDLE_TIMEOUT = Option.simple(UndertowOptions.class, "IDLE_TIMEOUT", Integer.class); /** * The maximum allowed time of reading HTTP request in milliseconds. * * -1 or missing value disables this functionality. */ public static final Option REQUEST_PARSE_TIMEOUT = Option.simple(UndertowOptions.class, "REQUEST_PARSE_TIMEOUT", Integer.class); /** * The amount of time the connection can be idle with no current requests before it is closed; */ public static final Option NO_REQUEST_TIMEOUT = Option.simple(UndertowOptions.class, "NO_REQUEST_TIMEOUT", Integer.class); public static final int DEFAULT_MAX_PARAMETERS = 1000; /** * The maximum number of parameters that will be parsed. This is used to protect against hash vulnerabilities. *

* This applies to both query parameters, and to POST data, but is not cumulative (i.e. you can potentially have * max parameters * 2 total parameters). *

* Defaults to 1000 */ public static final Option MAX_PARAMETERS = Option.simple(UndertowOptions.class, "MAX_PARAMETERS", Integer.class); public static final int DEFAULT_MAX_HEADERS = 200; /** * The maximum number of headers that will be parsed. This is used to protect against hash vulnerabilities. *

* Defaults to 200 */ public static final Option MAX_HEADERS = Option.simple(UndertowOptions.class, "MAX_HEADERS", Integer.class); /** * The maximum number of cookies that will be parsed. This is used to protect against hash vulnerabilities. *

* Defaults to 200 */ public static final Option MAX_COOKIES = Option.simple(UndertowOptions.class, "MAX_COOKIES", Integer.class); /** * If a request comes in with encoded / characters (i.e. %2F), will these be decoded. *

* This can cause security problems if a front end proxy does not perform the same decoding, and as a result * this is disabled by default. *

* Defaults to false * * See CVE-2007-0450 */ public static final Option ALLOW_ENCODED_SLASH = Option.simple(UndertowOptions.class, "ALLOW_ENCODED_SLASH", Boolean.class); /** * If this is true then the parser will decode the URL and query parameters using the selected character encoding (UTF-8 by default). If this is false they will * not be decoded. This will allow a later handler to decode them into whatever charset is desired. *

* Defaults to true. */ public static final Option DECODE_URL = Option.simple(UndertowOptions.class, "DECODE_URL", Boolean.class); /** * If this is true then the parser will decode the URL and query parameters using the selected character encoding (UTF-8 by default). If this is false they will * not be decoded. This will allow a later handler to decode them into whatever charset is desired. *

* Defaults to true. */ public static final Option URL_CHARSET = Option.simple(UndertowOptions.class, "URL_CHARSET", String.class); /** * If this is true then a Connection: keep-alive header will be added to responses, even when it is not strictly required by * the specification. *

* Defaults to true */ public static final Option ALWAYS_SET_KEEP_ALIVE = Option.simple(UndertowOptions.class, "ALWAYS_SET_KEEP_ALIVE", Boolean.class); /** * If this is true then a Date header will be added to all responses. The HTTP spec says this header should be added to all * responses, unless the server does not have an accurate clock. *

* Defaults to true */ public static final Option ALWAYS_SET_DATE = Option.simple(UndertowOptions.class, "ALWAYS_SET_DATE", Boolean.class); /** * Maximum size of a buffered request, in bytes *

* Requests are not usually buffered, the most common case is when performing SSL renegotiation for a POST request, and the post data must be fully * buffered in order to perform the renegotiation. *

* Defaults to 16384. */ public static final Option MAX_BUFFERED_REQUEST_SIZE = Option.simple(UndertowOptions.class, "MAX_BUFFERED_REQUEST_SIZE", Integer.class); public static final int DEFAULT_MAX_BUFFERED_REQUEST_SIZE = 16384; /** * If this is true then Undertow will record the request start time, to allow for request time to be logged * * This has a small but measurable performance impact * * default is false */ public static final Option RECORD_REQUEST_START_TIME = Option.simple(UndertowOptions.class, "RECORD_REQUEST_START_TIME", Boolean.class); /** * If this is true then Undertow will allow non-escaped equals characters in unquoted cookie values. *

* Unquoted cookie values may not contain equals characters. If present the value ends before the equals sign. The remainder of the cookie value will be dropped. *

* default is false */ public static final Option ALLOW_EQUALS_IN_COOKIE_VALUE = Option.simple(UndertowOptions.class, "ALLOW_EQUALS_IN_COOKIE_VALUE", Boolean.class); /** * If this is true then Undertow will enable RFC6265 compliant cookie validation for Set-Cookie header instead of legacy backward compatible behavior. * * default is false */ public static final Option ENABLE_RFC6265_COOKIE_VALIDATION = Option.simple(UndertowOptions.class, "ENABLE_RFC6265_COOKIE_VALIDATION", Boolean.class); public static final boolean DEFAULT_ENABLE_RFC6265_COOKIE_VALIDATION = false; /** * If we should attempt to use SPDY for HTTPS connections. * * SPDY is no longer supported, use HTTP/2 instead */ @Deprecated public static final Option ENABLE_SPDY = Option.simple(UndertowOptions.class, "ENABLE_SPDY", Boolean.class); /** * If we should attempt to use HTTP2 for HTTPS connections. */ public static final Option ENABLE_HTTP2 = Option.simple(UndertowOptions.class, "ENABLE_HTTP2", Boolean.class); /** * If connector level statistics should be enabled. This has a slight performance impact, but allows statistics such * as bytes sent/recevied to be monitored. * * If this is passed to the client then client statistics will be enabled. * */ public static final Option ENABLE_STATISTICS = Option.simple(UndertowOptions.class, "ENABLE_STATISTICS", Boolean.class); /** * If connector level statistics should be enabled. This has a slight performance impact, but allows statistics such * as bytes sent/recevied to be monitored. */ @Deprecated public static final Option ENABLE_CONNECTOR_STATISTICS = ENABLE_STATISTICS; /** * If unknown protocols should be allowed. The known protocols are: * * HTTP/0.9 * HTTP/1.0 * HTTP/1.1 * HTTP/2.0 * * If this is false then requests that specify any other protocol will be rejected with a 400 * * Defaults to false */ public static final Option ALLOW_UNKNOWN_PROTOCOLS = Option.simple(UndertowOptions.class, "ALLOW_UNKNOWN_PROTOCOLS", Boolean.class); /** * The size of the header table that is used in the encoder */ public static final Option HTTP2_SETTINGS_HEADER_TABLE_SIZE = Option.simple(UndertowOptions.class, "HTTP2_SETTINGS_HEADER_TABLE_SIZE", Integer.class); public static final int HTTP2_SETTINGS_HEADER_TABLE_SIZE_DEFAULT = 4096; /** * If push should be enabled for this connection. */ public static final Option HTTP2_SETTINGS_ENABLE_PUSH = Option.simple(UndertowOptions.class, "HTTP2_SETTINGS_ENABLE_PUSH", Boolean.class); /** * The maximum number of concurrent */ public static final Option HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS = Option.simple(UndertowOptions.class, "HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS", Integer.class); public static final Option HTTP2_SETTINGS_INITIAL_WINDOW_SIZE = Option.simple(UndertowOptions.class, "HTTP2_SETTINGS_INITIAL_WINDOW_SIZE", Integer.class); public static final Option HTTP2_SETTINGS_MAX_FRAME_SIZE = Option.simple(UndertowOptions.class, "HTTP2_SETTINGS_MAX_FRAME_SIZE", Integer.class); /** * Deprecated, as it is effectively a duplicate of MAX_HEADER_SIZE * * @see #MAX_HEADER_SIZE */ @Deprecated public static final Option HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE = Option.simple(UndertowOptions.class, "HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE", Integer.class); /** * The maximum amount of padding to send in a HTTP/2 frame. Actual amount will be randomly determined, defaults to Zero. */ public static final Option HTTP2_PADDING_SIZE = Option.simple(UndertowOptions.class, "HTTP2_PADDING_SIZE", Integer.class); /** * Undertow keeps a LRU cache of common huffman encodings. This sets the maximum size, setting this to 0 will disable the caching. * */ public static final Option HTTP2_HUFFMAN_CACHE_SIZE = Option.simple(UndertowOptions.class, "HTTP2_HUFFMAN_CACHE_SIZE", Integer.class); /** * The maximum number of concurrent requests that will be processed at a time. This differs from max concurrent streams in that it is not sent to the remote client. * * If the number of pending requests exceeds this number then requests will be queued, the difference between this and max concurrent streams determines * the maximum number of requests that will be queued. * * Queued requests are processed by a priority queue, rather than a FIFO based queue, using HTTP2 stream priority. * * If this number is smaller than or equal to zero then max concurrent streams determines the maximum number of streams that can be run. * * */ public static final Option MAX_CONCURRENT_REQUESTS_PER_CONNECTION = Option.simple(UndertowOptions.class, "MAX_CONCURRENT_REQUESTS_PER_CONNECTION", Integer.class); /** * The maximum number of buffers that will be used before reads are paused in framed protocols. Defaults to 10 */ public static final Option MAX_QUEUED_READ_BUFFERS = Option.simple(UndertowOptions.class, "MAX_QUEUED_READ_BUFFERS", Integer.class); /** * The maximum AJP packet size, default is 8192 */ public static final Option MAX_AJP_PACKET_SIZE = Option.simple(UndertowOptions.class, "MAX_AJP_PACKET_SIZE", Integer.class); /** * If this is true then HTTP/1.1 requests will be failed if no host header is present. */ public static final Option REQUIRE_HOST_HTTP11 = Option.simple(UndertowOptions.class, "REQUIRE_HOST_HTTP11", Boolean.class); public static final int DEFAULT_MAX_CACHED_HEADER_SIZE = 150; /** * The maximum size of a header name+value combo that is cached in the per connection cache. Defaults to 150 */ public static final Option MAX_CACHED_HEADER_SIZE = Option.simple(UndertowOptions.class, "MAX_CACHED_HEADER_SIZE", Integer.class); public static final int DEFAULT_HTTP_HEADERS_CACHE_SIZE = 15; /** * The maximum number of headers that are cached per connection. Defaults to 15. If this is set to zero the cache is disabled. */ public static final Option HTTP_HEADERS_CACHE_SIZE = Option.simple(UndertowOptions.class, "HTTP_HEADERS_CACHE_SIZE", Integer.class); /** * If the SSLEngine should prefer the servers cipher version. Only applicable on JDK8+. */ public static final Option SSL_USER_CIPHER_SUITES_ORDER = Option.simple(UndertowOptions.class, "SSL_USER_CIPHER_SUITES_ORDER", Boolean.class); public static final Option ALLOW_UNESCAPED_CHARACTERS_IN_URL = Option.simple(UndertowOptions.class,"ALLOW_UNESCAPED_CHARACTERS_IN_URL", Boolean.class); /** * The server shutdown timeout in milliseconds after which the executor will be forcefully shut down interrupting * tasks which are still executing. * * There is no timeout by default. */ public static final Option SHUTDOWN_TIMEOUT = Option.simple(UndertowOptions.class, "SHUTDOWN_TIMEOUT", Integer.class); /** * The endpoint identification algorithm. * * @see javax.net.ssl.SSLParameters#setEndpointIdentificationAlgorithm(String) */ public static final Option ENDPOINT_IDENTIFICATION_ALGORITHM = Option.simple(UndertowOptions.class, "ENDPOINT_IDENTIFICATION_ALGORITHM", String.class); /** * The maximum numbers of frames that can be queued before reads are suspended. Once this number is hit then reads will not be resumed until {@link #QUEUED_FRAMES_LOW_WATER_MARK} * is hit. * * Defaults to 50 */ public static final Option QUEUED_FRAMES_HIGH_WATER_MARK = Option.simple(UndertowOptions.class, "QUEUED_FRAMES_HIGH_WATER_MARK", Integer.class); /** * The point at which reads will resume again after hitting the high water mark * * Defaults to 10 */ public static final Option QUEUED_FRAMES_LOW_WATER_MARK = Option.simple(UndertowOptions.class, "QUEUED_FRAMES_LOW_WATER_MARK", Integer.class); /** * The AJP protocol itself supports the passing of arbitrary request attributes. * The reverse proxy passes various information to the AJP connector using request attributes through AJP protocol. * Unrecognised request attributes will be ignored unless the entire attribute name matches this regular expression. * * If not specified, the default value is null. */ public static final Option AJP_ALLOWED_REQUEST_ATTRIBUTES_PATTERN = Option.simple(UndertowOptions.class, "AJP_ALLOWED_REQUEST_ATTRIBUTES_PATTERN", String.class); private UndertowOptions() { } } undertow-2.2.16.Final/core/src/main/java/io/undertow/Version.java000066400000000000000000000033031420065311100246230ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow; import java.io.InputStream; import java.util.Properties; /** * @author Tomaz Cerar (c) 2013 Red Hat Inc. */ public class Version { private static final String versionString; private static final String SERVER_NAME = "Undertow"; private static final String fullVersionString; static { String version = "Unknown"; try (InputStream versionPropsStream = Version.class.getResourceAsStream("version.properties")){ Properties props = new Properties(); props.load(versionPropsStream); version = props.getProperty("undertow.version"); } catch (Exception e) { e.printStackTrace(); } versionString = version; fullVersionString = SERVER_NAME + " - "+ versionString; } public static String getVersionString() { return versionString; } public static String getFullVersionString() { return fullVersionString; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/000077500000000000000000000000001420065311100243375ustar00rootroot00000000000000AuthenticationTypeExchangeAttribute.java000066400000000000000000000041061420065311100342740ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.security.api.SecurityContext; import io.undertow.server.HttpServerExchange; /** * @author Stuart Douglas */ public class AuthenticationTypeExchangeAttribute implements ExchangeAttribute { public static final String TOKEN = "%{AUTHENTICATION_TYPE}"; public static final ExchangeAttribute INSTANCE = new AuthenticationTypeExchangeAttribute(); @Override public String readAttribute(HttpServerExchange exchange) { SecurityContext sc = exchange.getSecurityContext(); if(sc == null) { return null; } return sc.getMechanismName(); } @Override public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Authentication Type", newValue); } @Override public String toString() { return TOKEN; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Authentication Type"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(TOKEN)) { return INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/BytesSentAttribute.java000066400000000000000000000047271420065311100310200ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; /** * The bytes sent * * @author Filipe Ferraz */ public class BytesSentAttribute implements ExchangeAttribute { public static final String BYTES_SENT_SHORT_UPPER = "%B"; public static final String BYTES_SENT_SHORT_LOWER = "%b"; public static final String BYTES_SENT = "%{BYTES_SENT}"; private final boolean dashIfZero; public BytesSentAttribute(boolean dashIfZero) { this.dashIfZero = dashIfZero; } @Override public String readAttribute(final HttpServerExchange exchange) { if (dashIfZero ) { long bytesSent = exchange.getResponseBytesSent(); return bytesSent == 0 ? "-" : Long.toString(bytesSent); } else { return Long.toString(exchange.getResponseBytesSent()); } } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Bytes sent", newValue); } @Override public String toString() { return BYTES_SENT; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Bytes Sent"; } @Override public ExchangeAttribute build(final String token) { if(token.equals(BYTES_SENT_SHORT_LOWER)) { return new BytesSentAttribute(true); } if (token.equals(BYTES_SENT) || token.equals(BYTES_SENT_SHORT_UPPER)) { return new BytesSentAttribute(false); } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/CompositeExchangeAttribute.java000066400000000000000000000041451420065311100324770ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; /** * Exchange attribute that represents a combination of attributes that should be merged into a single string. * * @author Stuart Douglas */ public class CompositeExchangeAttribute implements ExchangeAttribute { private final ExchangeAttribute[] attributes; public CompositeExchangeAttribute(ExchangeAttribute[] attributes) { ExchangeAttribute[] copy = new ExchangeAttribute[attributes.length]; System.arraycopy(attributes, 0, copy, 0, attributes.length); this.attributes = copy; } @Override public String readAttribute(HttpServerExchange exchange) { final StringBuilder sb = new StringBuilder(); for (int i = 0; i < attributes.length; ++i) { final String val = attributes[i].readAttribute(exchange); if(val != null) { sb.append(val); } } return sb.toString(); } @Override public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("combined", newValue); } @Override public String toString() { final StringBuilder sb = new StringBuilder(); for (int i = 0; i < attributes.length; ++i) { sb.append(attributes[i].toString()); } return sb.toString(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/ConstantExchangeAttribute.java000066400000000000000000000027101420065311100323220ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; /** * Exchange attribute that represents a fixed value * * @author Stuart Douglas */ public class ConstantExchangeAttribute implements ExchangeAttribute { private final String value; public ConstantExchangeAttribute(final String value) { this.value = value; } @Override public String readAttribute(final HttpServerExchange exchange) { return value; } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("constant", newValue); } @Override public String toString() { return value; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/CookieAttribute.java000066400000000000000000000045251420065311100303050ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.Cookie; import io.undertow.server.handlers.CookieImpl; /** * A cookie * * @author Stuart Douglas * @author Richard Opalka */ public class CookieAttribute implements ExchangeAttribute { private final String cookieName; public CookieAttribute(final String cookieName) { this.cookieName = cookieName; } @Override public String readAttribute(final HttpServerExchange exchange) { for (Cookie cookie : exchange.requestCookies()) { if (cookieName.equals(cookie.getName())) { return cookie.getValue(); } } return null; } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { exchange.setResponseCookie(new CookieImpl(cookieName, newValue)); } @Override public String toString() { return "%{c," + cookieName + "}"; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Cookie"; } @Override public ExchangeAttribute build(final String token) { if (token.startsWith("%{c,") && token.endsWith("}")) { final String cookieName = token.substring(4, token.length() - 1); return new CookieAttribute(cookieName); } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/DateTimeAttribute.java000066400000000000000000000067151420065311100305730ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; import io.undertow.server.HttpServerExchange; import io.undertow.util.DateUtils; /** * The current time * * @author Stuart Douglas */ public class DateTimeAttribute implements ExchangeAttribute { public static final String DATE_TIME_SHORT = "%t"; public static final String DATE_TIME = "%{DATE_TIME}"; public static final String CUSTOM_TIME = "%{time,"; public static final ExchangeAttribute INSTANCE = new DateTimeAttribute(); private final String dateFormat; private final ThreadLocal cachedFormat; private DateTimeAttribute() { this.dateFormat = null; this.cachedFormat = null; } public DateTimeAttribute(final String dateFormat) { this(dateFormat, null); } public DateTimeAttribute(final String dateFormat, final String timezone) { this.dateFormat = dateFormat; this.cachedFormat = new ThreadLocal() { @Override protected SimpleDateFormat initialValue() { final SimpleDateFormat format = new SimpleDateFormat(dateFormat); if(timezone != null) { format.setTimeZone(TimeZone.getTimeZone(timezone)); } return format; } }; } @Override public String readAttribute(final HttpServerExchange exchange) { if(dateFormat == null) { return DateUtils.toCommonLogFormat(new Date()); } else { final SimpleDateFormat dateFormat = this.cachedFormat.get(); return dateFormat.format(new Date()); } } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Date time", newValue); } @Override public String toString() { if (dateFormat == null) return DATE_TIME; return CUSTOM_TIME + dateFormat + "}"; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Date Time"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(DATE_TIME) || token.equals(DATE_TIME_SHORT)) { return DateTimeAttribute.INSTANCE; } if(token.startsWith(CUSTOM_TIME) && token.endsWith("}")) { return new DateTimeAttribute(token.substring(CUSTOM_TIME.length(), token.length() - 1)); } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/ExchangeAttribute.java000066400000000000000000000030461420065311100306130ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; /** * Representation of a string attribute from a HTTP server exchange. * * * @author Stuart Douglas */ public interface ExchangeAttribute { /** * Resolve the attribute from the HTTP server exchange. This may return null if the attribute is not present. * @param exchange The exchange * @return The attribute */ String readAttribute(final HttpServerExchange exchange); /** * Sets a new value for the attribute. Not all attributes are writable. * @param exchange The exchange * @param newValue The new value for the attribute * @throws ReadOnlyAttributeException when attribute cannot be written */ void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException; } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/ExchangeAttributeBuilder.java000066400000000000000000000032611420065311100321210ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; /** * An interface that knows how to build an exchange attribute from a textual representation. *

* This makes it easy to configure attributes based on a string representation * * @author Stuart Douglas */ public interface ExchangeAttributeBuilder { /** * The string representation of the attribute name. This is used solely for debugging / informational purposes * * @return The attribute name */ String name(); /** * Build the attribute from a text based representation. If the attribute does not understand this representation then * it will just return null. * * @param token The string token * @return The exchange attribute, or null */ ExchangeAttribute build(final String token); /** * The priority of the builder. Builders will be tried in priority builder. Built in builders use the priority range 0-100, * * @return The priority */ int priority(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/ExchangeAttributeParser.java000066400000000000000000000152351420065311100317730ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.ServiceLoader; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; /** * Attribute parser for exchange attributes. This builds an attribute from a string definition. *

* This uses a service loader mechanism to allow additional token types to be loaded. Token definitions are loaded * from the provided class loader. * * @author Stuart Douglas * @see ExchangeAttributes#parser(ClassLoader) */ public class ExchangeAttributeParser { private final List builders; private final List wrappers; ExchangeAttributeParser(final ClassLoader classLoader, List wrappers) { this.wrappers = wrappers; ServiceLoader loader = ServiceLoader.load(ExchangeAttributeBuilder.class, classLoader); final List builders = new ArrayList<>(); for (ExchangeAttributeBuilder instance : loader) { builders.add(instance); } //sort with highest priority first Collections.sort(builders, new Comparator() { @Override public int compare(ExchangeAttributeBuilder o1, ExchangeAttributeBuilder o2) { return Integer.compare(o2.priority(), o1.priority()); } }); this.builders = Collections.unmodifiableList(builders); } /** * Parses the provided value string, and turns it into a list of exchange attributes. *

* Tokens are created according to the following rules: *

* %a - % followed by single character. %% is an escape for a literal % * %{.*}a? - % plus curly braces with any amount of content inside, followed by an optional character * ${.*} - $ followed by a curly braces to reference an item from the predicate context * * @param valueString * @return */ public ExchangeAttribute parse(final String valueString) { final List attributes = new ArrayList<>(); int pos = 0; int state = 0; //0 = literal, 1 = %, 2 = %{, 3 = $, 4 = ${ for (int i = 0; i < valueString.length(); ++i) { char c = valueString.charAt(i); switch (state) { case 0: { if (c == '%' || c == '$') { if (pos != i) { attributes.add(wrap(parseSingleToken(valueString.substring(pos, i)))); pos = i; } if (c == '%') { state = 1; } else { state = 3; } } break; } case 1: { if (c == '{') { state = 2; } else if (c == '%') { //literal percent attributes.add(wrap(new ConstantExchangeAttribute("%"))); pos = i + 1; state = 0; } else { attributes.add(wrap(parseSingleToken(valueString.substring(pos, i + 1)))); pos = i + 1; state = 0; } break; } case 2: { if (c == '}') { attributes.add(wrap(parseSingleToken(valueString.substring(pos, i + 1)))); pos = i + 1; state = 0; } break; } case 3: { if (c == '{') { state = 4; } else if (c == '$') { //literal dollars attributes.add(wrap(new ConstantExchangeAttribute("$"))); pos = i + 1; state = 0; } else { attributes.add(wrap(parseSingleToken(valueString.substring(pos, i + 1)))); pos = i + 1; state = 0; } break; } case 4: { if (c == '}') { attributes.add(wrap(parseSingleToken(valueString.substring(pos, i + 1)))); pos = i + 1; state = 0; } break; } } } switch (state) { case 0: case 1: case 3:{ if(pos != valueString.length()) { attributes.add(wrap(parseSingleToken(valueString.substring(pos)))); } break; } case 2: case 4: { throw UndertowMessages.MESSAGES.mismatchedBraces(valueString); } } if(attributes.size() == 1) { return attributes.get(0); } return new CompositeExchangeAttribute(attributes.toArray(new ExchangeAttribute[attributes.size()])); } public ExchangeAttribute parseSingleToken(final String token) { for (final ExchangeAttributeBuilder builder : builders) { ExchangeAttribute res = builder.build(token); if (res != null) { return res; } } if (token.startsWith("%")) { UndertowLogger.ROOT_LOGGER.unknownVariable(token); } return new ConstantExchangeAttribute(token); } private ExchangeAttribute wrap(ExchangeAttribute attribute) { ExchangeAttribute res = attribute; for(ExchangeAttributeWrapper w : wrappers) { res = w.wrap(res); } return res; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/ExchangeAttributeWrapper.java000066400000000000000000000003531420065311100321520ustar00rootroot00000000000000package io.undertow.attribute; /** * Interface that can be used to wrap an exchange attribute. * * @author Stuart Douglas */ public interface ExchangeAttributeWrapper { ExchangeAttribute wrap(ExchangeAttribute attribute); } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/ExchangeAttributes.java000066400000000000000000000105761420065311100310040ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; import io.undertow.util.HttpString; import java.util.Arrays; import java.util.Collections; /** * Utility class for retrieving exchange attributes * * @author Stuart Douglas */ public class ExchangeAttributes { public static ExchangeAttributeParser parser(final ClassLoader classLoader) { return new ExchangeAttributeParser(classLoader, Collections.emptyList()); } public static ExchangeAttributeParser parser(final ClassLoader classLoader, ExchangeAttributeWrapper ... wrappers) { return new ExchangeAttributeParser(classLoader, Arrays.asList(wrappers)); } public static ExchangeAttribute cookie(final String cookieName) { return new CookieAttribute(cookieName); } public static ExchangeAttribute bytesSent(boolean dashIfZero) { return new BytesSentAttribute(dashIfZero); } public static ExchangeAttribute dateTime() { return DateTimeAttribute.INSTANCE; } public static ExchangeAttribute localIp() { return LocalIPAttribute.INSTANCE; } public static ExchangeAttribute localPort() { return LocalPortAttribute.INSTANCE; } public static ExchangeAttribute localServerName() { return LocalServerNameAttribute.INSTANCE; } public static ExchangeAttribute queryString() { return QueryStringAttribute.INSTANCE; } public static ExchangeAttribute relativePath() { return RelativePathAttribute.INSTANCE; } public static ExchangeAttribute remoteIp() { return RemoteIPAttribute.INSTANCE; } public static ExchangeAttribute remoteObfuscatedIp() { return RemoteObfuscatedIPAttribute.INSTANCE; } public static ExchangeAttribute remoteUser() { return RemoteUserAttribute.INSTANCE; } public static ExchangeAttribute requestHeader(final HttpString header) { return new RequestHeaderAttribute(header); } public static ExchangeAttribute requestList() { return RequestLineAttribute.INSTANCE; } public static ExchangeAttribute requestMethod() { return RequestMethodAttribute.INSTANCE; } public static ExchangeAttribute requestProtocol() { return RequestProtocolAttribute.INSTANCE; } public static ExchangeAttribute requestURL() { return RequestURLAttribute.INSTANCE; } public static ExchangeAttribute responseCode() { return ResponseCodeAttribute.INSTANCE; } public static ExchangeAttribute responseReasonPhrase() { return ResponseReasonPhraseAttribute.INSTANCE; } public static ExchangeAttribute responseHeader(final HttpString header) { return new ResponseHeaderAttribute(header); } public static ExchangeAttribute transportProtocol() { return TransportProtocolAttribute.INSTANCE; } public static ExchangeAttribute threadName() { return ThreadNameAttribute.INSTANCE; } public static ExchangeAttribute constant(String value) { return new ConstantExchangeAttribute(value); } public static String resolve(final HttpServerExchange exchange, final ExchangeAttribute[] attributes) { final StringBuilder result = new StringBuilder(); for (int i = 0; i < attributes.length; ++i) { final String str = attributes[i].readAttribute(exchange); if (str != null) { result.append(str); } } return result.toString(); } private ExchangeAttributes() { } public static ExchangeAttribute authenticationType() { return AuthenticationTypeExchangeAttribute.INSTANCE; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/HostAndPortAttribute.java000066400000000000000000000037661420065311100313070ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; /** * The request scheme * * @author Stuart Douglas */ public class HostAndPortAttribute implements ExchangeAttribute { public static final String HOST_AND_PORT = "%{HOST_AND_PORT}"; public static final ExchangeAttribute INSTANCE = new HostAndPortAttribute(); private HostAndPortAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { return exchange.getHostAndPort(); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Host and Port", newValue); } @Override public String toString() { return HOST_AND_PORT; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Host and Port"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(HOST_AND_PORT)) { return HostAndPortAttribute.INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/IdentUsernameAttribute.java000066400000000000000000000040301420065311100316260ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; /** * The ident username, not used, included for apache access log compatibility * * @author Stuart Douglas */ public class IdentUsernameAttribute implements ExchangeAttribute { public static final String IDENT_USERNAME = "%l"; public static final ExchangeAttribute INSTANCE = new IdentUsernameAttribute(); private IdentUsernameAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { return null; } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Ident username", newValue); } @Override public String toString() { return IDENT_USERNAME; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Ident Username"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(IDENT_USERNAME)) { return IdentUsernameAttribute.INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/LocalIPAttribute.java000066400000000000000000000042761420065311100303620ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import java.net.InetSocketAddress; import io.undertow.server.HttpServerExchange; /** * The local IP address * * @author Stuart Douglas */ public class LocalIPAttribute implements ExchangeAttribute { public static final String LOCAL_IP = "%{LOCAL_IP}"; public static final String LOCAL_IP_SHORT = "%A"; public static final ExchangeAttribute INSTANCE = new LocalIPAttribute(); private LocalIPAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { InetSocketAddress localAddress = (InetSocketAddress) exchange.getConnection().getLocalAddress(); return localAddress.getAddress().getHostAddress(); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Local IP", newValue); } @Override public String toString() { return LOCAL_IP; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Local IP"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(LOCAL_IP) || token.equals(LOCAL_IP_SHORT)) { return LocalIPAttribute.INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/LocalPortAttribute.java000066400000000000000000000043161420065311100307710ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import java.net.InetSocketAddress; import io.undertow.server.HttpServerExchange; /** * The local port * * @author Stuart Douglas */ public class LocalPortAttribute implements ExchangeAttribute { public static final String LOCAL_PORT_SHORT = "%p"; public static final String LOCAL_PORT = "%{LOCAL_PORT}"; public static final ExchangeAttribute INSTANCE = new LocalPortAttribute(); private LocalPortAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { InetSocketAddress localAddress = (InetSocketAddress) exchange.getConnection().getLocalAddress(); return Integer.toString(localAddress.getPort()); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Local port", newValue); } @Override public String toString() { return LOCAL_PORT; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Local Port"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(LOCAL_PORT) || token.equals(LOCAL_PORT_SHORT)) { return LocalPortAttribute.INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/LocalServerNameAttribute.java000066400000000000000000000042061420065311100321120ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; /** * The local server name * * @author Stuart Douglas */ public class LocalServerNameAttribute implements ExchangeAttribute { public static final String LOCAL_SERVER_NAME_SHORT = "%v"; public static final String LOCAL_SERVER_NAME = "%{LOCAL_SERVER_NAME}"; public static final ExchangeAttribute INSTANCE = new LocalServerNameAttribute(); private LocalServerNameAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { return exchange.getHostName(); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Local server name", newValue); } @Override public String toString() { return LOCAL_SERVER_NAME; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Local server name"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(LOCAL_SERVER_NAME) || token.equals(LOCAL_SERVER_NAME_SHORT)) { return LocalServerNameAttribute.INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/NullAttribute.java000066400000000000000000000035621420065311100300060ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; /** * A cookie * * @author Stuart Douglas */ public class NullAttribute implements ExchangeAttribute { public static final String NAME = "%{NULL}"; public static final NullAttribute INSTANCE = new NullAttribute(); private NullAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { return null; } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException(NAME, newValue); } @Override public String toString() { return NAME; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "null"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(NAME)) { return INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/PathParameterAttribute.java000066400000000000000000000053141420065311100316260ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import java.util.ArrayDeque; import java.util.Deque; import io.undertow.server.HttpServerExchange; /** * Path parameter * * @author Stuart Douglas */ public class PathParameterAttribute implements ExchangeAttribute { private final String parameter; public PathParameterAttribute(String parameter) { this.parameter = parameter; } @Override public String readAttribute(final HttpServerExchange exchange) { Deque res = exchange.getPathParameters().get(parameter); if(res == null) { return null; }else if(res.isEmpty()) { return ""; } else if(res.size() ==1) { return res.getFirst(); } else { StringBuilder sb = new StringBuilder("["); int i = 0; for(String s : res) { sb.append(s); if(++i != res.size()) { sb.append(", "); } } sb.append("]"); return sb.toString(); } } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { final ArrayDeque value = new ArrayDeque<>(); value.add(newValue); exchange.getPathParameters().put(parameter, value); } @Override public String toString() { return "%{p," + parameter + "}"; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Path Parameter"; } @Override public ExchangeAttribute build(final String token) { if (token.startsWith("%{p,") && token.endsWith("}")) { final String qp = token.substring(4, token.length() - 1); return new PathParameterAttribute(qp); } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/PredicateContextAttribute.java000066400000000000000000000050041420065311100323320ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import java.util.Map; import io.undertow.predicate.Predicate; import io.undertow.server.HttpServerExchange; /** * @author Stuart Douglas */ public class PredicateContextAttribute implements ExchangeAttribute { private final String name; public PredicateContextAttribute(final String name) { this.name = name; } @Override public String readAttribute(final HttpServerExchange exchange) { Map context = exchange.getAttachment(Predicate.PREDICATE_CONTEXT); if (context != null) { Object object = context.get(name); return object == null ? null : object.toString(); } return null; } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { Map context = exchange.getAttachment(Predicate.PREDICATE_CONTEXT); if (context != null) { context.put(name, newValue); } } @Override public String toString() { return "${" + name + "}"; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Predicate context variable"; } @Override public ExchangeAttribute build(final String token) { if (token.startsWith("${") && token.endsWith("}") && token.length() > 3) { return new PredicateContextAttribute(token.substring(2, token.length() - 1)); } else if (token.startsWith("$")) { return new PredicateContextAttribute(token.substring(1, token.length())); } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/QueryParameterAttribute.java000066400000000000000000000053231420065311100320370ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; import java.util.ArrayDeque; import java.util.Deque; /** * Query parameter * * @author Stuart Douglas */ public class QueryParameterAttribute implements ExchangeAttribute { private final String parameter; public QueryParameterAttribute(String parameter) { this.parameter = parameter; } @Override public String readAttribute(final HttpServerExchange exchange) { Deque res = exchange.getQueryParameters().get(parameter); if(res == null) { return null; }else if(res.isEmpty()) { return ""; } else if(res.size() ==1) { return res.getFirst(); } else { StringBuilder sb = new StringBuilder("["); int i = 0; for(String s : res) { sb.append(s); if(++i != res.size()) { sb.append(", "); } } sb.append("]"); return sb.toString(); } } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { final ArrayDeque value = new ArrayDeque<>(); value.add(newValue); exchange.getQueryParameters().put(parameter, value); } @Override public String toString() { return "%{q," + parameter + "}"; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Query Parameter"; } @Override public ExchangeAttribute build(final String token) { if (token.startsWith("%{q,") && token.endsWith("}")) { final String qp = token.substring(4, token.length() - 1); return new QueryParameterAttribute(qp); } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/QueryStringAttribute.java000066400000000000000000000052501420065311100313640ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; /** * The query string * * @author Stuart Douglas */ public class QueryStringAttribute implements ExchangeAttribute { public static final String QUERY_STRING_SHORT = "%q"; public static final String QUERY_STRING = "%{QUERY_STRING}"; public static final String BARE_QUERY_STRING = "%{BARE_QUERY_STRING}"; public static final ExchangeAttribute INSTANCE = new QueryStringAttribute(true); public static final ExchangeAttribute BARE_INSTANCE = new QueryStringAttribute(false); private final boolean includeQuestionMark; private QueryStringAttribute(boolean includeQuestionMark) { this.includeQuestionMark = includeQuestionMark; } @Override public String readAttribute(final HttpServerExchange exchange) { String qs = exchange.getQueryString(); if(qs.isEmpty() || !includeQuestionMark) { return qs; } return '?' + qs; } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { exchange.setQueryString(newValue); } @Override public String toString() { if(includeQuestionMark) { return QUERY_STRING; } else { return BARE_QUERY_STRING; } } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Query String"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(QUERY_STRING) || token.equals(QUERY_STRING_SHORT)) { return QueryStringAttribute.INSTANCE; } else if(token.equals(BARE_QUERY_STRING)) { return QueryStringAttribute.BARE_INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/QuotingExchangeAttribute.java000066400000000000000000000052171420065311100321640ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; /** * Exchange attribute that wraps string attributes in quotes. * * This is mostly used * * @author Stuart Douglas */ public class QuotingExchangeAttribute implements ExchangeAttribute { private final ExchangeAttribute exchangeAttribute; public static final ExchangeAttributeWrapper WRAPPER = new Wrapper(); public QuotingExchangeAttribute(ExchangeAttribute exchangeAttribute) { this.exchangeAttribute = exchangeAttribute; } @Override public String readAttribute(HttpServerExchange exchange) { String svalue = exchangeAttribute.readAttribute(exchange); // Does the value contain a " ? If so must encode it if (svalue == null || "-".equals(svalue) || svalue.isEmpty()) { return "-"; } /* Wrap all quotes in double quotes. */ StringBuilder buffer = new StringBuilder(svalue.length() + 2); buffer.append('\''); int i = 0; while (i < svalue.length()) { int j = svalue.indexOf('\'', i); if (j == -1) { buffer.append(svalue.substring(i)); i = svalue.length(); } else { buffer.append(svalue.substring(i, j + 1)); buffer.append('"'); i = j + 2; } } buffer.append('\''); return buffer.toString(); } @Override public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException(); } @Override public String toString() { return "\"" + exchangeAttribute.toString() + "\""; } public static class Wrapper implements ExchangeAttributeWrapper { @Override public ExchangeAttribute wrap(final ExchangeAttribute attribute) { return new QuotingExchangeAttribute(attribute); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/ReadOnlyAttributeException.java000066400000000000000000000022341420065311100324630ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.UndertowMessages; /** * An exception that is thrown when an attribute is read only * * @author Stuart Douglas */ public class ReadOnlyAttributeException extends Exception { public ReadOnlyAttributeException() { } public ReadOnlyAttributeException(final String attributeName, final String newValue) { super(UndertowMessages.MESSAGES.couldNotSetAttribute(attributeName, newValue)); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/RelativePathAttribute.java000066400000000000000000000070621420065311100314630ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; import io.undertow.util.QueryParameterUtils; /** * The relative path * * @author Stuart Douglas */ public class RelativePathAttribute implements ExchangeAttribute { public static final String RELATIVE_PATH_SHORT = "%R"; public static final String RELATIVE_PATH = "%{RELATIVE_PATH}"; public static final ExchangeAttribute INSTANCE = new RelativePathAttribute(); private RelativePathAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { return exchange.getRelativePath(); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { int pos = newValue.indexOf('?'); if (pos == -1) { exchange.setRelativePath(newValue); String requestURI = exchange.getResolvedPath() + newValue; if(requestURI.contains("%")) { //as the request URI is supposed to be encoded we need to replace //percent characters with their encoded form, otherwise we can run into issues //where the percent will be taked to be a encoded character //TODO: should we fully encode this? It seems like it also has the potential to cause issues, and encoding the percent character is probably enough exchange.setRequestURI(requestURI.replaceAll("%", "%25")); } else { exchange.setRequestURI(requestURI); } exchange.setRequestPath(requestURI); } else { final String path = newValue.substring(0, pos); exchange.setRelativePath(path); String requestURI = exchange.getResolvedPath() + path; if(requestURI.contains("%")) { exchange.setRequestURI(requestURI.replaceAll("%", "%25")); } else { exchange.setRequestURI(requestURI); } exchange.setRequestPath(requestURI); final String newQueryString = newValue.substring(pos); exchange.setQueryString(newQueryString); exchange.getQueryParameters().putAll(QueryParameterUtils.parseQueryString(newQueryString.substring(1), QueryParameterUtils.getQueryParamEncoding(exchange))); } } @Override public String toString() { return RELATIVE_PATH; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Relative Path"; } @Override public ExchangeAttribute build(final String token) { return token.equals(RELATIVE_PATH) || token.equals(RELATIVE_PATH_SHORT) ? INSTANCE : null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/RemoteHostAttribute.java000066400000000000000000000043241420065311100311620ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; import java.net.InetSocketAddress; /** * The remote Host address (if resolved) * * @author Stuart Douglas */ public class RemoteHostAttribute implements ExchangeAttribute { public static final String REMOTE_HOST_NAME_SHORT = "%h"; public static final String REMOTE_HOST = "%{REMOTE_HOST}"; public static final ExchangeAttribute INSTANCE = new RemoteHostAttribute(); private RemoteHostAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { final InetSocketAddress sourceAddress = exchange.getSourceAddress(); return sourceAddress.getHostString(); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Remote host", newValue); } @Override public String toString() { return REMOTE_HOST; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Remote host"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(REMOTE_HOST) || token.equals(REMOTE_HOST_NAME_SHORT)) { return RemoteHostAttribute.INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/RemoteIPAttribute.java000066400000000000000000000051241420065311100305540ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import java.net.InetAddress; import java.net.InetSocketAddress; import io.undertow.server.HttpServerExchange; /** * The remote IP address * * @author Stuart Douglas */ public class RemoteIPAttribute implements ExchangeAttribute { public static final String REMOTE_IP_SHORT = "%a"; public static final String REMOTE_IP = "%{REMOTE_IP}"; public static final ExchangeAttribute INSTANCE = new RemoteIPAttribute(); private RemoteIPAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { final InetSocketAddress sourceAddress = exchange.getSourceAddress(); InetAddress address = sourceAddress.getAddress(); if (address == null) { //this can happen when we have an unresolved X-forwarded-for address //in this case we just return the IP of the balancer address = ((InetSocketAddress) exchange.getConnection().getPeerAddress()).getAddress(); } if(address == null) { return null; } return address.getHostAddress(); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Remote IP", newValue); } @Override public String toString() { return REMOTE_IP; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Remote IP"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(REMOTE_IP) || token.equals(REMOTE_IP_SHORT)) { return RemoteIPAttribute.INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/RemoteObfuscatedIPAttribute.java000066400000000000000000000054121420065311100325540ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; import io.undertow.util.NetworkUtils; import java.net.InetAddress; import java.net.InetSocketAddress; /** * The remote IP address * * @author Stuart Douglas */ public class RemoteObfuscatedIPAttribute implements ExchangeAttribute { public static final String REMOTE_OBFUSCATED_IP_SHORT = "%o"; public static final String REMOTE_OBFUSCATED_IP = "%{REMOTE_OBFUSCATED_IP}"; public static final ExchangeAttribute INSTANCE = new RemoteObfuscatedIPAttribute(); private RemoteObfuscatedIPAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { final InetSocketAddress sourceAddress = exchange.getSourceAddress(); InetAddress address = sourceAddress.getAddress(); if (address == null) { //this can happen when we have an unresolved X-forwarded-for address //in this case we just return the IP of the balancer address = ((InetSocketAddress) exchange.getConnection().getPeerAddress()).getAddress(); } if(address == null) { return null; } return NetworkUtils.toObfuscatedString(address); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Remote Obfuscated IP", newValue); } @Override public String toString() { return REMOTE_OBFUSCATED_IP; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Remote Obfuscated IP"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(REMOTE_OBFUSCATED_IP) || token.equals(REMOTE_OBFUSCATED_IP_SHORT)) { return RemoteObfuscatedIPAttribute.INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/RemoteUserAttribute.java000066400000000000000000000044361420065311100311670ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.security.api.SecurityContext; import io.undertow.server.HttpServerExchange; /** * The remote user * * @author Stuart Douglas */ public class RemoteUserAttribute implements ExchangeAttribute { public static final String REMOTE_USER_SHORT = "%u"; public static final String REMOTE_USER = "%{REMOTE_USER}"; public static final ExchangeAttribute INSTANCE = new RemoteUserAttribute(); private RemoteUserAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { SecurityContext sc = exchange.getSecurityContext(); if (sc == null || !sc.isAuthenticated()) { return null; } return sc.getAuthenticatedAccount().getPrincipal().getName(); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Remote user", newValue); } @Override public String toString() { return REMOTE_USER; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Remote user"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(REMOTE_USER) || token.equals(REMOTE_USER_SHORT)) { return RemoteUserAttribute.INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/RequestCookieAttribute.java000066400000000000000000000046751420065311100316640ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.Cookie; import io.undertow.server.handlers.CookieImpl; /** * A request cookie * @author Richard Opalka */ public class RequestCookieAttribute implements ExchangeAttribute { private static final String TOKEN_PREFIX = "%{req-cookie,"; private final String cookieName; public RequestCookieAttribute(final String cookieName) { this.cookieName = cookieName; } @Override public String readAttribute(final HttpServerExchange exchange) { for (Cookie cookie : exchange.requestCookies()) { if (cookieName.equals(cookie.getName())) { return cookie.getValue(); } } return null; } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { exchange.setRequestCookie(new CookieImpl(cookieName, newValue)); } @Override public String toString() { return TOKEN_PREFIX + cookieName + "}"; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Request cookie"; } @Override public ExchangeAttribute build(final String token) { if (token.startsWith(TOKEN_PREFIX) && token.endsWith("}")) { final String cookieName = token.substring(TOKEN_PREFIX.length(), token.length() - 1); return new RequestCookieAttribute(cookieName); } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/RequestHeaderAttribute.java000066400000000000000000000052261420065311100316340ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; import io.undertow.util.HeaderValues; import io.undertow.util.HttpString; /** * A request header * * @author Stuart Douglas */ public class RequestHeaderAttribute implements ExchangeAttribute { private final HttpString requestHeader; public RequestHeaderAttribute(final HttpString requestHeader) { this.requestHeader = requestHeader; } @Override public String readAttribute(final HttpServerExchange exchange) { HeaderValues header = exchange.getRequestHeaders().get(requestHeader); if (header == null) { return null; } else if(header.size() == 1) { return header.getFirst(); } StringBuilder sb = new StringBuilder(); sb.append("["); for (int i = 0; i < header.size(); ++i) { if (i != 0) { sb.append(", "); } sb.append(header.get(i)); } sb.append("]"); return sb.toString(); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { exchange.getRequestHeaders().put(requestHeader, newValue); } @Override public String toString() { return "%{i," + requestHeader + "}"; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Request header"; } @Override public ExchangeAttribute build(final String token) { if (token.startsWith("%{i,") && token.endsWith("}")) { final HttpString headerName = HttpString.tryFromString(token.substring(4, token.length() - 1)); return new RequestHeaderAttribute(headerName); } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/RequestLineAttribute.java000066400000000000000000000047511420065311100313350ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; /** * The request line * * @author Stuart Douglas */ public class RequestLineAttribute implements ExchangeAttribute { public static final String REQUEST_LINE_SHORT = "%r"; public static final String REQUEST_LINE = "%{REQUEST_LINE}"; public static final ExchangeAttribute INSTANCE = new RequestLineAttribute(); private RequestLineAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { StringBuilder sb = new StringBuilder() .append(exchange.getRequestMethod().toString()) .append(' ') .append(exchange.getRequestURI()); if (!exchange.getQueryString().isEmpty()) { sb.append('?'); sb.append(exchange.getQueryString()); } sb.append(' ') .append(exchange.getProtocol().toString()).toString(); return sb.toString(); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Request line", newValue); } @Override public String toString() { return REQUEST_LINE; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Request line"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(REQUEST_LINE) || token.equals(REQUEST_LINE_SHORT)) { return RequestLineAttribute.INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/RequestMethodAttribute.java000066400000000000000000000041531420065311100316620ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; /** * The request method * * @author Stuart Douglas */ public class RequestMethodAttribute implements ExchangeAttribute { public static final String REQUEST_METHOD_SHORT = "%m"; public static final String REQUEST_METHOD = "%{METHOD}"; public static final ExchangeAttribute INSTANCE = new RequestMethodAttribute(); private RequestMethodAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { return exchange.getRequestMethod().toString(); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Request method", newValue); } @Override public String toString() { return REQUEST_METHOD; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Request method"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(REQUEST_METHOD) || token.equals(REQUEST_METHOD_SHORT)) { return RequestMethodAttribute.INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/RequestPathAttribute.java000066400000000000000000000051461420065311100313410ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; import io.undertow.util.QueryParameterUtils; /** * @author Stuart Douglas */ public class RequestPathAttribute implements ExchangeAttribute { public static final String REQUEST_PATH = "%{REQUEST_PATH}"; public static final ExchangeAttribute INSTANCE = new RequestPathAttribute(); private RequestPathAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { return exchange.getRelativePath(); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { int pos = newValue.indexOf('?'); exchange.setResolvedPath(""); if (pos == -1) { exchange.setRelativePath(newValue); exchange.setRequestURI(newValue); exchange.setRequestPath(newValue); } else { final String path = newValue.substring(0, pos); exchange.setRequestPath(path); exchange.setRelativePath(path); exchange.setRequestURI(newValue); final String newQueryString = newValue.substring(pos); exchange.setQueryString(newQueryString); exchange.getQueryParameters().putAll(QueryParameterUtils.parseQueryString(newQueryString.substring(1), QueryParameterUtils.getQueryParamEncoding(exchange))); } } @Override public String toString() { return REQUEST_PATH; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Request Path"; } @Override public ExchangeAttribute build(final String token) { return token.equals(REQUEST_PATH) ? INSTANCE : null; } @Override public int priority() { return 0; } } }undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/RequestProtocolAttribute.java000066400000000000000000000042001420065311100322340ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; /** * The request protocol * * @author Stuart Douglas */ public class RequestProtocolAttribute implements ExchangeAttribute { public static final String REQUEST_PROTOCOL_SHORT = "%H"; public static final String REQUEST_PROTOCOL = "%{PROTOCOL}"; public static final ExchangeAttribute INSTANCE = new RequestProtocolAttribute(); private RequestProtocolAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { return exchange.getProtocol().toString(); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Request protocol", newValue); } @Override public String toString() { return REQUEST_PROTOCOL; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Request protocol"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(REQUEST_PROTOCOL) || token.equals(REQUEST_PROTOCOL_SHORT)) { return RequestProtocolAttribute.INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/RequestSchemeAttribute.java000066400000000000000000000037411420065311100316500ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; /** * The request scheme * * @author Stuart Douglas */ public class RequestSchemeAttribute implements ExchangeAttribute { public static final String REQUEST_SCHEME = "%{SCHEME}"; public static final ExchangeAttribute INSTANCE = new RequestSchemeAttribute(); private RequestSchemeAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { return exchange.getRequestScheme(); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { exchange.setRequestScheme(newValue); } @Override public String toString() { return REQUEST_SCHEME; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Request scheme"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(REQUEST_SCHEME)) { return RequestSchemeAttribute.INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/RequestURLAttribute.java000066400000000000000000000055021420065311100311030ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; import io.undertow.util.QueryParameterUtils; /** * The request URL * * @author Stuart Douglas */ public class RequestURLAttribute implements ExchangeAttribute { public static final String REQUEST_URL_SHORT = "%U"; public static final String REQUEST_URL = "%{REQUEST_URL}"; public static final ExchangeAttribute INSTANCE = new RequestURLAttribute(); private RequestURLAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { return exchange.getRequestURI(); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { int pos = newValue.indexOf('?'); if (pos == -1) { exchange.setRelativePath(newValue); exchange.setRequestURI(newValue); exchange.setRequestPath(newValue); exchange.setResolvedPath(""); } else { final String path = newValue.substring(0, pos); exchange.setRelativePath(path); exchange.setRequestURI(path); exchange.setRequestPath(path); exchange.setResolvedPath(""); final String newQueryString = newValue.substring(pos); exchange.setQueryString(newQueryString); exchange.getQueryParameters().putAll(QueryParameterUtils.parseQueryString(newQueryString.substring(1), QueryParameterUtils.getQueryParamEncoding(exchange))); } } @Override public String toString() { return REQUEST_URL; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Request URL"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(REQUEST_URL) || token.equals(REQUEST_URL_SHORT)) { return RequestURLAttribute.INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/ResolvedPathAttribute.java000066400000000000000000000035711420065311100314740ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; /** * @author Stuart Douglas */ public class ResolvedPathAttribute implements ExchangeAttribute { public static final String RESOLVED_PATH = "%{RESOLVED_PATH}"; public static final ExchangeAttribute INSTANCE = new ResolvedPathAttribute(); private ResolvedPathAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { return exchange.getResolvedPath(); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { exchange.setResolvedPath(newValue); } @Override public String toString() { return RESOLVED_PATH; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Resolved Path"; } @Override public ExchangeAttribute build(final String token) { return token.equals(RESOLVED_PATH) ? INSTANCE : null; } @Override public int priority() { return 0; } } }undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/ResponseCodeAttribute.java000066400000000000000000000041431420065311100314610ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; /** * The request status code * * @author Stuart Douglas */ public class ResponseCodeAttribute implements ExchangeAttribute { public static final String RESPONSE_CODE_SHORT = "%s"; public static final String RESPONSE_CODE = "%{RESPONSE_CODE}"; public static final ExchangeAttribute INSTANCE = new ResponseCodeAttribute(); private ResponseCodeAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { return Integer.toString(exchange.getStatusCode()); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { exchange.setStatusCode(Integer.parseInt(newValue)); } @Override public String toString() { return RESPONSE_CODE; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Response code"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(RESPONSE_CODE) || token.equals(RESPONSE_CODE_SHORT)) { return ResponseCodeAttribute.INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/ResponseCookieAttribute.java000066400000000000000000000047051420065311100320240ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.Cookie; import io.undertow.server.handlers.CookieImpl; /** * A response cookie * @author Richard Opalka */ public class ResponseCookieAttribute implements ExchangeAttribute { private static final String TOKEN_PREFIX = "%{resp-cookie,"; private final String cookieName; public ResponseCookieAttribute(final String cookieName) { this.cookieName = cookieName; } @Override public String readAttribute(final HttpServerExchange exchange) { for (Cookie cookie : exchange.responseCookies()) { if (cookieName.equals(cookie.getName())) { return cookie.getValue(); } } return null; } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { exchange.setResponseCookie(new CookieImpl(cookieName, newValue)); } @Override public String toString() { return TOKEN_PREFIX + cookieName + "}"; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Response cookie"; } @Override public ExchangeAttribute build(final String token) { if (token.startsWith(TOKEN_PREFIX) && token.endsWith("}")) { final String cookieName = token.substring(TOKEN_PREFIX.length(), token.length() - 1); return new ResponseCookieAttribute(cookieName); } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/ResponseHeaderAttribute.java000066400000000000000000000052441420065311100320020ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; import io.undertow.util.HeaderValues; import io.undertow.util.HttpString; /** * A response header * * @author Stuart Douglas */ public class ResponseHeaderAttribute implements ExchangeAttribute { private final HttpString responseHeader; public ResponseHeaderAttribute(final HttpString responseHeader) { this.responseHeader = responseHeader; } @Override public String readAttribute(final HttpServerExchange exchange) { HeaderValues header = exchange.getResponseHeaders().get(responseHeader); if (header == null) { return null; } else if(header.size() == 1) { return header.getFirst(); } StringBuilder sb = new StringBuilder(); sb.append("["); for (int i = 0; i < header.size(); ++i) { if (i != 0) { sb.append(", "); } sb.append(header.get(i)); } sb.append("]"); return sb.toString(); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { exchange.getResponseHeaders().put(responseHeader, newValue); } @Override public String toString() { return "%{o," + responseHeader + "}"; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Response header"; } @Override public ExchangeAttribute build(final String token) { if (token.startsWith("%{o,") && token.endsWith("}")) { final HttpString headerName = HttpString.tryFromString(token.substring(4, token.length() - 1)); return new ResponseHeaderAttribute(headerName); } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/ResponseReasonPhraseAttribute.java000066400000000000000000000041531420065311100332020ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; import io.undertow.util.StatusCodes; /** * The request status code * * @author Stuart Douglas */ public class ResponseReasonPhraseAttribute implements ExchangeAttribute { public static final String RESPONSE_REASON_PHRASE = "%{RESPONSE_REASON_PHRASE}"; public static final ExchangeAttribute INSTANCE = new ResponseReasonPhraseAttribute(); private ResponseReasonPhraseAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { return StatusCodes.getReason(exchange.getStatusCode()); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { exchange.setReasonPhrase(newValue); } @Override public String toString() { return RESPONSE_REASON_PHRASE; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Response reason phrase"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(RESPONSE_REASON_PHRASE)) { return ResponseReasonPhraseAttribute.INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/ResponseTimeAttribute.java000066400000000000000000000111121420065311100314770ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; import io.undertow.util.AttachmentKey; import java.util.concurrent.TimeUnit; /** * The response time * * This will only work if {@link io.undertow.UndertowOptions#RECORD_REQUEST_START_TIME} has been set */ public class ResponseTimeAttribute implements ExchangeAttribute { private static final AttachmentKey FIRST_RESPONSE_TIME_NANOS = AttachmentKey.create(Long.class); public static final String RESPONSE_TIME_MILLIS_SHORT = "%D"; public static final String RESPONSE_TIME_SECONDS_SHORT = "%T"; public static final String RESPONSE_TIME_MILLIS = "%{RESPONSE_TIME}"; public static final String RESPONSE_TIME_MICROS = "%{RESPONSE_TIME_MICROS}"; public static final String RESPONSE_TIME_NANOS = "%{RESPONSE_TIME_NANOS}"; private final TimeUnit timeUnit; public ResponseTimeAttribute(TimeUnit timeUnit) { this.timeUnit = timeUnit; } @Override public String readAttribute(HttpServerExchange exchange) { long requestStartTime = exchange.getRequestStartTime(); if(requestStartTime == -1) { return null; } final long nanos; Long first = exchange.getAttachment(FIRST_RESPONSE_TIME_NANOS); if(first != null) { nanos = first; } else { nanos = System.nanoTime() - requestStartTime; if(exchange.isResponseComplete()) { //save the response time so it is consistent exchange.putAttachment(FIRST_RESPONSE_TIME_NANOS, nanos); } } if(timeUnit == TimeUnit.SECONDS) { StringBuilder buf = new StringBuilder(); long milis = TimeUnit.MILLISECONDS.convert(nanos, TimeUnit.NANOSECONDS); buf.append(Long.toString(milis / 1000)); buf.append('.'); int remains = (int) (milis % 1000); buf.append(Long.toString(remains / 100)); remains = remains % 100; buf.append(Long.toString(remains / 10)); buf.append(Long.toString(remains % 10)); return buf.toString(); } else { return String.valueOf(timeUnit.convert(nanos, TimeUnit.NANOSECONDS)); } } @Override public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Response Time", newValue); } @Override public String toString() { if (timeUnit.equals(TimeUnit.MILLISECONDS)) { return RESPONSE_TIME_MILLIS; } if (timeUnit.equals(TimeUnit.SECONDS)) { return RESPONSE_TIME_SECONDS_SHORT; } if(timeUnit.equals(TimeUnit.MICROSECONDS)) { return RESPONSE_TIME_MICROS; } if(timeUnit.equals(TimeUnit.NANOSECONDS)) { return RESPONSE_TIME_NANOS; } return "ResponseTimeAttribute"; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Response Time"; } @Override public ExchangeAttribute build(String token) { if (token.equals(RESPONSE_TIME_MILLIS) || token.equals(RESPONSE_TIME_MILLIS_SHORT)) { return new ResponseTimeAttribute(TimeUnit.MILLISECONDS); } if (token.equals(RESPONSE_TIME_SECONDS_SHORT)) { return new ResponseTimeAttribute(TimeUnit.SECONDS); } if(token.equals(RESPONSE_TIME_MICROS)) { return new ResponseTimeAttribute(TimeUnit.MICROSECONDS); } if(token.equals(RESPONSE_TIME_NANOS)) { return new ResponseTimeAttribute(TimeUnit.NANOSECONDS); } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/SecureExchangeAttribute.java000066400000000000000000000040441420065311100317610ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; /** * @author Stuart Douglas */ public class SecureExchangeAttribute implements ExchangeAttribute { public static final String TOKEN = "%{SECURE}"; public static final String LEGACY_INCORRECT_TOKEN = "${SECURE}"; //this was a bug, but we still support it for compat public static final ExchangeAttribute INSTANCE = new SecureExchangeAttribute(); @Override public String readAttribute(HttpServerExchange exchange) { return Boolean.toString(exchange.isSecure()); } @Override public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException { exchange.putAttachment(HttpServerExchange.SECURE_REQUEST, Boolean.parseBoolean(newValue)); } @Override public String toString() { return TOKEN; } public static class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Secure"; } @Override public ExchangeAttribute build(String token) { if(token.equals(TOKEN) || token.equals(LEGACY_INCORRECT_TOKEN)) { return INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/SslCipherAttribute.java000066400000000000000000000037551420065311100307740ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; import io.undertow.server.SSLSessionInfo; /** * @author Stuart Douglas */ public class SslCipherAttribute implements ExchangeAttribute { public static final SslCipherAttribute INSTANCE = new SslCipherAttribute(); @Override public String readAttribute(HttpServerExchange exchange) { SSLSessionInfo ssl = exchange.getConnection().getSslSessionInfo(); if(ssl == null) { return null; } return ssl.getCipherSuite(); } @Override public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("SSL Cipher", newValue); } @Override public String toString() { return "%{SSL_CIPHER}"; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "SSL Cipher"; } @Override public ExchangeAttribute build(final String token) { if (token.equals("%{SSL_CIPHER}")) { return INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/SslClientCertAttribute.java000066400000000000000000000052021420065311100316030ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; import io.undertow.server.RenegotiationRequiredException; import io.undertow.server.SSLSessionInfo; import io.undertow.util.Certificates; import javax.net.ssl.SSLPeerUnverifiedException; import java.security.cert.CertificateEncodingException; import java.security.cert.Certificate; /** * @author Stuart Douglas */ public class SslClientCertAttribute implements ExchangeAttribute { public static final SslClientCertAttribute INSTANCE = new SslClientCertAttribute(); @Override public String readAttribute(HttpServerExchange exchange) { SSLSessionInfo ssl = exchange.getConnection().getSslSessionInfo(); if(ssl == null) { return null; } Certificate[] certificates; try { certificates = ssl.getPeerCertificates(); if(certificates.length > 0) { return Certificates.toPem(certificates[0]); } return null; } catch (SSLPeerUnverifiedException | CertificateEncodingException | RenegotiationRequiredException e) { return null; } } @Override public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("SSL Client Cert", newValue); } @Override public String toString() { return "%{SSL_CLIENT_CERT}"; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "SSL Client Cert"; } @Override public ExchangeAttribute build(final String token) { if (token.equals("%{SSL_CLIENT_CERT}")) { return INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/SslSessionIdAttribute.java000066400000000000000000000041511420065311100314510ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; import io.undertow.server.SSLSessionInfo; import io.undertow.util.HexConverter; /** * @author Stuart Douglas */ public class SslSessionIdAttribute implements ExchangeAttribute { public static final SslSessionIdAttribute INSTANCE = new SslSessionIdAttribute(); @Override public String readAttribute(HttpServerExchange exchange) { SSLSessionInfo ssl = exchange.getConnection().getSslSessionInfo(); if(ssl == null || ssl.getSessionId() == null) { return null; } return HexConverter.convertToHexString(ssl.getSessionId()); } @Override public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("SSL Session ID", newValue); } @Override public String toString() { return "%{SSL_SESSION_ID}"; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "SSL Session ID"; } @Override public ExchangeAttribute build(final String token) { if (token.equals("%{SSL_SESSION_ID}")) { return INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/StoredResponse.java000066400000000000000000000062261420065311100301670ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import io.undertow.UndertowLogger; import io.undertow.conduits.StoredResponseStreamSinkConduit; import io.undertow.server.HttpServerExchange; import io.undertow.util.HeaderMap; import io.undertow.util.Headers; /** * @author Stuart Douglas */ public class StoredResponse implements ExchangeAttribute { public static final ExchangeAttribute INSTANCE = new StoredResponse(); private StoredResponse() { } @Override public String readAttribute(HttpServerExchange exchange) { byte[] data = exchange.getAttachment(StoredResponseStreamSinkConduit.RESPONSE); if(data == null) { return null; } String charset = extractCharset(exchange.getResponseHeaders()); if(charset == null) { return null; } try { return new String(data, charset); } catch (UnsupportedEncodingException e) { UndertowLogger.ROOT_LOGGER.debugf(e,"Could not decode response body using charset %s", charset); return null; } } private String extractCharset(HeaderMap headers) { String contentType = headers.getFirst(Headers.CONTENT_TYPE); if (contentType != null) { String value = Headers.extractQuotedValueFromHeader(contentType, "charset"); if (value != null) { return value; } //if it is text we default to ISO_8859_1 if(contentType.startsWith("text/")) { return StandardCharsets.ISO_8859_1.displayName(); } return null; } return null; } @Override public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Stored Response", newValue); } @Override public String toString() { return "%{STORED_RESPONSE}"; } public static class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Stored Response"; } @Override public ExchangeAttribute build(final String token) { if (token.equals("%{STORED_RESPONSE}")) { return INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/SubstituteEmptyWrapper.java000066400000000000000000000024611420065311100317400ustar00rootroot00000000000000package io.undertow.attribute; import io.undertow.server.HttpServerExchange; /** * @author Stuart Douglas */ public class SubstituteEmptyWrapper implements ExchangeAttributeWrapper { private final String substitute; public SubstituteEmptyWrapper(String substitute) { this.substitute = substitute; } @Override public ExchangeAttribute wrap(final ExchangeAttribute attribute) { return new SubstituteEmptyAttribute(attribute, substitute); } public static class SubstituteEmptyAttribute implements ExchangeAttribute { private final ExchangeAttribute attribute; private final String substitute; public SubstituteEmptyAttribute(ExchangeAttribute attribute, String substitute) { this.attribute = attribute; this.substitute = substitute; } @Override public String readAttribute(HttpServerExchange exchange) { String val = attribute.readAttribute(exchange); if(val == null || val.isEmpty()) { return substitute; } return val; } @Override public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException { attribute.writeAttribute(exchange, newValue); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/ThreadNameAttribute.java000066400000000000000000000041061420065311100310770ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; /** * The thread name * * @author Stuart Douglas */ public class ThreadNameAttribute implements ExchangeAttribute { public static final String THREAD_NAME_SHORT = "%I"; public static final String THREAD_NAME = "%{THREAD_NAME}"; public static final ExchangeAttribute INSTANCE = new ThreadNameAttribute(); private ThreadNameAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { return Thread.currentThread().getName(); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Thread name", newValue); } @Override public String toString() { return THREAD_NAME; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Thread name"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(THREAD_NAME) || token.equals(THREAD_NAME_SHORT)) { return ThreadNameAttribute.INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/attribute/TransportProtocolAttribute.java000066400000000000000000000041021420065311100326010ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.attribute; import io.undertow.server.HttpServerExchange; /** * The request method * * @author Stuart Douglas */ public class TransportProtocolAttribute implements ExchangeAttribute { public static final String TRANSPORT_PROTOCOL = "%{TRANSPORT_PROTOCOL}"; public static final ExchangeAttribute INSTANCE = new TransportProtocolAttribute(); private TransportProtocolAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { return exchange.getConnection().getTransportProtocol(); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("transport protocol", newValue); } @Override public String toString() { return TRANSPORT_PROTOCOL; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Transport Protocol"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(TRANSPORT_PROTOCOL)) { return TransportProtocolAttribute.INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/channels/000077500000000000000000000000001420065311100241275ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/channels/DetachableStreamSinkChannel.java000066400000000000000000000216221420065311100323030ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.channels; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.Option; import org.xnio.XnioExecutor; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.ConduitStreamSinkChannel; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.concurrent.TimeUnit; /** * Stream sink channel. When this channel is considered detached it will no longer forward * calls to the delegate * * @author Stuart Douglas */ public abstract class DetachableStreamSinkChannel implements StreamSinkChannel { protected final StreamSinkChannel delegate; protected ChannelListener.SimpleSetter writeSetter; protected ChannelListener.SimpleSetter closeSetter; public DetachableStreamSinkChannel(final StreamSinkChannel delegate) { this.delegate = delegate; } protected abstract boolean isFinished(); @Override public void suspendWrites() { if (isFinished()) { return; } delegate.suspendWrites(); } @Override public boolean isWriteResumed() { if (isFinished()) { return false; } return delegate.isWriteResumed(); } @Override public void shutdownWrites() throws IOException { if (isFinished()) { return; } delegate.shutdownWrites(); } @Override public void awaitWritable() throws IOException { if (isFinished()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } delegate.awaitWritable(); } @Override public void awaitWritable(final long time, final TimeUnit timeUnit) throws IOException { if (isFinished()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } delegate.awaitWritable(time, timeUnit); } @Override public XnioExecutor getWriteThread() { return delegate.getWriteThread(); } @Override public boolean isOpen() { return !isFinished() && delegate.isOpen(); } @Override public void close() throws IOException { if (isFinished()) return; delegate.close(); } @Override public boolean flush() throws IOException { if (isFinished()) { return true; } return delegate.flush(); } @Override public long transferFrom(final FileChannel src, final long position, final long count) throws IOException { if (isFinished()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } return delegate.transferFrom(src, position, count); } @Override public long transferFrom(final StreamSourceChannel source, final long count, final ByteBuffer throughBuffer) throws IOException { if (isFinished()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } return delegate.transferFrom(source, count, throughBuffer); } @Override public ChannelListener.Setter getWriteSetter() { if (writeSetter == null) { writeSetter = new ChannelListener.SimpleSetter<>(); if (!isFinished()) { if(delegate instanceof ConduitStreamSinkChannel) { ((ConduitStreamSinkChannel) delegate).setWriteListener(new SetterDelegatingListener((ChannelListener.SimpleSetter)writeSetter, this)); } else { delegate.getWriteSetter().set(new SetterDelegatingListener((ChannelListener.SimpleSetter)writeSetter, this)); } } } return writeSetter; } @Override public ChannelListener.Setter getCloseSetter() { if (closeSetter == null) { closeSetter = new ChannelListener.SimpleSetter<>(); if (!isFinished()) { delegate.getCloseSetter().set(ChannelListeners.delegatingChannelListener(this, closeSetter)); } } return closeSetter; } @Override public XnioWorker getWorker() { return delegate.getWorker(); } @Override public XnioIoThread getIoThread() { return delegate.getIoThread(); } @Override public long write(final ByteBuffer[] srcs, final int offset, final int length) throws IOException { if (isFinished()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } return delegate.write(srcs, offset, length); } @Override public long write(final ByteBuffer[] srcs) throws IOException { if (isFinished()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } return delegate.write(srcs); } @Override public int writeFinal(ByteBuffer src) throws IOException { if (isFinished()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } return delegate.writeFinal(src); } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { if (isFinished()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } return delegate.writeFinal(srcs, offset, length); } @Override public long writeFinal(ByteBuffer[] srcs) throws IOException { if (isFinished()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } return delegate.writeFinal(srcs); } @Override public boolean supportsOption(final Option option) { return delegate.supportsOption(option); } @Override public T getOption(final Option option) throws IOException { if (isFinished()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } return delegate.getOption(option); } @Override public T setOption(final Option option, final T value) throws IllegalArgumentException, IOException { if (isFinished()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } return delegate.setOption(option, value); } @Override public int write(final ByteBuffer src) throws IOException { if (isFinished()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } return delegate.write(src); } @Override public void resumeWrites() { if (isFinished()) { return; } delegate.resumeWrites(); } @Override public void wakeupWrites() { if (isFinished()) { return; } delegate.wakeupWrites(); } public void responseDone() { if(delegate instanceof ConduitStreamSinkChannel) { ((ConduitStreamSinkChannel) delegate).setCloseListener(null); ((ConduitStreamSinkChannel) delegate).setWriteListener(null); } else { delegate.getCloseSetter().set(null); delegate.getWriteSetter().set(null); } if (delegate.isWriteResumed()) { delegate.suspendWrites(); } } private static class SetterDelegatingListener implements ChannelListener { private final SimpleSetter setter; private final StreamSinkChannel channel; SetterDelegatingListener(final SimpleSetter setter, final StreamSinkChannel channel) { this.setter = setter; this.channel = channel; } public void handleEvent(final StreamSinkChannel channel) { ChannelListener channelListener = setter.get(); if(channelListener != null) { ChannelListeners.invokeChannelListener(this.channel, channelListener); } else { UndertowLogger.REQUEST_LOGGER.debugf("suspending writes on %s to prevent listener runaway", channel); channel.suspendWrites(); } } public String toString() { return "Setter delegating channel listener -> " + setter; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/channels/DetachableStreamSourceChannel.java000066400000000000000000000166511420065311100326450ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.channels; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.concurrent.TimeUnit; import io.undertow.UndertowLogger; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.Option; import org.xnio.XnioExecutor; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.ConduitStreamSourceChannel; import io.undertow.UndertowMessages; /** * A stream source channel that can be marked as detached. Once this is marked as detached then * calls will no longer be forwarded to the delegate. * * @author Stuart Douglas */ public abstract class DetachableStreamSourceChannel implements StreamSourceChannel{ protected final StreamSourceChannel delegate; protected ChannelListener.SimpleSetter readSetter; protected ChannelListener.SimpleSetter closeSetter; public DetachableStreamSourceChannel(final StreamSourceChannel delegate) { this.delegate = delegate; } protected abstract boolean isFinished(); @Override public void resumeReads() { if (isFinished()) { return; } delegate.resumeReads(); } public long transferTo(final long position, final long count, final FileChannel target) throws IOException { if (isFinished()) { return -1; } return delegate.transferTo(position, count, target); } public void awaitReadable() throws IOException { if (isFinished()) { return; } delegate.awaitReadable(); } public void suspendReads() { if (isFinished()) { return; } delegate.suspendReads(); } public long transferTo(final long count, final ByteBuffer throughBuffer, final StreamSinkChannel target) throws IOException { if (isFinished()) { return -1; } return delegate.transferTo(count, throughBuffer, target); } public XnioWorker getWorker() { return delegate.getWorker(); } public boolean isReadResumed() { if (isFinished()) { return false; } return delegate.isReadResumed(); } public T setOption(final Option option, final T value) throws IllegalArgumentException, IOException { if (isFinished()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } return delegate.setOption(option, value); } public boolean supportsOption(final Option option) { return delegate.supportsOption(option); } public void shutdownReads() throws IOException { if (isFinished()) { return; } delegate.shutdownReads(); } public ChannelListener.Setter getReadSetter() { if (readSetter == null) { readSetter = new ChannelListener.SimpleSetter<>(); if (!isFinished()) { if(delegate instanceof ConduitStreamSourceChannel) { ((ConduitStreamSourceChannel)delegate).setReadListener(new SetterDelegatingListener((ChannelListener.SimpleSetter)readSetter, this)); } else { delegate.getReadSetter().set(new SetterDelegatingListener((ChannelListener.SimpleSetter)readSetter, this)); } } } return readSetter; } public boolean isOpen() { if (isFinished()) { return false; } return delegate.isOpen(); } public long read(final ByteBuffer[] dsts) throws IOException { if (isFinished()) { return -1; } return delegate.read(dsts); } public long read(final ByteBuffer[] dsts, final int offset, final int length) throws IOException { if (isFinished()) { return -1; } return delegate.read(dsts, offset, length); } public void wakeupReads() { if (isFinished()) { return; } delegate.wakeupReads(); } public XnioExecutor getReadThread() { return delegate.getReadThread(); } public void awaitReadable(final long time, final TimeUnit timeUnit) throws IOException { if (isFinished()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } delegate.awaitReadable(time, timeUnit); } public ChannelListener.Setter getCloseSetter() { if (closeSetter == null) { closeSetter = new ChannelListener.SimpleSetter<>(); if (!isFinished()) { if(delegate instanceof ConduitStreamSourceChannel) { ((ConduitStreamSourceChannel)delegate).setCloseListener(ChannelListeners.delegatingChannelListener(this, closeSetter)); } else { delegate.getCloseSetter().set(ChannelListeners.delegatingChannelListener(this, closeSetter)); } } } return closeSetter; } public void close() throws IOException { if (isFinished()) { return; } delegate.close(); } public T getOption(final Option option) throws IOException { if (isFinished()) { throw UndertowMessages.MESSAGES.streamIsClosed(); } return delegate.getOption(option); } public int read(final ByteBuffer dst) throws IOException { if (isFinished()) { return -1; } return delegate.read(dst); } @Override public XnioIoThread getIoThread() { return delegate.getIoThread(); } private static class SetterDelegatingListener implements ChannelListener { private final SimpleSetter setter; private final StreamSourceChannel channel; SetterDelegatingListener(final SimpleSetter setter, final StreamSourceChannel channel) { this.setter = setter; this.channel = channel; } public void handleEvent(final StreamSourceChannel channel) { ChannelListener channelListener = setter.get(); if(channelListener != null) { ChannelListeners.invokeChannelListener(this.channel, channelListener); } else { UndertowLogger.REQUEST_LOGGER.debugf("suspending reads on %s to prevent listener runaway", channel); channel.suspendReads(); } } public String toString() { return "Setter delegating channel listener -> " + setter; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/000077500000000000000000000000001420065311100236125ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/client/ALPNClientSelector.java000066400000000000000000000146131420065311100300540ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLEngine; import org.xnio.ChannelListener; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.PushBackStreamSourceConduit; import org.xnio.ssl.SslConnection; import io.undertow.protocols.alpn.ALPNManager; import io.undertow.protocols.alpn.ALPNProvider; import io.undertow.protocols.ssl.SslConduit; import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.util.ImmediatePooled; /** * @author Stuart Douglas */ public class ALPNClientSelector { private ALPNClientSelector() { } public static void runAlpn(final SslConnection sslConnection, final ChannelListener fallback, final ClientCallback failedListener, final ALPNProtocol... details) { SslConduit conduit = UndertowXnioSsl.getSslConduit(sslConnection); final ALPNProvider provider = ALPNManager.INSTANCE.getProvider(conduit.getSSLEngine()); if (provider == null) { fallback.handleEvent(sslConnection); return; } String[] protocols = new String[details.length]; final Map protocolMap = new HashMap<>(); for (int i = 0; i < protocols.length; ++i) { protocols[i] = details[i].getProtocol(); protocolMap.put(details[i].getProtocol(), details[i]); } final SSLEngine sslEngine = provider.setProtocols(conduit.getSSLEngine(), protocols); conduit.setSslEngine(sslEngine); final AtomicReference handshakeDone = new AtomicReference<>(false); try { sslConnection.startHandshake(); sslConnection.getHandshakeSetter().set(new ChannelListener() { @Override public void handleEvent(SslConnection channel) { if(handshakeDone.get()) { return; } handshakeDone.set(true); } }); sslConnection.getSourceChannel().getReadSetter().set(new ChannelListener() { @Override public void handleEvent(StreamSourceChannel channel) { String selectedProtocol = provider.getSelectedProtocol(sslEngine); if (selectedProtocol != null) { handleSelected(selectedProtocol); } else { ByteBuffer buf = ByteBuffer.allocate(100); try { int read = channel.read(buf); if (read > 0) { buf.flip(); PushBackStreamSourceConduit pb = new PushBackStreamSourceConduit(sslConnection.getSourceChannel().getConduit()); pb.pushBack(new ImmediatePooled<>(buf)); sslConnection.getSourceChannel().setConduit(pb); } else if (read == -1) { failedListener.failed(new ClosedChannelException()); } selectedProtocol = provider.getSelectedProtocol(sslEngine); if (selectedProtocol != null) { handleSelected(selectedProtocol); } else if (read > 0 || handshakeDone.get()) { sslConnection.getSourceChannel().suspendReads(); fallback.handleEvent(sslConnection); return; } } catch (Throwable t) { IOException e = t instanceof IOException ? (IOException) t : new IOException(t); failedListener.failed(e); } } } private void handleSelected(String selected) { if (selected.isEmpty()) { sslConnection.getSourceChannel().suspendReads(); fallback.handleEvent(sslConnection); return; } else { ALPNClientSelector.ALPNProtocol details = protocolMap.get(selected); if (details == null) { //should never happen sslConnection.getSourceChannel().suspendReads(); fallback.handleEvent(sslConnection); return; } else { sslConnection.getSourceChannel().suspendReads(); details.getSelected().handleEvent(sslConnection); } } } }); sslConnection.getSourceChannel().resumeReads(); } catch (IOException e) { failedListener.failed(e); } catch (Throwable e) { failedListener.failed(new IOException(e)); } } public static class ALPNProtocol { private final ChannelListener selected; private final String protocol; public ALPNProtocol(ChannelListener selected, String protocol) { this.selected = selected; this.protocol = protocol; } public ChannelListener getSelected() { return selected; } public String getProtocol() { return protocol; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/ClientCallback.java000066400000000000000000000021361420065311100273120ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client; import java.io.IOException; /** * @author Emanuel Muckenhuber */ public interface ClientCallback { /** * Invoked when an operation completed. * * @param result the operation result */ void completed(T result); /** * Invoked when the operation failed. * * @param e the exception */ void failed(IOException e); } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/ClientConnection.java000066400000000000000000000107741420065311100277240ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client; import io.undertow.UndertowMessages; import org.xnio.ChannelListener; import org.xnio.Option; import io.undertow.connector.ByteBufferPool; import org.xnio.StreamConnection; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import java.io.IOException; import java.net.SocketAddress; import java.nio.channels.Channel; import java.util.concurrent.TimeUnit; /** * A client connection. This can be used to send requests, or to upgrade the connection. *

* In general these objects are not thread safe, they should only be used by the IO thread * that is responsible for the connection. As a result this client does not provide a mechanism * to perform blocking IO, it is designed for async operation only. * * @author Stuart Douglas */ public interface ClientConnection extends Channel { /** * Sends a client request. The request object should not be modified after it has been submitted to the connection. *

* Request objects can be queued. Once the request is in a state that it is ready to be sent the {@code clientCallback} * is invoked to provide the caller with the {@link ClientExchange} *

* If {@link #isMultiplexingSupported()} returns true then multiple requests may be active at the same time, and a later * request may complete before an earlier one. *

* Note that the request header may not be written out until after the callback has been invoked. This allows the * client to write out a header with a gathering write if the request contains content. * * @param request The request to send. */ void sendRequest(final ClientRequest request, final ClientCallback clientCallback); /** * Upgrade the connection, if the underlying protocol supports it. This should only be called after an upgrade request * has been submitted and the target server has accepted the upgrade. * * @return The resulting StreamConnection */ StreamConnection performUpgrade() throws IOException; /** * * @return The buffer pool used by the client */ ByteBufferPool getBufferPool(); SocketAddress getPeerAddress(); A getPeerAddress(Class type); ChannelListener.Setter getCloseSetter(); SocketAddress getLocalAddress(); A getLocalAddress(Class type); XnioWorker getWorker(); XnioIoThread getIoThread(); boolean isOpen(); boolean supportsOption(Option option); T getOption(Option option) throws IOException; T setOption(Option option, T value) throws IllegalArgumentException, IOException; boolean isUpgraded(); /** * * @return true if this connection support server push */ boolean isPushSupported(); /** * * @return true if this client supports multiplexing */ boolean isMultiplexingSupported(); /** * * @return the statistics information, or null if statistics are not supported or disabled */ ClientStatistics getStatistics(); boolean isUpgradeSupported(); /** * Adds a close listener, than will be invoked with the connection is closed * * @param listener The close listener */ void addCloseListener(ChannelListener listener); /** * * @return true if the underlying protocol supports sending a ping */ default boolean isPingSupported() { return false; } default void sendPing(PingListener listener, long timeout, TimeUnit timeUnit) { listener.failed(UndertowMessages.MESSAGES.pingNotSupported()); } interface PingListener { void acknowledged(); void failed(IOException e); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/ClientExchange.java000066400000000000000000000036271420065311100273460ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client; import io.undertow.util.Attachable; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; /** * @author Stuart Douglas */ public interface ClientExchange extends Attachable { void setResponseListener(final ClientCallback responseListener); void setContinueHandler(final ContinueNotification continueHandler); void setPushHandler(PushCallback pushCallback); /** * Returns the request channel that can be used to send data to the server. * * @return The request channel */ StreamSinkChannel getRequestChannel(); /** * Returns the response channel that can be used to read data from the target server. * * @return The response channel */ StreamSourceChannel getResponseChannel(); ClientRequest getRequest(); /** * * @return The client response, or null if it has not been received yet */ ClientResponse getResponse(); /** * * @return the result of a HTTP 100-continue response */ ClientResponse getContinueResponse(); /** * * @return The underlying connection */ ClientConnection getConnection(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/ClientProvider.java000066400000000000000000000036571420065311100274210ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.ssl.XnioSsl; import java.net.InetSocketAddress; import java.net.URI; import java.util.Set; /** * A client connection provider. This allows the difference between various connection * providers to be abstracted away (HTTP, AJP etc). * * @author Stuart Douglas */ public interface ClientProvider { Set handlesSchemes(); void connect(final ClientCallback listener, final URI uri, final XnioWorker worker, XnioSsl ssl, ByteBufferPool bufferPool, OptionMap options); void connect(final ClientCallback listener, InetSocketAddress bindAddress, final URI uri, final XnioWorker worker, XnioSsl ssl, ByteBufferPool bufferPool, OptionMap options); void connect(final ClientCallback listener, final URI uri, final XnioIoThread ioThread, XnioSsl ssl, ByteBufferPool bufferPool, OptionMap options); void connect(final ClientCallback listener, InetSocketAddress bindAddress, final URI uri, final XnioIoThread ioThread, XnioSsl ssl, ByteBufferPool bufferPool, OptionMap options); } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/ClientRequest.java000066400000000000000000000046041420065311100272500ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client; import io.undertow.util.AbstractAttachable; import io.undertow.util.HeaderMap; import io.undertow.util.HttpString; import io.undertow.util.Methods; import io.undertow.util.Protocols; /** * A client request. This class should not be modified once it has been submitted to the {@link ClientConnection}. * * This class only represents the HTTP header, it does not represent an entity body. If the request needs an entity * body then this must be specified by either setting a Content-Length or Transfer-Encoding header, otherwise * the client will assume that the body is empty. * * @author Stuart Douglas */ public final class ClientRequest extends AbstractAttachable { private final HeaderMap requestHeaders = new HeaderMap(); private String path = "/"; private HttpString method = Methods.GET; private HttpString protocol = Protocols.HTTP_1_1; public HeaderMap getRequestHeaders() { return requestHeaders; } public String getPath() { return path; } public HttpString getMethod() { return method; } public HttpString getProtocol() { return protocol; } public ClientRequest setPath(String path) { this.path = path; return this; } public ClientRequest setMethod(HttpString method) { this.method = method; return this; } public ClientRequest setProtocol(HttpString protocol) { this.protocol = protocol; return this; } @Override public String toString() { return "ClientRequest{path='" + path + '\'' + ", method=" + method + ", protocol=" + protocol + '}'; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/ClientResponse.java000066400000000000000000000044371420065311100274220ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client; import io.undertow.util.AbstractAttachable; import io.undertow.util.HeaderMap; import io.undertow.util.HttpString; /** * A client response. This just contains the parsed response header, the response body * can be read from the {@link ClientExchange}. * * @author Stuart Douglas */ public final class ClientResponse extends AbstractAttachable { private final HeaderMap responseHeaders; private final int responseCode; private final String status; private final HttpString protocol; public ClientResponse(int responseCode, String status, HttpString protocol) { this.responseCode = responseCode; this.status = status; this.protocol = protocol; this.responseHeaders = new HeaderMap(); } public ClientResponse(int responseCode, String status, HttpString protocol, HeaderMap headers) { this.responseCode = responseCode; this.status = status; this.protocol = protocol; this.responseHeaders = headers; } public HeaderMap getResponseHeaders() { return responseHeaders; } public HttpString getProtocol() { return protocol; } public int getResponseCode() { return responseCode; } public String getStatus() { return status; } @Override public String toString() { return "ClientResponse{" + "responseHeaders=" + responseHeaders + ", responseCode=" + responseCode + ", status='" + status + '\'' + ", protocol=" + protocol + '}'; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/ClientStatistics.java000066400000000000000000000017051420065311100277510ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client; /** * Returns statistics about the Undertow client connection * * @author Stuart Douglas */ public interface ClientStatistics { long getRequests(); long getRead(); long getWritten(); void reset(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/ContinueNotification.java000066400000000000000000000017071420065311100306150ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client; /** * Callback class that provides a notification of a HTTP 100 Continue response in the client. * * @author Stuart Douglas */ public interface ContinueNotification { void handleContinue(ClientExchange exchange); } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/ProxiedRequestAttachments.java000066400000000000000000000042421420065311100316360ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client; import io.undertow.util.AttachmentKey; /** * Additional attachments that are specific to requests that are being proxied from one server to another * * @author Stuart Douglas */ public class ProxiedRequestAttachments { public static final AttachmentKey REMOTE_ADDRESS = AttachmentKey.create(String.class); public static final AttachmentKey REMOTE_HOST = AttachmentKey.create(String.class); public static final AttachmentKey SERVER_NAME = AttachmentKey.create(String.class); public static final AttachmentKey SERVER_PORT = AttachmentKey.create(Integer.class); public static final AttachmentKey IS_SSL = AttachmentKey.create(Boolean.class); public static final AttachmentKey REMOTE_USER = AttachmentKey.create(String.class); public static final AttachmentKey AUTH_TYPE = AttachmentKey.create(String.class); public static final AttachmentKey ROUTE = AttachmentKey.create(String.class); public static final AttachmentKey SSL_CERT = AttachmentKey.create(String.class); public static final AttachmentKey SSL_CYPHER = AttachmentKey.create(String.class); public static final AttachmentKey SSL_SESSION_ID = AttachmentKey.create(byte[].class); public static final AttachmentKey SSL_KEY_SIZE = AttachmentKey.create(Integer.class); public static final AttachmentKey SECRET = AttachmentKey.create(String.class); } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/PushCallback.java000066400000000000000000000023301420065311100270070ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client; /** * @author Stuart Douglas */ public interface PushCallback { /** * Handles a server push. If the push cannot be handled for some reason, this method * should return false and the underlying * @param originalRequest The request that initiated the push * @param pushedRequest The pushed request * @return false if the server wants the push to be rejected */ boolean handlePush(ClientExchange originalRequest, ClientExchange pushedRequest); } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/UndertowClient.java000066400000000000000000000176051420065311100274340ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client; import static java.security.AccessController.doPrivileged; import org.xnio.FutureResult; import org.xnio.IoFuture; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.ssl.XnioSsl; import java.io.IOException; import java.net.InetSocketAddress; import java.net.URI; import java.security.PrivilegedAction; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.ServiceLoader; /** * Undertow client class. This class loads {@link ClientProvider} implementations, and uses them to * create connections to a target. * * @author Stuart Douglas */ public final class UndertowClient { private final Map clientProviders; private static final UndertowClient INSTANCE = new UndertowClient(); private UndertowClient() { this(UndertowClient.class.getClassLoader()); } private UndertowClient(final ClassLoader classLoader) { ServiceLoader providers = doPrivileged((PrivilegedAction>) () -> ServiceLoader.load(ClientProvider.class, classLoader)); final Map map = new HashMap<>(); for (ClientProvider provider : providers) { for (String scheme : provider.handlesSchemes()) { map.put(scheme, provider); } } this.clientProviders = Collections.unmodifiableMap(map); } public IoFuture connect(final URI uri, final XnioWorker worker, ByteBufferPool bufferPool, OptionMap options) { return connect(uri, worker, null, bufferPool, options); } public IoFuture connect(InetSocketAddress bindAddress, final URI uri, final XnioWorker worker, ByteBufferPool bufferPool, OptionMap options) { return connect(bindAddress, uri, worker, null, bufferPool, options); } public IoFuture connect(final URI uri, final XnioWorker worker, XnioSsl ssl, ByteBufferPool bufferPool, OptionMap options) { return connect((InetSocketAddress) null, uri, worker, ssl, bufferPool, options); } public IoFuture connect(InetSocketAddress bindAddress, final URI uri, final XnioWorker worker, XnioSsl ssl, ByteBufferPool bufferPool, OptionMap options) { ClientProvider provider = getClientProvider(uri); final FutureResult result = new FutureResult<>(); provider.connect(new ClientCallback() { @Override public void completed(ClientConnection r) { result.setResult(r); } @Override public void failed(IOException e) { result.setException(e); } }, bindAddress, uri, worker, ssl, bufferPool, options); return result.getIoFuture(); } public IoFuture connect(final URI uri, final XnioIoThread ioThread, ByteBufferPool bufferPool, OptionMap options) { return connect((InetSocketAddress) null, uri, ioThread, null, bufferPool, options); } public IoFuture connect(InetSocketAddress bindAddress, final URI uri, final XnioIoThread ioThread, ByteBufferPool bufferPool, OptionMap options) { return connect(bindAddress, uri, ioThread, null, bufferPool, options); } public IoFuture connect(final URI uri, final XnioIoThread ioThread, XnioSsl ssl, ByteBufferPool bufferPool, OptionMap options) { return connect((InetSocketAddress) null, uri, ioThread, ssl, bufferPool, options); } public IoFuture connect(InetSocketAddress bindAddress, final URI uri, final XnioIoThread ioThread, XnioSsl ssl, ByteBufferPool bufferPool, OptionMap options) { ClientProvider provider = getClientProvider(uri); final FutureResult result = new FutureResult<>(); provider.connect(new ClientCallback() { @Override public void completed(ClientConnection r) { result.setResult(r); } @Override public void failed(IOException e) { result.setException(e); } }, bindAddress, uri, ioThread, ssl, bufferPool, options); return result.getIoFuture(); } public void connect(final ClientCallback listener, final URI uri, final XnioWorker worker, ByteBufferPool bufferPool, OptionMap options) { connect(listener, uri, worker, null, bufferPool, options); } public void connect(final ClientCallback listener, InetSocketAddress bindAddress, final URI uri, final XnioWorker worker, ByteBufferPool bufferPool, OptionMap options) { connect(listener, bindAddress, uri, worker, null, bufferPool, options); } public void connect(final ClientCallback listener, final URI uri, final XnioWorker worker, XnioSsl ssl, ByteBufferPool bufferPool, OptionMap options) { ClientProvider provider = getClientProvider(uri); provider.connect(listener, uri, worker, ssl, bufferPool, options); } public void connect(final ClientCallback listener, InetSocketAddress bindAddress, final URI uri, final XnioWorker worker, XnioSsl ssl, ByteBufferPool bufferPool, OptionMap options) { ClientProvider provider = getClientProvider(uri); provider.connect(listener, bindAddress, uri, worker, ssl, bufferPool, options); } public void connect(final ClientCallback listener, final URI uri, final XnioIoThread ioThread, ByteBufferPool bufferPool, OptionMap options) { connect(listener, uri, ioThread, null, bufferPool, options); } public void connect(final ClientCallback listener, InetSocketAddress bindAddress, final URI uri, final XnioIoThread ioThread, ByteBufferPool bufferPool, OptionMap options) { connect(listener, bindAddress, uri, ioThread, null, bufferPool, options); } public void connect(final ClientCallback listener, final URI uri, final XnioIoThread ioThread, XnioSsl ssl, ByteBufferPool bufferPool, OptionMap options) { ClientProvider provider = getClientProvider(uri); provider.connect(listener, uri, ioThread, ssl, bufferPool, options); } public void connect(final ClientCallback listener, InetSocketAddress bindAddress, final URI uri, final XnioIoThread ioThread, XnioSsl ssl, ByteBufferPool bufferPool, OptionMap options) { ClientProvider provider = getClientProvider(uri); provider.connect(listener, bindAddress, uri, ioThread, ssl, bufferPool, options); } private ClientProvider getClientProvider(URI uri) { ClientProvider provider = clientProviders.get(uri.getScheme()); if (provider == null) { throw UndertowClientMessages.MESSAGES.unknownScheme(uri); } return provider; } public static UndertowClient getInstance() { return INSTANCE; } public static UndertowClient getInstance(final ClassLoader classLoader) { return new UndertowClient(classLoader); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/UndertowClientMessages.java000066400000000000000000000052531420065311100311200ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client; import io.undertow.util.HttpString; import org.jboss.logging.Messages; import org.jboss.logging.annotations.Message; import org.jboss.logging.annotations.MessageBundle; import java.io.IOException; import java.net.URI; /** * starting from 1000 * * @author Emanuel Muckenhuber */ @MessageBundle(projectCode = "UT") public interface UndertowClientMessages { UndertowClientMessages MESSAGES = Messages.getBundle(UndertowClientMessages.class); // 1000 @Message(id = 1000, value = "Connection closed") String connectionClosed(); @Message(id = 1001, value = "Request already written") IllegalStateException requestAlreadyWritten(); // 1020 @Message(id = 1020, value = "Failed to upgrade channel due to response %s (%s)") String failedToUpgradeChannel(final int responseCode, String reason); // 1030 @Message(id = 1030, value = "invalid content length %d") IllegalArgumentException illegalContentLength(long length); @Message(id = 1031, value = "Unknown scheme in URI %s") IllegalArgumentException unknownScheme(URI uri); @Message(id = 1032, value = "Unknown transfer encoding %s") IOException unknownTransferEncoding(String transferEncodingString); @Message(id = 1033, value = "Invalid connection state") IOException invalidConnectionState(); @Message(id = 1034, value = "Unknown AJP packet type %s") IOException unknownAjpMessageType(byte packetType); @Message(id = 1035, value = "Unknown method type for AJP request %s") IOException unknownMethod(HttpString method); @Message(id = 1036, value = "Data still remaining in chunk %s") IOException dataStillRemainingInChunk(long remaining); @Message(id = 1037, value = "Wrong magic number, expected %s, actual %s") IOException wrongMagicNumber(String expected, String actual); @Message(id = 1038, value = "Received invalid AJP chunk %s with response already complete") IOException receivedInvalidChunk(byte prefix); } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/ajp/000077500000000000000000000000001420065311100243645ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/client/ajp/AjpClientConnection.java000066400000000000000000000337051420065311100311300ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client.ajp; import static io.undertow.util.Headers.CLOSE; import static io.undertow.util.Headers.CONNECTION; import static io.undertow.util.Headers.CONTENT_LENGTH; import static io.undertow.util.Headers.TRANSFER_ENCODING; import static io.undertow.util.Headers.UPGRADE; import static org.xnio.Bits.anyAreSet; import static org.xnio.IoUtils.safeClose; import java.io.Closeable; import java.io.IOException; import java.net.SocketAddress; import java.nio.channels.ClosedChannelException; import java.util.ArrayDeque; import java.util.Deque; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import io.undertow.client.ClientStatistics; import org.jboss.logging.Logger; import org.xnio.ChannelExceptionHandler; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.Option; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import org.xnio.StreamConnection; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.Channels; import org.xnio.channels.StreamSinkChannel; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientExchange; import io.undertow.client.ClientRequest; import io.undertow.client.ClientResponse; import io.undertow.client.UndertowClientMessages; import io.undertow.protocols.ajp.AbstractAjpClientStreamSourceChannel; import io.undertow.protocols.ajp.AjpClientChannel; import io.undertow.protocols.ajp.AjpClientRequestClientStreamSinkChannel; import io.undertow.protocols.ajp.AjpClientResponseStreamSourceChannel; import io.undertow.util.AbstractAttachable; import io.undertow.util.Protocols; /** * @author David M. Lloyd */ class AjpClientConnection extends AbstractAttachable implements Closeable, ClientConnection { public final ChannelListener requestFinishListener = new ChannelListener() { @Override public void handleEvent(AjpClientRequestClientStreamSinkChannel channel) { if(currentRequest != null) { currentRequest.terminateRequest(); } } }; public final ChannelListener responseFinishedListener = new ChannelListener() { @Override public void handleEvent(AjpClientResponseStreamSourceChannel channel) { if(currentRequest != null) { currentRequest.terminateResponse(); } } }; private static final Logger log = Logger.getLogger(AjpClientConnection.class); private final Deque pendingQueue = new ArrayDeque<>(); private AjpClientExchange currentRequest; private final OptionMap options; private final AjpClientChannel connection; private final ByteBufferPool bufferPool; private static final int UPGRADED = 1 << 28; private static final int UPGRADE_REQUESTED = 1 << 29; private static final int CLOSE_REQ = 1 << 30; private static final int CLOSED = 1 << 31; private int state; private final ChannelListener.SimpleSetter closeSetter = new ChannelListener.SimpleSetter<>(); private final ClientStatistics clientStatistics; private final List> closeListeners = new CopyOnWriteArrayList<>(); AjpClientConnection(final AjpClientChannel connection, final OptionMap options, final ByteBufferPool bufferPool, ClientStatistics clientStatistics) { this.clientStatistics = clientStatistics; this.options = options; this.connection = connection; this.bufferPool = bufferPool; connection.addCloseTask(new ChannelListener() { @Override public void handleEvent(AjpClientChannel channel) { log.debugf("connection to %s closed", getPeerAddress()); AjpClientConnection.this.state |= CLOSED; ChannelListeners.invokeChannelListener(AjpClientConnection.this, closeSetter.get()); for(ChannelListener listener : closeListeners) { listener.handleEvent(AjpClientConnection.this); } AjpClientExchange pending = pendingQueue.poll(); while (pending != null) { pending.setFailed(new ClosedChannelException()); pending = pendingQueue.poll(); } if(currentRequest != null) { currentRequest.setFailed(new ClosedChannelException()); currentRequest = null; } } }); connection.getReceiveSetter().set(new ClientReceiveListener()); connection.resumeReceives(); } @Override public ByteBufferPool getBufferPool() { return bufferPool; } @Override public SocketAddress getPeerAddress() { return connection.getPeerAddress(); } @Override public A getPeerAddress(Class type) { return connection.getPeerAddress(type); } @Override public ChannelListener.Setter getCloseSetter() { return closeSetter; } @Override public SocketAddress getLocalAddress() { return connection.getLocalAddress(); } @Override public A getLocalAddress(Class type) { return connection.getLocalAddress(type); } @Override public XnioWorker getWorker() { return connection.getWorker(); } @Override public XnioIoThread getIoThread() { return connection.getIoThread(); } @Override public boolean isOpen() { return connection.isOpen(); } @Override public boolean supportsOption(Option option) { return connection.supportsOption(option); } @Override public T getOption(Option option) throws IOException { return connection.getOption(option); } @Override public T setOption(Option option, T value) throws IllegalArgumentException, IOException { return connection.setOption(option, value); } @Override public boolean isUpgraded() { return anyAreSet(state, UPGRADE_REQUESTED | UPGRADED); } @Override public boolean isPushSupported() { return false; } @Override public boolean isMultiplexingSupported() { return false; } @Override public ClientStatistics getStatistics() { return clientStatistics; } @Override public boolean isUpgradeSupported() { return false; } @Override public void addCloseListener(ChannelListener listener) { closeListeners.add(listener); } @Override public void sendRequest(final ClientRequest request, final ClientCallback clientCallback) { if (anyAreSet(state, UPGRADE_REQUESTED | UPGRADED | CLOSE_REQ | CLOSED)) { clientCallback.failed(UndertowClientMessages.MESSAGES.invalidConnectionState()); return; } final AjpClientExchange AjpClientExchange = new AjpClientExchange(clientCallback, request, this); if (currentRequest == null) { initiateRequest(AjpClientExchange); } else { pendingQueue.add(AjpClientExchange); } } @Override public boolean isPingSupported() { return true; } @Override public void sendPing(PingListener listener, long timeout, TimeUnit timeUnit) { connection.sendPing(listener, timeout, timeUnit); } private void initiateRequest(AjpClientExchange AjpClientExchange) { currentRequest = AjpClientExchange; ClientRequest request = AjpClientExchange.getRequest(); String connectionString = request.getRequestHeaders().getFirst(CONNECTION); if (connectionString != null) { if (CLOSE.equalToString(connectionString)) { state |= CLOSE_REQ; } } else if (request.getProtocol() != Protocols.HTTP_1_1) { state |= CLOSE_REQ; } if (request.getRequestHeaders().contains(UPGRADE)) { state |= UPGRADE_REQUESTED; } long length = 0; String fixedLengthString = request.getRequestHeaders().getFirst(CONTENT_LENGTH); String transferEncodingString = request.getRequestHeaders().getLast(TRANSFER_ENCODING); if (fixedLengthString != null) { length = Long.parseLong(fixedLengthString); } else if (transferEncodingString != null) { length = -1; } AjpClientRequestClientStreamSinkChannel sinkChannel = connection.sendRequest(request.getMethod(), request.getPath(), request.getProtocol(), request.getRequestHeaders(), request, requestFinishListener); currentRequest.setRequestChannel(sinkChannel); AjpClientExchange.invokeReadReadyCallback(AjpClientExchange); if (length == 0) { //if there is no content we flush the response channel. //otherwise it is up to the user try { sinkChannel.shutdownWrites(); if (!sinkChannel.flush()) { handleFailedFlush(sinkChannel); } } catch (Throwable t) { handleError((t instanceof IOException) ? (IOException) t : new IOException(t)); } } } private void handleFailedFlush(AjpClientRequestClientStreamSinkChannel sinkChannel) { sinkChannel.getWriteSetter().set(ChannelListeners.flushingChannelListener(null, new ChannelExceptionHandler() { @Override public void handleException(StreamSinkChannel channel, IOException exception) { handleError(exception); } })); sinkChannel.resumeWrites(); } private void handleError(IOException exception) { currentRequest.setFailed(exception); safeClose(connection); } public StreamConnection performUpgrade() throws IOException { throw UndertowMessages.MESSAGES.upgradeNotSupported(); } public void close() throws IOException { log.debugf("close called on connection to %s", getPeerAddress()); if (anyAreSet(state, CLOSED)) { return; } state |= CLOSED | CLOSE_REQ; connection.close(); } /** * Notification that the current request is finished */ public void requestDone() { currentRequest = null; if (anyAreSet(state, CLOSE_REQ)) { safeClose(connection); } else if (anyAreSet(state, UPGRADE_REQUESTED)) { safeClose(connection); //we don't support upgrade, just close the connection to be safe return; } AjpClientExchange next = pendingQueue.poll(); if (next != null) { initiateRequest(next); } } public void requestClose() { state |= CLOSE_REQ; } class ClientReceiveListener implements ChannelListener { public void handleEvent(AjpClientChannel channel) { try { AbstractAjpClientStreamSourceChannel result = channel.receive(); if(result == null) { if(!channel.isOpen()) { //we execute this in a runnable //as there may be close/data frames that need to be processed getIoThread().execute(new Runnable() { @Override public void run() { if(currentRequest != null) { currentRequest.setFailed(new ClosedChannelException()); } } }); } return; } if(result instanceof AjpClientResponseStreamSourceChannel) { AjpClientResponseStreamSourceChannel response = (AjpClientResponseStreamSourceChannel) result; response.setFinishListener(responseFinishedListener); ClientResponse cr = new ClientResponse(response.getStatusCode(), response.getReasonPhrase(), currentRequest.getRequest().getProtocol(), response.getHeaders()); if (response.getStatusCode() == 100) { currentRequest.setContinueResponse(cr); } else { currentRequest.setResponseChannel(response); currentRequest.setResponse(cr); } } else { //TODO: ping, pong ETC Channels.drain(result, Long.MAX_VALUE); } } catch (Throwable e) { UndertowLogger.CLIENT_LOGGER.exceptionProcessingRequest(e); safeClose(connection); if(currentRequest != null) { currentRequest.setFailed(e instanceof IOException ? (IOException) e : new IOException(e)); } } } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/ajp/AjpClientExchange.java000066400000000000000000000144011420065311100305430ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client.ajp; import io.undertow.channels.DetachableStreamSinkChannel; import io.undertow.channels.DetachableStreamSourceChannel; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientExchange; import io.undertow.client.ClientRequest; import io.undertow.client.ClientResponse; import io.undertow.client.ContinueNotification; import io.undertow.client.PushCallback; import io.undertow.protocols.ajp.AjpClientRequestClientStreamSinkChannel; import io.undertow.protocols.ajp.AjpClientResponseStreamSourceChannel; import io.undertow.util.AbstractAttachable; import io.undertow.util.Headers; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; import java.io.IOException; import static org.xnio.Bits.anyAreSet; /** * @author Stuart Douglas */ class AjpClientExchange extends AbstractAttachable implements ClientExchange { private final ClientRequest request; private final boolean requiresContinue; private final AjpClientConnection clientConnection; private ClientCallback responseCallback; private ClientCallback readyCallback; private ContinueNotification continueNotification; private ClientResponse response; private ClientResponse continueResponse; private IOException failedReason; private AjpClientResponseStreamSourceChannel responseChannel; private AjpClientRequestClientStreamSinkChannel requestChannel; private int state = 0; private static final int REQUEST_TERMINATED = 1; private static final int RESPONSE_TERMINATED = 1 << 1; AjpClientExchange(ClientCallback readyCallback, ClientRequest request, AjpClientConnection clientConnection) { this.readyCallback = readyCallback; this.request = request; this.clientConnection = clientConnection; boolean reqContinue = false; if (request.getRequestHeaders().contains(Headers.EXPECT)) { for (String header : request.getRequestHeaders().get(Headers.EXPECT)) { if (header.equals("100-continue")) { reqContinue = true; } } } this.requiresContinue = reqContinue; } void terminateRequest() { state |= REQUEST_TERMINATED; if(!clientConnection.isOpen()) { state |= RESPONSE_TERMINATED; } if (anyAreSet(state, RESPONSE_TERMINATED)) { clientConnection.requestDone(); } } void terminateResponse() { state |= RESPONSE_TERMINATED; if(!clientConnection.isOpen()) { state |= REQUEST_TERMINATED; } if (anyAreSet(state, REQUEST_TERMINATED)) { clientConnection.requestDone(); } } public boolean isRequiresContinue() { return requiresContinue; } void setContinueResponse(ClientResponse response) { this.continueResponse = response; if (continueNotification != null) { this.continueNotification.handleContinue(this); } } void setResponse(ClientResponse response) { this.response = response; if (responseCallback != null) { this.responseCallback.completed(this); } } @Override public void setResponseListener(ClientCallback listener) { this.responseCallback = listener; if (listener != null) { if (failedReason != null) { listener.failed(failedReason); } else if (response != null) { listener.completed(this); } } } @Override public void setContinueHandler(ContinueNotification continueHandler) { this.continueNotification = continueHandler; } @Override public void setPushHandler(PushCallback pushCallback) { } void setFailed(IOException e) { this.failedReason = e; if (readyCallback != null) { readyCallback.failed(e); readyCallback = null; } if (responseCallback != null) { responseCallback.failed(e); responseCallback = null; } } @Override public StreamSinkChannel getRequestChannel() { return new DetachableStreamSinkChannel(requestChannel) { @Override protected boolean isFinished() { return anyAreSet(state, REQUEST_TERMINATED); } }; } @Override public StreamSourceChannel getResponseChannel() { return new DetachableStreamSourceChannel(responseChannel) { @Override protected boolean isFinished() { return anyAreSet(state, RESPONSE_TERMINATED); } }; } @Override public ClientRequest getRequest() { return request; } @Override public ClientResponse getResponse() { return response; } @Override public ClientResponse getContinueResponse() { return continueResponse; } @Override public ClientConnection getConnection() { return clientConnection; } void setResponseChannel(AjpClientResponseStreamSourceChannel responseChannel) { this.responseChannel = responseChannel; } void setRequestChannel(AjpClientRequestClientStreamSinkChannel requestChannel) { this.requestChannel = requestChannel; } void invokeReadReadyCallback(final ClientExchange result) { if(readyCallback != null) { readyCallback.completed(result); readyCallback = null; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/ajp/AjpClientProvider.java000066400000000000000000000154201420065311100306150ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client.ajp; import io.undertow.UndertowOptions; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientProvider; import io.undertow.client.ClientStatistics; import io.undertow.conduits.ByteActivityCallback; import io.undertow.conduits.BytesReceivedStreamSourceConduit; import io.undertow.conduits.BytesSentStreamSinkConduit; import io.undertow.protocols.ajp.AjpClientChannel; import org.xnio.ChannelListener; import org.xnio.IoFuture; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import org.xnio.StreamConnection; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.ssl.XnioSsl; import java.net.InetSocketAddress; import java.net.URI; import java.util.Arrays; import java.util.HashSet; import java.util.Set; /** * @author Stuart Douglas */ public class AjpClientProvider implements ClientProvider { @Override public Set handlesSchemes() { return new HashSet<>(Arrays.asList(new String[]{"ajp"})); } @Override public void connect(final ClientCallback listener, final URI uri, final XnioWorker worker, final XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap options) { connect(listener, null, uri, worker, ssl, bufferPool, options); } @Override public void connect(final ClientCallback listener, final URI uri, final XnioIoThread ioThread, final XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap options) { connect(listener, null, uri, ioThread, ssl, bufferPool, options); } @Override public void connect(final ClientCallback listener, InetSocketAddress bindAddress, final URI uri, final XnioWorker worker, final XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap options) { ChannelListener openListener = new ChannelListener() { @Override public void handleEvent(StreamConnection connection) { handleConnected(connection, listener, uri, ssl, bufferPool, options); } }; IoFuture.Notifier notifier = new IoFuture.Notifier() { @Override public void notify(IoFuture ioFuture, Object o) { if (ioFuture.getStatus() == IoFuture.Status.FAILED) { listener.failed(ioFuture.getException()); } } }; if(bindAddress == null) { worker.openStreamConnection(new InetSocketAddress(uri.getHost(), uri.getPort() == -1 ? 8009 : uri.getPort()), openListener, options).addNotifier(notifier, null); } else { worker.openStreamConnection(bindAddress, new InetSocketAddress(uri.getHost(), uri.getPort() == -1 ? 8009 : uri.getPort()), openListener, null, options).addNotifier(notifier, null); } } @Override public void connect(final ClientCallback listener, InetSocketAddress bindAddress,final URI uri, final XnioIoThread ioThread, final XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap options) { ChannelListener openListener = new ChannelListener() { @Override public void handleEvent(StreamConnection connection) { handleConnected(connection, listener, uri, ssl, bufferPool, options); } }; IoFuture.Notifier notifier = new IoFuture.Notifier() { @Override public void notify(IoFuture ioFuture, Object o) { if (ioFuture.getStatus() == IoFuture.Status.FAILED) { listener.failed(ioFuture.getException()); } } }; if(bindAddress == null) { ioThread.openStreamConnection(new InetSocketAddress(uri.getHost(), uri.getPort() == -1 ? 8009 : uri.getPort()), openListener, options).addNotifier(notifier, null); } else { ioThread.openStreamConnection(bindAddress, new InetSocketAddress(uri.getHost(), uri.getPort() == -1 ? 8009 : uri.getPort()), openListener, null, options).addNotifier(notifier, null); } } private void handleConnected(StreamConnection connection, ClientCallback listener, URI uri, XnioSsl ssl, ByteBufferPool bufferPool, OptionMap options) { final ClientStatisticsImpl clientStatistics; //first we set up statistics, if required if (options.get(UndertowOptions.ENABLE_STATISTICS, false)) { clientStatistics = new ClientStatisticsImpl(); connection.getSinkChannel().setConduit(new BytesSentStreamSinkConduit(connection.getSinkChannel().getConduit(), new ByteActivityCallback() { @Override public void activity(long bytes) { clientStatistics.written += bytes; } })); connection.getSourceChannel().setConduit(new BytesReceivedStreamSourceConduit(connection.getSourceChannel().getConduit(), new ByteActivityCallback() { @Override public void activity(long bytes) { clientStatistics.read += bytes; } })); } else { clientStatistics = null; } listener.completed(new AjpClientConnection(new AjpClientChannel(connection, bufferPool, options), options, bufferPool, clientStatistics)); } private static class ClientStatisticsImpl implements ClientStatistics { private long requestCount, read, written; @Override public long getRequests() { return requestCount; } @Override public long getRead() { return read; } @Override public long getWritten() { return written; } @Override public void reset() { read = 0; written = 0; requestCount = 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/http/000077500000000000000000000000001420065311100245715ustar00rootroot00000000000000ClientFixedLengthStreamSinkConduit.java000066400000000000000000000032711420065311100342470ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/client/http/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client.http; import io.undertow.conduits.AbstractFixedLengthStreamSinkConduit; import org.xnio.conduits.StreamSinkConduit; class ClientFixedLengthStreamSinkConduit extends AbstractFixedLengthStreamSinkConduit { private final HttpClientExchange exchange; /** * Construct a new instance. * * @param next the next channel * @param contentLength the content length * @param configurable {@code true} if this instance should pass configuration to the next * @param propagateClose {@code true} if this instance should pass close to the next * @param exchange */ ClientFixedLengthStreamSinkConduit(StreamSinkConduit next, long contentLength, boolean configurable, boolean propagateClose, HttpClientExchange exchange) { super(next, contentLength, configurable, propagateClose); this.exchange = exchange; } @Override protected void channelFinished() { exchange.terminateRequest(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/http/HttpClientConnection.java000066400000000000000000000774541420065311100315530ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client.http; import io.undertow.UndertowLogger; import io.undertow.UndertowOptions; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientExchange; import io.undertow.client.ClientRequest; import io.undertow.client.ClientResponse; import io.undertow.client.ClientStatistics; import io.undertow.client.UndertowClientMessages; import io.undertow.client.http2.Http2ClearClientProvider; import io.undertow.client.http2.Http2ClientConnection; import io.undertow.conduits.ByteActivityCallback; import io.undertow.conduits.BytesReceivedStreamSourceConduit; import io.undertow.conduits.BytesSentStreamSinkConduit; import io.undertow.conduits.ChunkedStreamSinkConduit; import io.undertow.conduits.ChunkedStreamSourceConduit; import io.undertow.conduits.ConduitListener; import io.undertow.conduits.FinishableStreamSourceConduit; import io.undertow.conduits.FixedLengthStreamSourceConduit; import io.undertow.conduits.ReadTimeoutStreamSourceConduit; import io.undertow.conduits.WriteTimeoutStreamSinkConduit; import io.undertow.protocols.http2.Http2Channel; import io.undertow.server.Connectors; import io.undertow.server.protocol.http.HttpContinue; import io.undertow.server.protocol.http.HttpOpenListener; import io.undertow.util.AbstractAttachable; import io.undertow.util.ConnectionUtils; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.Methods; import io.undertow.util.PooledAdaptor; import io.undertow.util.Protocols; import io.undertow.util.StatusCodes; import org.jboss.logging.Logger; import org.xnio.ChannelExceptionHandler; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.Option; import org.xnio.OptionMap; import org.xnio.Options; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import org.xnio.StreamConnection; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.ConduitStreamSinkChannel; import org.xnio.conduits.ConduitStreamSourceChannel; import org.xnio.conduits.PushBackStreamSourceConduit; import org.xnio.conduits.StreamSinkConduit; import org.xnio.conduits.StreamSourceConduit; import org.xnio.ssl.SslConnection; import java.io.Closeable; import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.util.ArrayDeque; import java.util.Deque; import java.util.List; import java.util.Locale; import java.util.concurrent.CopyOnWriteArrayList; import static io.undertow.client.UndertowClientMessages.MESSAGES; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.allAreSet; import static org.xnio.Bits.anyAreSet; import static org.xnio.IoUtils.safeClose; /** * @author David M. Lloyd */ class HttpClientConnection extends AbstractAttachable implements Closeable, ClientConnection { public final ConduitListener requestFinishListener = new ConduitListener() { @Override public void handleEvent(StreamSinkConduit channel) { if(currentRequest != null) { currentRequest.terminateRequest(); } } }; public final ConduitListener responseFinishedListener = new ConduitListener() { @Override public void handleEvent(StreamSourceConduit channel) { if(currentRequest != null) { currentRequest.terminateResponse(); } } }; private static final Logger log = Logger.getLogger(HttpClientConnection.class); private final Deque pendingQueue = new ArrayDeque<>(); private HttpClientExchange currentRequest; private HttpResponseBuilder pendingResponse; private final OptionMap options; private final StreamConnection connection; private final PushBackStreamSourceConduit pushBackStreamSourceConduit; private final ClientReadListener clientReadListener = new ClientReadListener(); private final ByteBufferPool bufferPool; private PooledByteBuffer pooledBuffer; private final StreamSinkConduit originalSinkConduit; private static final int UPGRADED = 1 << 28; private static final int UPGRADE_REQUESTED = 1 << 29; private static final int CLOSE_REQ = 1 << 30; private static final int CLOSED = 1 << 31; private int state; private final ChannelListener.SimpleSetter closeSetter = new ChannelListener.SimpleSetter<>(); private final ClientStatistics clientStatistics; private int requestCount; private int read, written; private boolean http2Tried = false; private boolean http2UpgradeReceived = false; /** * The actual connection if this has been upgraded to h2c */ private ClientConnection http2Delegate; private final List> closeListeners = new CopyOnWriteArrayList<>(); HttpClientConnection(final StreamConnection connection, final OptionMap options, final ByteBufferPool bufferPool) { //first we set up statistics, if required if(options.get(UndertowOptions.ENABLE_STATISTICS, false)) { clientStatistics = new ClientStatisticsImpl(); connection.getSinkChannel().setConduit(new BytesSentStreamSinkConduit(connection.getSinkChannel().getConduit(), new ByteActivityCallback() { @Override public void activity(long bytes) { written+=bytes; } })); connection.getSourceChannel().setConduit(new BytesReceivedStreamSourceConduit(connection.getSourceChannel().getConduit(), new ByteActivityCallback() { @Override public void activity(long bytes) { read+=bytes; } })); } else { clientStatistics = null; } this.options = options; this.connection = connection; this.pushBackStreamSourceConduit = new PushBackStreamSourceConduit(connection.getSourceChannel().getConduit()); this.connection.getSourceChannel().setConduit(pushBackStreamSourceConduit); this.bufferPool = bufferPool; this.originalSinkConduit = connection.getSinkChannel().getConduit(); connection.getCloseSetter().set(new ChannelListener() { public void handleEvent(StreamConnection channel) { log.debugf("connection to %s closed", getPeerAddress()); HttpClientConnection.this.state |= CLOSED; ChannelListeners.invokeChannelListener(HttpClientConnection.this, closeSetter.get()); try { if (pooledBuffer != null) { pooledBuffer.close(); } } catch (Throwable ignored){} for(ChannelListener listener : closeListeners) { listener.handleEvent(HttpClientConnection.this); } HttpClientExchange pending = pendingQueue.poll(); while (pending != null) { pending.setFailed(new ClosedChannelException()); pending = pendingQueue.poll(); } if(currentRequest != null) { currentRequest.setFailed(new ClosedChannelException()); currentRequest = null; pendingResponse = null; } } }); //we resume reads, so if the target goes away we get notified connection.getSourceChannel().setReadListener(clientReadListener); connection.getSourceChannel().resumeReads(); } @Override public ByteBufferPool getBufferPool() { return bufferPool; } @Override public SocketAddress getPeerAddress() { return connection.getPeerAddress(); } StreamConnection getConnection() { return connection; } @Override public A getPeerAddress(Class type) { return connection.getPeerAddress(type); } @Override public ChannelListener.Setter getCloseSetter() { return closeSetter; } @Override public SocketAddress getLocalAddress() { return connection.getLocalAddress(); } @Override public A getLocalAddress(Class type) { return connection.getLocalAddress(type); } @Override public XnioWorker getWorker() { return connection.getWorker(); } @Override public XnioIoThread getIoThread() { return connection.getIoThread(); } @Override public boolean isOpen() { if(http2Delegate != null) { return http2Delegate.isOpen(); } return connection.isOpen() && allAreClear(state, CLOSE_REQ | CLOSED); } @Override public boolean supportsOption(Option option) { if(http2Delegate != null) { return http2Delegate.supportsOption(option); } return connection.supportsOption(option); } @Override public T getOption(Option option) throws IOException { if(http2Delegate != null) { return http2Delegate.getOption(option); } return connection.getOption(option); } @Override public T setOption(Option option, T value) throws IllegalArgumentException, IOException { if(http2Delegate != null) { return http2Delegate.setOption(option, value); } return connection.setOption(option, value); } @Override public boolean isUpgraded() { if(http2Delegate != null) { return http2Delegate.isUpgraded(); } return anyAreSet(state, UPGRADE_REQUESTED | UPGRADED); } @Override public boolean isPushSupported() { if(http2Delegate != null) { return http2Delegate.isPushSupported(); } return false; } @Override public boolean isMultiplexingSupported() { if(http2Delegate != null) { return http2Delegate.isMultiplexingSupported(); } return false; } @Override public ClientStatistics getStatistics() { if(http2Delegate != null) { return http2Delegate.getStatistics(); } return clientStatistics; } @Override public boolean isUpgradeSupported() { if(http2Delegate != null) { return false; } return true; } @Override public void addCloseListener(ChannelListener listener) { closeListeners.add(listener); } @Override public void sendRequest(final ClientRequest request, final ClientCallback clientCallback) { try { Integer readTimeout = connection.getOption(Options.READ_TIMEOUT); if (readTimeout != null && readTimeout > 0) { connection.getSourceChannel().setConduit(new ReadTimeoutStreamSourceConduit(connection.getSourceChannel().getConduit(), connection, new HttpOpenListener(bufferPool))); } Integer writeTimeout = connection.getOption(Options.WRITE_TIMEOUT); if (writeTimeout != null && writeTimeout > 0) { connection.getSinkChannel().setConduit(new WriteTimeoutStreamSinkConduit(connection.getSinkChannel().getConduit(), connection, new HttpOpenListener(bufferPool))); } } catch (IOException e) { IoUtils.safeClose(connection); UndertowLogger.REQUEST_IO_LOGGER.ioException(e); } if(http2Delegate != null) { http2Delegate.sendRequest(request, clientCallback); return; } if (anyAreSet(state, UPGRADE_REQUESTED | UPGRADED | CLOSE_REQ | CLOSED)) { clientCallback.failed(UndertowClientMessages.MESSAGES.invalidConnectionState()); return; } final HttpClientExchange httpClientExchange = new HttpClientExchange(clientCallback, request, this); boolean ssl = this.connection instanceof SslConnection; if(!ssl && !http2Tried && options.get(UndertowOptions.ENABLE_HTTP2, false) && !request.getRequestHeaders().contains(Headers.UPGRADE)) { //this is the first request, as we want to try a HTTP2 upgrade request.getRequestHeaders().put(new HttpString("HTTP2-Settings"), Http2ClearClientProvider.createSettingsFrame(options, bufferPool)); request.getRequestHeaders().put(Headers.UPGRADE, Http2Channel.CLEARTEXT_UPGRADE_STRING); request.getRequestHeaders().put(Headers.CONNECTION, "Upgrade, HTTP2-Settings"); http2Tried = true; } if (currentRequest == null) { initiateRequest(httpClientExchange); } else { pendingQueue.add(httpClientExchange); } } private void initiateRequest(HttpClientExchange httpClientExchange) { this.requestCount++; currentRequest = httpClientExchange; pendingResponse = new HttpResponseBuilder(); ClientRequest request = httpClientExchange.getRequest(); String connectionString = request.getRequestHeaders().getFirst(Headers.CONNECTION); if (connectionString != null) { if (Headers.CLOSE.equalToString(connectionString)) { state |= CLOSE_REQ; } else if (Headers.UPGRADE.equalToString(connectionString)) { state |= UPGRADE_REQUESTED; } } else if (request.getProtocol() != Protocols.HTTP_1_1) { state |= CLOSE_REQ; } if (request.getRequestHeaders().contains(Headers.UPGRADE)) { state |= UPGRADE_REQUESTED; } if(request.getMethod().equals(Methods.CONNECT)) { //we treat CONNECT like upgrade requests state |= UPGRADE_REQUESTED; } //setup the client request conduits final ConduitStreamSourceChannel sourceChannel = connection.getSourceChannel(); sourceChannel.setReadListener(clientReadListener); sourceChannel.resumeReads(); ConduitStreamSinkChannel sinkChannel = connection.getSinkChannel(); StreamSinkConduit conduit = originalSinkConduit; HttpRequestConduit httpRequestConduit = new HttpRequestConduit(conduit, bufferPool, request); httpClientExchange.setRequestConduit(httpRequestConduit); conduit = httpRequestConduit; String fixedLengthString = request.getRequestHeaders().getFirst(Headers.CONTENT_LENGTH); String transferEncodingString = request.getRequestHeaders().getLast(Headers.TRANSFER_ENCODING); boolean hasContent = true; if (fixedLengthString != null) { try { long length = Long.parseLong(fixedLengthString); conduit = new ClientFixedLengthStreamSinkConduit(conduit, length, false, false, currentRequest); hasContent = length != 0; } catch (NumberFormatException e) { handleError(e); return; } } else if (transferEncodingString != null) { if (!transferEncodingString.toLowerCase(Locale.ENGLISH).contains(Headers.CHUNKED.toString())) { handleError(UndertowClientMessages.MESSAGES.unknownTransferEncoding(transferEncodingString)); return; } conduit = new ChunkedStreamSinkConduit(conduit, httpClientExchange.getConnection().getBufferPool(), false, false, httpClientExchange.getRequest().getRequestHeaders(), requestFinishListener, httpClientExchange); } else { conduit = new ClientFixedLengthStreamSinkConduit(conduit, 0, false, false, currentRequest); hasContent = false; } sinkChannel.setConduit(conduit); httpClientExchange.invokeReadReadyCallback(); if (!hasContent) { //if there is no content we flush the response channel. //otherwise it is up to the user try { sinkChannel.shutdownWrites(); if (!sinkChannel.flush()) { sinkChannel.setWriteListener(ChannelListeners.flushingChannelListener(null, new ChannelExceptionHandler() { @Override public void handleException(ConduitStreamSinkChannel channel, IOException exception) { handleError(exception); } })); sinkChannel.resumeWrites(); } } catch (Throwable t) { handleError(t); } } } private void handleError(Throwable exception) { if (exception instanceof IOException) { handleError((IOException) exception); } else { handleError(new IOException(exception)); } } private void handleError(IOException exception) { UndertowLogger.REQUEST_IO_LOGGER.ioException(exception); currentRequest.setFailed(exception); currentRequest = null; pendingResponse = null; safeClose(connection); } public StreamConnection performUpgrade() throws IOException { log.debugf("connection to %s is being upgraded", getPeerAddress()); // Upgrade the connection // Set the upgraded flag already to prevent new requests after this one if (allAreSet(state, UPGRADED | CLOSE_REQ | CLOSED)) { throw new IOException(UndertowClientMessages.MESSAGES.connectionClosed()); } state |= UPGRADED; connection.getSinkChannel().setConduit(originalSinkConduit); connection.getSourceChannel().setConduit(pushBackStreamSourceConduit); return connection; } public void close() throws IOException { log.debugf("close called on connection to %s", getPeerAddress()); if(http2Delegate != null) { http2Delegate.close(); } if (anyAreSet(state, CLOSED)) { return; } state |= CLOSED | CLOSE_REQ; ConnectionUtils.cleanClose(connection); } /** * Notification that the current request is finished */ public void exchangeDone() { log.debugf("exchange complete in connection to %s", getPeerAddress()); connection.getSinkChannel().setConduit(originalSinkConduit); connection.getSourceChannel().setConduit(pushBackStreamSourceConduit); connection.getSinkChannel().suspendWrites(); connection.getSinkChannel().setWriteListener(null); if (anyAreSet(state, CLOSE_REQ)) { currentRequest = null; pendingResponse = null; this.state |= CLOSED; safeClose(connection); } else if (anyAreSet(state, UPGRADE_REQUESTED)) { connection.getSourceChannel().suspendReads(); currentRequest = null; pendingResponse = null; return; } currentRequest = null; pendingResponse = null; HttpClientExchange next = pendingQueue.poll(); if (next == null) { //we resume reads, so if the target goes away we get notified connection.getSourceChannel().setReadListener(clientReadListener); connection.getSourceChannel().resumeReads(); } else { initiateRequest(next); } } public void requestDataSent() { if(http2UpgradeReceived) { doHttp2Upgrade(); } } class ClientReadListener implements ChannelListener { public void handleEvent(StreamSourceChannel channel) { HttpResponseBuilder builder = pendingResponse; final PooledByteBuffer pooled = bufferPool.allocate(); final ByteBuffer buffer = pooled.getBuffer(); boolean free = true; try { if (builder == null) { //read ready when no request pending buffer.clear(); try { int res = channel.read(buffer); if(res == -1) { UndertowLogger.CLIENT_LOGGER.debugf("Connection to %s was closed by the target server", connection.getPeerAddress()); safeClose(HttpClientConnection.this); } else if(res != 0) { UndertowLogger.CLIENT_LOGGER.debugf("Target server %s sent unexpected data when no request pending, closing connection", connection.getPeerAddress()); safeClose(HttpClientConnection.this); } //otherwise it is a spurious notification } catch (IOException e) { if (UndertowLogger.CLIENT_LOGGER.isDebugEnabled()) { UndertowLogger.CLIENT_LOGGER.debugf(e, "Connection closed with IOException"); } safeClose(connection); } return; } final ResponseParseState state = builder.getParseState(); int res; do { buffer.clear(); try { res = channel.read(buffer); } catch (IOException e) { if (UndertowLogger.CLIENT_LOGGER.isDebugEnabled()) { UndertowLogger.CLIENT_LOGGER.debugf(e, "Connection closed with IOException"); } try { if (currentRequest != null) { currentRequest.setFailed(e); currentRequest = null; } pendingResponse = null; } finally { safeClose(channel, HttpClientConnection.this); } return; } if (res == 0) { if (!channel.isReadResumed()) { channel.getReadSetter().set(this); channel.resumeReads(); } return; } else if (res == -1) { channel.suspendReads(); try { // Cancel the current active request if (currentRequest != null) { currentRequest.setFailed(new IOException(MESSAGES.connectionClosed())); currentRequest = null; } pendingResponse = null; } finally { safeClose(HttpClientConnection.this); } return; } buffer.flip(); HttpResponseParser.INSTANCE.handle(buffer, state, builder); if (buffer.hasRemaining()) { free = false; pushBackStreamSourceConduit.pushBack(new PooledAdaptor(pooled)); pushBackStreamSourceConduit.wakeupReads(); } } while (!state.isComplete()); final ClientResponse response = builder.build(); String connectionString = response.getResponseHeaders().getFirst(Headers.CONNECTION); //check if an upgrade worked if (anyAreSet(HttpClientConnection.this.state, UPGRADE_REQUESTED)) { if ((connectionString == null || !Headers.UPGRADE.equalToString(connectionString)) && !response.getResponseHeaders().contains(Headers.UPGRADE)) { if(!currentRequest.getRequest().getMethod().equals(Methods.CONNECT) || response.getResponseCode() != 200) { //make sure it was not actually a connect request //just unset the upgrade requested flag HttpClientConnection.this.state &= ~UPGRADE_REQUESTED; } } } boolean close = false; if(connectionString != null) { if (Headers.CLOSE.equalToString(connectionString)) { close = true; } else if(!response.getProtocol().equals(Protocols.HTTP_1_1)) { if(!Headers.KEEP_ALIVE.equalToString(connectionString)) { close = true; } } } else if(!response.getProtocol().equals(Protocols.HTTP_1_1)) { close = true; } if(close) { HttpClientConnection.this.state |= CLOSE_REQ; //we are going to close, kill any queued connections HttpClientExchange ex = pendingQueue.poll(); while (ex != null) { ex.setFailed(new IOException(UndertowClientMessages.MESSAGES.connectionClosed())); ex = pendingQueue.poll(); } } if(response.getResponseCode() == StatusCodes.SWITCHING_PROTOCOLS && Http2Channel.CLEARTEXT_UPGRADE_STRING.equals(response.getResponseHeaders().getFirst(Headers.UPGRADE))) { //http2 upgrade http2UpgradeReceived = true; if(currentRequest.isRequestDataSent()) { doHttp2Upgrade(); } } else if (builder.getStatusCode() == StatusCodes.CONTINUE) { pendingResponse = new HttpResponseBuilder(); currentRequest.setContinueResponse(response); } else { prepareResponseChannel(response, currentRequest); channel.getReadSetter().set(null); channel.suspendReads(); pendingResponse = null; currentRequest.setResponse(response); if(response.getResponseCode() == StatusCodes.EXPECTATION_FAILED) { if(HttpContinue.requiresContinueResponse(currentRequest.getRequest().getRequestHeaders())) { HttpClientConnection.this.state |= CLOSE_REQ; ConduitStreamSinkChannel sinkChannel = HttpClientConnection.this.connection.getSinkChannel(); sinkChannel.shutdownWrites(); if(!sinkChannel.flush()) { sinkChannel.setWriteListener(ChannelListeners.flushingChannelListener(null, null)); sinkChannel.resumeWrites(); } if(currentRequest != null) { //we need the null check as flushing the response may have terminated the request currentRequest.terminateRequest(); } } } } } catch (Throwable t) { UndertowLogger.CLIENT_LOGGER.exceptionProcessingRequest(t); safeClose(connection); if(currentRequest != null) { currentRequest.setFailed(new IOException(t)); } } finally { if (free) { pooled.close(); pooledBuffer = null; } else { pooledBuffer = pooled; } } } } protected void doHttp2Upgrade() { try { StreamConnection connectedStreamChannel = this.performUpgrade(); Http2Channel http2Channel = new Http2Channel(connectedStreamChannel, null, bufferPool, null, true, true, options); Http2ClientConnection http2ClientConnection = new Http2ClientConnection(http2Channel, currentRequest.getResponseCallback(), currentRequest.getRequest(), currentRequest.getRequest().getRequestHeaders().getFirst(Headers.HOST), clientStatistics, false); http2ClientConnection.getCloseSetter().set(new ChannelListener() { @Override public void handleEvent(ClientConnection channel) { ChannelListeners.invokeChannelListener(HttpClientConnection.this, HttpClientConnection.this.closeSetter.get()); } }); http2Delegate = http2ClientConnection; connectedStreamChannel.getSourceChannel().wakeupReads(); //make sure the read listener is immediately invoked, as it may not happen if data is pushed back currentRequest = null; pendingResponse = null; } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); safeClose(this); } } private void prepareResponseChannel(ClientResponse response, ClientExchange exchange) { String encoding = response.getResponseHeaders().getLast(Headers.TRANSFER_ENCODING); boolean chunked = encoding != null && Headers.CHUNKED.equals(new HttpString(encoding)); String length = response.getResponseHeaders().getFirst(Headers.CONTENT_LENGTH); if (exchange.getRequest().getMethod().equals(Methods.HEAD)) { connection.getSourceChannel().setConduit(new FixedLengthStreamSourceConduit(connection.getSourceChannel().getConduit(), 0, responseFinishedListener)); } else if (chunked) { connection.getSourceChannel().setConduit(new ChunkedStreamSourceConduit(connection.getSourceChannel().getConduit(), pushBackStreamSourceConduit, bufferPool, responseFinishedListener, exchange, connection)); } else if (length != null) { try { long contentLength = Long.parseLong(length); connection.getSourceChannel().setConduit(new FixedLengthStreamSourceConduit(connection.getSourceChannel().getConduit(), contentLength, responseFinishedListener)); } catch (NumberFormatException e) { handleError(e); throw e; } } else if (response.getProtocol().equals(Protocols.HTTP_1_1) && !Connectors.isEntityBodyAllowed(response.getResponseCode())) { connection.getSourceChannel().setConduit(new FixedLengthStreamSourceConduit(connection.getSourceChannel().getConduit(), 0, responseFinishedListener)); } else { connection.getSourceChannel().setConduit(new FinishableStreamSourceConduit(connection.getSourceChannel().getConduit(), responseFinishedListener)); state |= CLOSE_REQ; } } private class ClientStatisticsImpl implements ClientStatistics { @Override public long getRequests() { return requestCount; } @Override public long getRead() { return read; } @Override public long getWritten() { return written; } @Override public void reset() { read = 0; written = 0; requestCount = 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/http/HttpClientExchange.java000066400000000000000000000151231420065311100311570ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client.http; import io.undertow.channels.DetachableStreamSinkChannel; import io.undertow.channels.DetachableStreamSourceChannel; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientExchange; import io.undertow.client.ClientRequest; import io.undertow.client.ClientResponse; import io.undertow.client.ContinueNotification; import io.undertow.client.PushCallback; import io.undertow.util.AbstractAttachable; import io.undertow.util.Headers; import org.jboss.logging.Logger; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; import java.io.IOException; import static org.xnio.Bits.anyAreSet; /** * @author Stuart Douglas */ class HttpClientExchange extends AbstractAttachable implements ClientExchange { private static final Logger log = Logger.getLogger(HttpClientExchange.class.getName()); private final ClientRequest request; private final boolean requiresContinue; private final HttpClientConnection clientConnection; private ClientCallback responseCallback; private ClientCallback readyCallback; private ContinueNotification continueNotification; private ClientResponse response; private ClientResponse continueResponse; private IOException failedReason; private HttpRequestConduit requestConduit; private int state = 0; private static final int REQUEST_TERMINATED = 1; private static final int RESPONSE_TERMINATED = 1 << 1; HttpClientExchange(ClientCallback readyCallback, ClientRequest request, HttpClientConnection clientConnection) { this.readyCallback = readyCallback; this.request = request; this.clientConnection = clientConnection; boolean reqContinue = false; if (request.getRequestHeaders().contains(Headers.EXPECT)) { for (String header : request.getRequestHeaders().get(Headers.EXPECT)) { if (header.equals("100-continue")) { reqContinue = true; } } } this.requiresContinue = reqContinue; } public void setRequestConduit(HttpRequestConduit requestConduit) { this.requestConduit = requestConduit; } void terminateRequest() { if(anyAreSet(state, REQUEST_TERMINATED)) { return; } log.debugf("request terminated for request to %s %s", clientConnection.getPeerAddress(), getRequest().getPath()); state |= REQUEST_TERMINATED; clientConnection.requestDataSent(); if (anyAreSet(state, RESPONSE_TERMINATED)) { clientConnection.exchangeDone(); } } boolean isRequestDataSent() { return anyAreSet(state, REQUEST_TERMINATED); } void terminateResponse() { if(anyAreSet(state, RESPONSE_TERMINATED)) { return; } log.debugf("response terminated for request to %s %s", clientConnection.getPeerAddress(), getRequest().getPath()); state |= RESPONSE_TERMINATED; if (anyAreSet(state, REQUEST_TERMINATED)) { clientConnection.exchangeDone(); } } public boolean isRequiresContinue() { return requiresContinue; } void setContinueResponse(ClientResponse response) { this.continueResponse = response; if (continueNotification != null) { this.continueNotification.handleContinue(this); } } void setResponse(ClientResponse response) { this.response = response; if (responseCallback != null) { this.responseCallback.completed(this); } } @Override public void setResponseListener(ClientCallback listener) { this.responseCallback = listener; if (listener != null) { if (failedReason != null) { listener.failed(failedReason); } else if (response != null) { listener.completed(this); } } } @Override public void setContinueHandler(ContinueNotification continueHandler) { this.continueNotification = continueHandler; } @Override public void setPushHandler(PushCallback pushCallback) { } void setFailed(IOException e) { this.failedReason = e; if (readyCallback != null) { readyCallback.failed(e); readyCallback = null; } if (responseCallback != null) { responseCallback.failed(e); responseCallback = null; } if(requestConduit != null) { requestConduit.freeBuffers(); } } @Override public StreamSinkChannel getRequestChannel() { return new DetachableStreamSinkChannel(clientConnection.getConnection().getSinkChannel()) { @Override protected boolean isFinished() { return anyAreSet(state, REQUEST_TERMINATED); } }; } @Override public StreamSourceChannel getResponseChannel() { return new DetachableStreamSourceChannel(clientConnection.getConnection().getSourceChannel()) { @Override protected boolean isFinished() { return anyAreSet(state, RESPONSE_TERMINATED); } }; } @Override public ClientRequest getRequest() { return request; } @Override public ClientResponse getResponse() { return response; } @Override public ClientResponse getContinueResponse() { return continueResponse; } @Override public ClientConnection getConnection() { return clientConnection; } ClientCallback getResponseCallback() { return responseCallback; } void invokeReadReadyCallback() { if(readyCallback != null) { readyCallback.completed(this); readyCallback = null; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/http/HttpClientProvider.java000066400000000000000000000176111420065311100312330ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client.http; import io.undertow.UndertowMessages; import io.undertow.UndertowOptions; import io.undertow.client.ALPNClientSelector; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientProvider; import io.undertow.client.http2.Http2ClientProvider; import org.xnio.ChannelListener; import org.xnio.IoFuture; import org.xnio.OptionMap; import org.xnio.Options; import io.undertow.connector.ByteBufferPool; import org.xnio.StreamConnection; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.ssl.SslConnection; import org.xnio.ssl.XnioSsl; import java.io.IOException; import java.net.InetSocketAddress; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; /** * @author Stuart Douglas */ public class HttpClientProvider implements ClientProvider { @Override public Set handlesSchemes() { return new HashSet<>(Arrays.asList(new String[]{"http", "https"})); } @Override public void connect(final ClientCallback listener, final URI uri, final XnioWorker worker, final XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap options) { connect(listener, null, uri, worker, ssl, bufferPool, options); } @Override public void connect(final ClientCallback listener, final URI uri, final XnioIoThread ioThread, final XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap options) { connect(listener, null, uri, ioThread, ssl, bufferPool, options); } @Override public void connect(ClientCallback listener, InetSocketAddress bindAddress, URI uri, XnioWorker worker, XnioSsl ssl, ByteBufferPool bufferPool, OptionMap options) { if (uri.getScheme().equals("https")) { if (ssl == null) { listener.failed(UndertowMessages.MESSAGES.sslWasNull()); return; } OptionMap tlsOptions = OptionMap.builder().addAll(options).set(Options.SSL_STARTTLS, true).getMap(); if (bindAddress == null) { ssl.openSslConnection(worker, new InetSocketAddress(uri.getHost(), uri.getPort() == -1 ? 443 : uri.getPort()), createOpenListener(listener, bufferPool, tlsOptions, uri), tlsOptions).addNotifier(createNotifier(listener), null); } else { ssl.openSslConnection(worker, bindAddress, new InetSocketAddress(uri.getHost(), uri.getPort() == -1 ? 443 : uri.getPort()), createOpenListener(listener, bufferPool, tlsOptions, uri), tlsOptions).addNotifier(createNotifier(listener), null); } } else { if (bindAddress == null) { worker.openStreamConnection(new InetSocketAddress(uri.getHost(), uri.getPort() == -1 ? 80 : uri.getPort()), createOpenListener(listener, bufferPool, options, uri), options).addNotifier(createNotifier(listener), null); } else { worker.openStreamConnection(bindAddress, new InetSocketAddress(uri.getHost(), uri.getPort() == -1 ? 80 : uri.getPort()), createOpenListener(listener, bufferPool, options, uri), null, options).addNotifier(createNotifier(listener), null); } } } @Override public void connect(ClientCallback listener, InetSocketAddress bindAddress, URI uri, XnioIoThread ioThread, XnioSsl ssl, ByteBufferPool bufferPool, OptionMap options) { if (uri.getScheme().equals("https")) { if (ssl == null) { listener.failed(UndertowMessages.MESSAGES.sslWasNull()); return; } OptionMap tlsOptions = OptionMap.builder().addAll(options).set(Options.SSL_STARTTLS, true).getMap(); if (bindAddress == null) { ssl.openSslConnection(ioThread, new InetSocketAddress(uri.getHost(), uri.getPort() == -1 ? 443 : uri.getPort()), createOpenListener(listener, bufferPool, tlsOptions, uri), tlsOptions).addNotifier(createNotifier(listener), null); } else { ssl.openSslConnection(ioThread, bindAddress, new InetSocketAddress(uri.getHost(), uri.getPort() == -1 ? 443 : uri.getPort()), createOpenListener(listener, bufferPool, tlsOptions, uri), tlsOptions).addNotifier(createNotifier(listener), null); } } else { if (bindAddress == null) { ioThread.openStreamConnection(new InetSocketAddress(uri.getHost(), uri.getPort() == -1 ? 80 : uri.getPort()), createOpenListener(listener, bufferPool, options, uri), options).addNotifier(createNotifier(listener), null); } else { ioThread.openStreamConnection(bindAddress, new InetSocketAddress(uri.getHost(), uri.getPort() == -1 ? 80 : uri.getPort()), createOpenListener(listener, bufferPool, options, uri), null, options).addNotifier(createNotifier(listener), null); } } } private IoFuture.Notifier createNotifier(final ClientCallback listener) { return new IoFuture.Notifier() { @Override public void notify(IoFuture ioFuture, Object o) { if (ioFuture.getStatus() == IoFuture.Status.FAILED) { listener.failed(ioFuture.getException()); } } }; } private ChannelListener createOpenListener(final ClientCallback listener, final ByteBufferPool bufferPool, final OptionMap options, final URI uri) { return new ChannelListener() { @Override public void handleEvent(StreamConnection connection) { handleConnected(connection, listener, bufferPool, options, uri); } }; } private void handleConnected(final StreamConnection connection, final ClientCallback listener, final ByteBufferPool bufferPool, final OptionMap options, URI uri) { boolean h2 = options.get(UndertowOptions.ENABLE_HTTP2, false); if(connection instanceof SslConnection && (h2)) { List protocolList = new ArrayList<>(); if(h2) { protocolList.add(Http2ClientProvider.alpnProtocol(listener, uri, bufferPool, options)); } ALPNClientSelector.runAlpn((SslConnection) connection, new ChannelListener() { @Override public void handleEvent(SslConnection connection) { listener.completed(new HttpClientConnection(connection, options, bufferPool)); } }, listener, protocolList.toArray(new ALPNClientSelector.ALPNProtocol[protocolList.size()])); } else { if(connection instanceof SslConnection) { try { ((SslConnection) connection).startHandshake(); } catch (Throwable t) { listener.failed((t instanceof IOException) ? (IOException) t : new IOException(t)); } } listener.completed(new HttpClientConnection(connection, options, bufferPool)); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/http/HttpRequestConduit.java000066400000000000000000000675221420065311100312660ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client.http; import io.undertow.client.ClientRequest; import io.undertow.server.TruncatedResponseException; import io.undertow.util.HeaderMap; import io.undertow.util.HttpString; import org.jboss.logging.Logger; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import org.xnio.XnioWorker; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.AbstractStreamSinkConduit; import org.xnio.conduits.Conduits; import org.xnio.conduits.StreamSinkConduit; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.util.Iterator; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.allAreSet; /** * @author David M. Lloyd * @author Emanuel Muckenhuber */ final class HttpRequestConduit extends AbstractStreamSinkConduit { private static final Logger log = Logger.getLogger("io.undertow.client.request"); private final ByteBufferPool pool; private int state = STATE_START; private Iterator nameIterator; private String string; private HttpString headerName; private Iterator valueIterator; private int charIndex; private PooledByteBuffer pooledBuffer; private final ClientRequest request; private static final int STATE_BODY = 0; // Message body, normal pass-through operation private static final int STATE_URL = 1; //Writing the URL private static final int STATE_START = 2; // No headers written yet private static final int STATE_HDR_NAME = 3; // Header name indexed by charIndex private static final int STATE_HDR_D = 4; // Header delimiter ':' private static final int STATE_HDR_DS = 5; // Header delimiter ': ' private static final int STATE_HDR_VAL = 6; // Header value private static final int STATE_HDR_EOL_CR = 7; // Header line CR private static final int STATE_HDR_EOL_LF = 8; // Header line LF private static final int STATE_HDR_FINAL_CR = 9; // Final CR private static final int STATE_HDR_FINAL_LF = 10; // Final LF private static final int STATE_BUF_FLUSH = 11; // flush the buffer and go to writing body private static final int MASK_STATE = 0x0000000F; private static final int FLAG_SHUTDOWN = 0x00000010; HttpRequestConduit(final StreamSinkConduit next, final ByteBufferPool pool, final ClientRequest request) { super(next); this.pool = pool; this.request = request; } /** * Handles writing out the header data. It can also take a byte buffer of user * data, to enable both user data and headers to be written out in a single operation, * which has a noticeable performance impact. * * It is up to the caller to note the current position of this buffer before and after they * call this method, and use this to figure out how many bytes (if any) have been written. * @param state * @param userData * @return * @throws java.io.IOException */ private int processWrite(int state, final ByteBuffer userData) throws IOException { if (state == STATE_START) { pooledBuffer = pool.allocate(); } ClientRequest request = this.request; ByteBuffer buffer = pooledBuffer.getBuffer(); int length; int res; // BUFFER IS FLIPPED COMING IN if (state != STATE_START && buffer.hasRemaining()) { log.trace("Flushing remaining buffer"); do { res = next.write(buffer); if (res == 0) { return state; } } while (buffer.hasRemaining()); } buffer.clear(); // BUFFER IS NOW EMPTY FOR FILLING for (;;) { switch (state) { case STATE_BODY: { // shouldn't be possible, but might as well do the right thing anyway return state; } case STATE_START: { log.trace("Starting request"); int len = request.getMethod().length() + request.getPath().length() + request.getProtocol().length() + 4; // test that our buffer has enough space for the initial request line plus one more CR+LF if(len <= buffer.remaining()) { assert buffer.remaining() >= 50; request.getMethod().appendTo(buffer); buffer.put((byte) ' '); string = request.getPath(); length = string.length(); for (charIndex = 0; charIndex < length; charIndex++) { buffer.put((byte) string.charAt(charIndex)); } buffer.put((byte) ' '); request.getProtocol().appendTo(buffer); buffer.put((byte) '\r').put((byte) '\n'); } else { StringBuilder sb = new StringBuilder(len); sb.append(request.getMethod().toString()); sb.append(" "); sb.append(request.getPath()); sb.append(" "); sb.append(request.getProtocol()); sb.append("\r\n"); string = sb.toString(); charIndex = 0; state = STATE_URL; break; } HeaderMap headers = request.getRequestHeaders(); nameIterator = headers.getHeaderNames().iterator(); if (! nameIterator.hasNext()) { log.trace("No request headers"); buffer.put((byte) '\r').put((byte) '\n'); buffer.flip(); while (buffer.hasRemaining()) { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_BUF_FLUSH; } } pooledBuffer.close(); pooledBuffer = null; log.trace("Body"); return STATE_BODY; } headerName = nameIterator.next(); charIndex = 0; // fall thru } case STATE_HDR_NAME: { log.tracef("Processing header '%s'", headerName); length = headerName.length(); while (charIndex < length) { if (buffer.hasRemaining()) { buffer.put(headerName.byteAt(charIndex++)); } else { log.trace("Buffer flush"); buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_HDR_NAME; } } while (buffer.hasRemaining()); buffer.clear(); } } // fall thru } case STATE_HDR_D: { if (! buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_HDR_D; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) ':'); // fall thru } case STATE_HDR_DS: { if (! buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_HDR_DS; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) ' '); if(valueIterator == null) { valueIterator = request.getRequestHeaders().get(headerName).iterator(); } assert valueIterator.hasNext(); string = valueIterator.next(); charIndex = 0; // fall thru } case STATE_HDR_VAL: { log.tracef("Processing header value '%s'", string); length = string.length(); while (charIndex < length) { if (buffer.hasRemaining()) { buffer.put((byte) string.charAt(charIndex++)); } else { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_HDR_VAL; } } while (buffer.hasRemaining()); buffer.clear(); } } charIndex = 0; if (! valueIterator.hasNext()) { if (! buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_HDR_EOL_CR; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) 13); // CR if (! buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_HDR_EOL_LF; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) 10); // LF if (nameIterator.hasNext()) { headerName = nameIterator.next(); valueIterator = null; state = STATE_HDR_NAME; break; } else { if (! buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_HDR_FINAL_CR; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) 13); // CR if (! buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_HDR_FINAL_LF; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) 10); // LF this.nameIterator = null; this.valueIterator = null; this.string = null; buffer.flip(); //for performance reasons we use a gather write if there is user data if(userData == null) { do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_BUF_FLUSH; } } while (buffer.hasRemaining()); } else { ByteBuffer[] b = {buffer, userData}; do { long r = next.write(b, 0, b.length); if (r == 0 && buffer.hasRemaining()) { log.trace("Continuation"); return STATE_BUF_FLUSH; } } while (buffer.hasRemaining()); } pooledBuffer.close(); pooledBuffer = null; log.trace("Body"); return STATE_BODY; } // not reached } // fall thru } // Clean-up states case STATE_HDR_EOL_CR: { if (! buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_HDR_EOL_CR; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) 13); // CR } case STATE_HDR_EOL_LF: { if (! buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_HDR_EOL_LF; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) 10); // LF if(valueIterator != null && valueIterator.hasNext()) { state = STATE_HDR_NAME; break; } else if (nameIterator.hasNext()) { headerName = nameIterator.next(); valueIterator = null; state = STATE_HDR_NAME; break; } // fall thru } case STATE_HDR_FINAL_CR: { if (! buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_HDR_FINAL_CR; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) 13); // CR // fall thru } case STATE_HDR_FINAL_LF: { if (! buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_HDR_FINAL_LF; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) 10); // LF this.nameIterator = null; this.valueIterator = null; this.string = null; buffer.flip(); //for performance reasons we use a gather write if there is user data if(userData == null) { do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_BUF_FLUSH; } } while (buffer.hasRemaining()); } else { ByteBuffer[] b = {buffer, userData}; do { long r = next.write(b, 0, b.length); if (r == 0 && buffer.hasRemaining()) { log.trace("Continuation"); return STATE_BUF_FLUSH; } } while (buffer.hasRemaining()); } // fall thru } case STATE_BUF_FLUSH: { // buffer was successfully flushed above pooledBuffer.close(); pooledBuffer = null; return STATE_BODY; } case STATE_URL: { for(int i = charIndex; i < string.length(); ++i) { if(!buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); this.charIndex = i; this.state = STATE_URL; return STATE_URL; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) string.charAt(i)); } HeaderMap headers = request.getRequestHeaders(); nameIterator = headers.getHeaderNames().iterator(); state = STATE_HDR_NAME; if (! nameIterator.hasNext()) { log.trace("No request headers"); buffer.put((byte) '\r').put((byte) '\n'); buffer.flip(); while (buffer.hasRemaining()) { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_BUF_FLUSH; } } pooledBuffer.close(); pooledBuffer = null; log.trace("Body"); return STATE_BODY; } headerName = nameIterator.next(); charIndex = 0; break; } default: { throw new IllegalStateException(); } } } } public int write(final ByteBuffer src) throws IOException { log.trace("write"); int oldState = this.state; int state = oldState & MASK_STATE; int alreadyWritten = 0; int originalRemaining = - 1; try { if (state != 0) { originalRemaining = src.remaining(); state = processWrite(state, src); if (state != 0) { return 0; } alreadyWritten = originalRemaining - src.remaining(); if (allAreSet(oldState, FLAG_SHUTDOWN)) { next.terminateWrites(); throw new ClosedChannelException(); } } if(alreadyWritten != originalRemaining) { return next.write(src) + alreadyWritten; } return alreadyWritten; } catch (IOException | RuntimeException | Error e) { this.state |= FLAG_SHUTDOWN; if(pooledBuffer != null) { pooledBuffer.close(); pooledBuffer = null; } throw e; } finally { this.state = oldState & ~MASK_STATE | state; } } public long write(final ByteBuffer[] srcs) throws IOException { return write(srcs, 0, srcs.length); } public long write(final ByteBuffer[] srcs, final int offset, final int length) throws IOException { log.trace("write"); if (length == 0) { return 0L; } int oldVal = state; int state = oldVal & MASK_STATE; try { if (state != 0) { //todo: use gathering write here state = processWrite(state, null); if (state != 0) { return 0; } if (allAreSet(oldVal, FLAG_SHUTDOWN)) { next.terminateWrites(); throw new ClosedChannelException(); } } return length == 1 ? next.write(srcs[offset]) : next.write(srcs, offset, length); } catch (IOException | RuntimeException | Error e) { this.state |= FLAG_SHUTDOWN; if(pooledBuffer != null) { pooledBuffer.close(); pooledBuffer = null; } throw e; } finally { this.state = oldVal & ~MASK_STATE | state; } } @Override public int writeFinal(ByteBuffer src) throws IOException { return Conduits.writeFinalBasic(this, src); } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { return Conduits.writeFinalBasic(this, srcs, offset, length); } public long transferFrom(final FileChannel src, final long position, final long count) throws IOException { log.trace("transfer"); if (count == 0L) { return 0L; } int oldVal = state; int state = oldVal & MASK_STATE; try { if (state != 0) { state = processWrite(state, null); if (state != 0) { return 0; } if (allAreSet(oldVal, FLAG_SHUTDOWN)) { next.terminateWrites(); throw new ClosedChannelException(); } } return next.transferFrom(src, position, count); } catch (IOException | RuntimeException | Error e) { this.state |= FLAG_SHUTDOWN; if(pooledBuffer != null) { pooledBuffer.close(); pooledBuffer = null; } throw e; } finally { this.state = oldVal & ~MASK_STATE | state; } } public long transferFrom(final StreamSourceChannel source, final long count, final ByteBuffer throughBuffer) throws IOException { log.trace("transfer"); if (count == 0) { throughBuffer.clear().limit(0); return 0L; } int oldVal = state; int state = oldVal & MASK_STATE; try { if (state != 0) { state = processWrite(state, null); if (state != 0) { return 0; } if (allAreSet(oldVal, FLAG_SHUTDOWN)) { next.terminateWrites(); throw new ClosedChannelException(); } } return next.transferFrom(source, count, throughBuffer); } catch (IOException | RuntimeException | Error e) { this.state |= FLAG_SHUTDOWN; if(pooledBuffer != null) { pooledBuffer.close(); pooledBuffer = null; } throw e; } finally { this.state = oldVal & ~MASK_STATE | state; } } public boolean flush() throws IOException { log.trace("flush"); int oldVal = state; int state = oldVal & MASK_STATE; try { if (state != 0) { state = processWrite(state, null); if (state != 0) { log.trace("Flush false because headers aren't written yet"); return false; } if (allAreSet(oldVal, FLAG_SHUTDOWN)) { next.terminateWrites(); // fall out to the flush } } log.trace("Delegating flush"); return next.flush(); } catch (IOException | RuntimeException | Error e) { this.state |= FLAG_SHUTDOWN; if(pooledBuffer != null) { pooledBuffer.close(); pooledBuffer = null; } throw e; } finally { this.state = oldVal & ~MASK_STATE | state; } } public void terminateWrites() throws IOException { log.trace("shutdown"); int oldVal = this.state; if (allAreClear(oldVal, MASK_STATE)) { next.terminateWrites(); return; } this.state = oldVal | FLAG_SHUTDOWN; } public void truncateWrites() throws IOException { log.trace("close"); int oldVal = this.state; if (allAreClear(oldVal, MASK_STATE)) { try { next.truncateWrites(); } finally { if (pooledBuffer != null) { pooledBuffer.close(); pooledBuffer = null; } } return; } this.state = oldVal & ~MASK_STATE | FLAG_SHUTDOWN; throw new TruncatedResponseException(); } public XnioWorker getWorker() { return next.getWorker(); } public void freeBuffers() { if(pooledBuffer != null) { pooledBuffer.close(); pooledBuffer = null; this.state = state & ~MASK_STATE | FLAG_SHUTDOWN; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/http/HttpResponseBuilder.java000066400000000000000000000037151420065311100314070ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client.http; import io.undertow.client.ClientResponse; import io.undertow.util.HeaderMap; import io.undertow.util.HttpString; /** * A pending http request. * * @author Emanuel Muckenhuber */ final class HttpResponseBuilder { private final ResponseParseState parseState = new ResponseParseState(); private int statusCode; private HttpString protocol; private String reasonPhrase; private final HeaderMap responseHeaders = new HeaderMap(); public ResponseParseState getParseState() { return parseState; } HeaderMap getResponseHeaders() { return responseHeaders; } int getStatusCode() { return statusCode; } void setStatusCode(final int statusCode) { this.statusCode = statusCode; } String getReasonPhrase() { return reasonPhrase; } void setReasonPhrase(final String reasonPhrase) { this.reasonPhrase = reasonPhrase; } HttpString getProtocol() { return protocol; } @SuppressWarnings("unused") void setProtocol(final HttpString protocol) { this.protocol = protocol; } public ClientResponse build() { return new ClientResponse(statusCode, reasonPhrase, protocol, responseHeaders); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/http/HttpResponseParser.java000066400000000000000000000346471420065311100312650ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client.http; import io.undertow.annotationprocessor.HttpResponseParserConfig; import io.undertow.util.BadRequestException; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.Methods; import io.undertow.util.Protocols; import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; import static io.undertow.util.Headers.ACCEPT_RANGES_STRING; import static io.undertow.util.Headers.AGE_STRING; import static io.undertow.util.Headers.CACHE_CONTROL_STRING; import static io.undertow.util.Headers.CONNECTION_STRING; import static io.undertow.util.Headers.CONTENT_DISPOSITION_STRING; import static io.undertow.util.Headers.CONTENT_ENCODING_STRING; import static io.undertow.util.Headers.CONTENT_LANGUAGE_STRING; import static io.undertow.util.Headers.CONTENT_LENGTH_STRING; import static io.undertow.util.Headers.CONTENT_LOCATION_STRING; import static io.undertow.util.Headers.CONTENT_MD5_STRING; import static io.undertow.util.Headers.CONTENT_RANGE_STRING; import static io.undertow.util.Headers.CONTENT_TYPE_STRING; import static io.undertow.util.Headers.DATE_STRING; import static io.undertow.util.Headers.ETAG_STRING; import static io.undertow.util.Headers.EXPIRES_STRING; import static io.undertow.util.Headers.LAST_MODIFIED_STRING; import static io.undertow.util.Headers.LOCATION_STRING; import static io.undertow.util.Headers.PRAGMA_STRING; import static io.undertow.util.Headers.PROXY_AUTHENTICATE_STRING; import static io.undertow.util.Headers.REFRESH_STRING; import static io.undertow.util.Headers.RETRY_AFTER_STRING; import static io.undertow.util.Headers.SERVER_STRING; import static io.undertow.util.Headers.SET_COOKIE2_STRING; import static io.undertow.util.Headers.SET_COOKIE_STRING; import static io.undertow.util.Headers.STRICT_TRANSPORT_SECURITY_STRING; import static io.undertow.util.Headers.TRAILER_STRING; import static io.undertow.util.Headers.TRANSFER_ENCODING_STRING; import static io.undertow.util.Headers.VARY_STRING; import static io.undertow.util.Headers.VIA_STRING; import static io.undertow.util.Headers.WARNING_STRING; import static io.undertow.util.Headers.WWW_AUTHENTICATE_STRING; import static io.undertow.util.Protocols.HTTP_0_9_STRING; import static io.undertow.util.Protocols.HTTP_1_0_STRING; import static io.undertow.util.Protocols.HTTP_1_1_STRING; /** * @author Emanuel Muckenhuber */ @HttpResponseParserConfig( protocols = { HTTP_0_9_STRING, HTTP_1_0_STRING, HTTP_1_1_STRING }, headers = { ACCEPT_RANGES_STRING, AGE_STRING, CACHE_CONTROL_STRING, CONNECTION_STRING, CONTENT_DISPOSITION_STRING, CONTENT_ENCODING_STRING, CONTENT_LANGUAGE_STRING, CONTENT_LENGTH_STRING, CONTENT_LOCATION_STRING, CONTENT_MD5_STRING, CONTENT_RANGE_STRING, CONTENT_TYPE_STRING, DATE_STRING, EXPIRES_STRING, ETAG_STRING, LAST_MODIFIED_STRING, LOCATION_STRING, PRAGMA_STRING, PROXY_AUTHENTICATE_STRING, REFRESH_STRING, RETRY_AFTER_STRING, SERVER_STRING, SET_COOKIE_STRING, SET_COOKIE2_STRING, STRICT_TRANSPORT_SECURITY_STRING, TRAILER_STRING, TRANSFER_ENCODING_STRING, VARY_STRING, VIA_STRING, WARNING_STRING, WWW_AUTHENTICATE_STRING }) abstract class HttpResponseParser { public static final HttpResponseParser INSTANCE; static { try { final Class cls = Class.forName(HttpResponseParser.class.getName() + "$$generated", false, HttpResponseParser.class.getClassLoader()); INSTANCE = (HttpResponseParser) cls.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } abstract void handleHttpVersion(ByteBuffer buffer, ResponseParseState currentState, HttpResponseBuilder builder) throws BadRequestException; abstract void handleHeader(ByteBuffer buffer, ResponseParseState currentState, HttpResponseBuilder builder) throws BadRequestException; public void handle(final ByteBuffer buffer, final ResponseParseState currentState, final HttpResponseBuilder builder) throws BadRequestException { if (currentState.state == ResponseParseState.VERSION) { handleHttpVersion(buffer, currentState, builder); if (!buffer.hasRemaining()) { return; } } if (currentState.state == ResponseParseState.STATUS_CODE) { handleStatusCode(buffer, currentState, builder); if (!buffer.hasRemaining()) { return; } } if (currentState.state == ResponseParseState.REASON_PHRASE) { handleReasonPhrase(buffer, currentState, builder); if (!buffer.hasRemaining()) { return; } } if (currentState.state == ResponseParseState.AFTER_REASON_PHRASE) { handleAfterReasonPhrase(buffer, currentState, builder); if (!buffer.hasRemaining()) { return; } } while (currentState.state != ResponseParseState.PARSE_COMPLETE) { if (currentState.state == ResponseParseState.HEADER) { handleHeader(buffer, currentState, builder); if (!buffer.hasRemaining()) { return; } } if (currentState.state == ResponseParseState.HEADER_VALUE) { handleHeaderValue(buffer, currentState, builder); if (!buffer.hasRemaining()) { return; } } } } /** * Parses the status code. This is called from the generated bytecode. * * @param buffer The buffer * @param state The current state * @param builder The exchange builder * @return The number of bytes remaining */ @SuppressWarnings("unused") final void handleStatusCode(ByteBuffer buffer, ResponseParseState state, HttpResponseBuilder builder) { StringBuilder stringBuilder = state.stringBuilder; while (buffer.hasRemaining()) { final char next = (char) buffer.get(); if (next == ' ' || next == '\t') { builder.setStatusCode(Integer.parseInt(stringBuilder.toString())); state.state = ResponseParseState.REASON_PHRASE; state.stringBuilder.setLength(0); state.parseState = 0; state.pos = 0; state.nextHeader = null; return; } else { stringBuilder.append(next); } } } /** * Parses the reason phrase. This is called from the generated bytecode. * * @param buffer The buffer * @param state The current state * @param builder The exchange builder * @return The number of bytes remaining */ @SuppressWarnings("unused") final void handleReasonPhrase(ByteBuffer buffer, ResponseParseState state, HttpResponseBuilder builder) { StringBuilder stringBuilder = state.stringBuilder; while (buffer.hasRemaining()) { final char next = (char) buffer.get(); if (next == '\n' || next == '\r') { builder.setReasonPhrase(stringBuilder.toString()); state.state = ResponseParseState.AFTER_REASON_PHRASE; state.stringBuilder.setLength(0); state.parseState = 0; state.leftOver = (byte) next; state.pos = 0; state.nextHeader = null; return; } else { stringBuilder.append(next); } } } /** * The parse states for parsing heading values */ private static final int NORMAL = 0; private static final int WHITESPACE = 1; private static final int BEGIN_LINE_END = 2; private static final int LINE_END = 3; private static final int AWAIT_DATA_END = 4; /** * Parses a header value. This is called from the generated bytecode. * * @param buffer The buffer * @param state The current state * @param builder The exchange builder * @return The number of bytes remaining */ @SuppressWarnings("unused") final void handleHeaderValue(ByteBuffer buffer, ResponseParseState state, HttpResponseBuilder builder) { StringBuilder stringBuilder = state.stringBuilder; if (stringBuilder == null) { stringBuilder = new StringBuilder(); state.parseState = 0; } int parseState = state.parseState; while (buffer.hasRemaining()) { final byte next = buffer.get(); switch (parseState) { case NORMAL: { if (next == '\r') { parseState = BEGIN_LINE_END; } else if (next == '\n') { parseState = LINE_END; } else if (next == ' ' || next == '\t') { parseState = WHITESPACE; } else { stringBuilder.append((char) next); } break; } case WHITESPACE: { if (next == '\r') { parseState = BEGIN_LINE_END; } else if (next == '\n') { parseState = LINE_END; } else if (next == ' ' || next == '\t') { } else { if (stringBuilder.length() > 0) { stringBuilder.append(' '); } stringBuilder.append((char) next); parseState = NORMAL; } break; } case LINE_END: case BEGIN_LINE_END: { if (next == '\n' && parseState == BEGIN_LINE_END) { parseState = LINE_END; } else if (next == '\t' || next == ' ') { //this is a continuation parseState = WHITESPACE; } else { //we have a header HttpString nextStandardHeader = state.nextHeader; String headerValue = stringBuilder.toString(); //TODO: we need to decode this according to RFC-2047 if we have seen a =? symbol builder.getResponseHeaders().add(nextStandardHeader, headerValue); state.nextHeader = null; state.leftOver = next; state.stringBuilder.setLength(0); if (next == '\r') { parseState = AWAIT_DATA_END; } else { state.state = ResponseParseState.HEADER; state.parseState = 0; return; } } break; } case AWAIT_DATA_END: { state.state = ResponseParseState.PARSE_COMPLETE; return; } } } //we only write to the state if we did not finish parsing state.parseState = parseState; } protected void handleAfterReasonPhrase(ByteBuffer buffer, ResponseParseState state, HttpResponseBuilder builder) { boolean newLine = state.leftOver == '\n'; while (buffer.hasRemaining()) { final byte next = buffer.get(); if (newLine) { if (next == '\n') { state.state = ResponseParseState.PARSE_COMPLETE; return; } else { state.state = ResponseParseState.HEADER; state.leftOver = next; return; } } else { if (next == '\n') { newLine = true; } else if (next != '\r' && next != ' ' && next != '\t') { state.state = ResponseParseState.HEADER; state.leftOver = next; return; } } } if (newLine) { state.leftOver = '\n'; } } /** * This is a bit of hack to enable the parser to get access to the HttpString's that are sorted * in the static fields of the relevant classes. This means that in most cases a HttpString comparison * will take the fast path == route, as they will be the same object * * @return */ protected static Map httpStrings() { final Map results = new HashMap<>(); final Class[] classs = {Headers.class, Methods.class, Protocols.class}; for (Class c : classs) { for (Field field : c.getDeclaredFields()) { if (field.getType().equals(HttpString.class)) { field.setAccessible(true); HttpString result = null; try { result = (HttpString) field.get(null); results.put(result.toString(), result); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } } } return results; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/http/ResponseParseState.java000066400000000000000000000051531420065311100312320ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client.http; import io.undertow.util.HttpString; /** * @author Emanuel Muckenhuber */ class ResponseParseState { //parsing states public static final int VERSION = 0; public static final int STATUS_CODE = 1; public static final int REASON_PHRASE = 2; public static final int AFTER_REASON_PHRASE = 3; public static final int HEADER = 4; public static final int HEADER_VALUE = 5; public static final int PARSE_COMPLETE = 6; /** * The actual state of request parsing */ int state; /** * The current state in the tokenizer state machine. */ int parseState; /** * If this state is a prefix or terminal match state this is set to the string * that is a candidate to be matched */ HttpString current; /** * The bytes version of {@link #current} */ byte[] currentBytes; /** * If this state is a prefix match state then this holds the current position in the string. */ int pos; /** * If this is in {@link #NO_STATE} then this holds the current token that has been read so far. */ final StringBuilder stringBuilder = new StringBuilder(); /** * This has different meanings depending on the current state. *

* In state {@link #HEADER} it is a the first character of the header, that was read by * {@link #HEADER_VALUE} to see if this was a continuation. *

* In state {@link #HEADER_VALUE} if represents the last character that was seen. */ byte leftOver; /** * This is used to store the next header value when parsing header key / value pairs, */ HttpString nextHeader; ResponseParseState() { this.parseState = 0; this.pos = 0; } public boolean isComplete() { return state == PARSE_COMPLETE; } public final void parseComplete() { state = PARSE_COMPLETE; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/http2/000077500000000000000000000000001420065311100246535ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/client/http2/Http2ClearClientProvider.java000066400000000000000000000304671420065311100323520ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client.http2; import java.io.IOException; import java.net.InetSocketAddress; import java.net.URI; import java.net.URISyntaxException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import io.undertow.client.ClientStatistics; import io.undertow.conduits.ByteActivityCallback; import io.undertow.conduits.BytesReceivedStreamSourceConduit; import io.undertow.conduits.BytesSentStreamSinkConduit; import org.xnio.ChannelListener; import org.xnio.IoFuture; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import org.xnio.StreamConnection; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.BoundChannel; import org.xnio.http.HttpUpgrade; import org.xnio.ssl.XnioSsl; import io.undertow.UndertowOptions; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientProvider; import io.undertow.protocols.http2.Http2Channel; import io.undertow.protocols.http2.Http2Setting; import io.undertow.util.FlexBase64; import io.undertow.util.Headers; /** * HTTP2 client provider that uses HTTP upgrade rather than ALPN. This provider will only use h2c, and sends an initial * dummy request to do the initial upgrade. * * * * @author Stuart Douglas */ public class Http2ClearClientProvider implements ClientProvider { @Override public void connect(final ClientCallback listener, final URI uri, final XnioWorker worker, final XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap options) { connect(listener, null, uri, worker, ssl, bufferPool, options); } @Override public void connect(final ClientCallback listener, final URI uri, final XnioIoThread ioThread, final XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap options) { connect(listener, null, uri, ioThread, ssl, bufferPool, options); } @Override public Set handlesSchemes() { return new HashSet<>(Arrays.asList(new String[]{"h2c"})); } @Override public void connect(final ClientCallback listener, InetSocketAddress bindAddress, final URI uri, final XnioWorker worker, final XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap options) { final URI upgradeUri; try { upgradeUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment()); } catch (URISyntaxException e) { listener.failed(new IOException(e)); return; } Map headers = createHeaders(options, bufferPool, uri); HttpUpgrade.performUpgrade(worker, bindAddress, upgradeUri, headers, new Http2ClearOpenListener(bufferPool, options, listener, uri.getHost()), null, options, null).addNotifier(new FailedNotifier(listener), null); } @Override public void connect(final ClientCallback listener, final InetSocketAddress bindAddress, final URI uri, final XnioIoThread ioThread, final XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap options) { final URI upgradeUri; try { upgradeUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment()); } catch (URISyntaxException e) { listener.failed(new IOException(e)); return; } if (bindAddress != null) { ioThread.openStreamConnection(bindAddress, new InetSocketAddress(uri.getHost(), uri.getPort()), new ChannelListener() { @Override public void handleEvent(StreamConnection channel) { Map headers = createHeaders(options, bufferPool, uri); HttpUpgrade.performUpgrade(channel, upgradeUri, headers, new Http2ClearOpenListener(bufferPool, options, listener, uri.getHost()), null).addNotifier(new FailedNotifier(listener), null); } }, new ChannelListener() { @Override public void handleEvent(BoundChannel channel) { } }, options).addNotifier(new FailedNotifier(listener), null); } else { ioThread.openStreamConnection(new InetSocketAddress(uri.getHost(), uri.getPort()), new ChannelListener() { @Override public void handleEvent(StreamConnection channel) { Map headers = createHeaders(options, bufferPool, uri); HttpUpgrade.performUpgrade(channel, upgradeUri, headers, new Http2ClearOpenListener(bufferPool, options, listener, uri.getHost()), null).addNotifier(new FailedNotifier(listener), null); } }, new ChannelListener() { @Override public void handleEvent(BoundChannel channel) { } }, options).addNotifier(new FailedNotifier(listener), null); } } private Map createHeaders(OptionMap options, ByteBufferPool bufferPool, URI uri) { Map headers = new HashMap<>(); headers.put("HTTP2-Settings", createSettingsFrame(options, bufferPool)); headers.put(Headers.UPGRADE_STRING, Http2Channel.CLEARTEXT_UPGRADE_STRING); headers.put(Headers.CONNECTION_STRING, "Upgrade, HTTP2-Settings"); headers.put(Headers.HOST_STRING, uri.getHost()); headers.put("X-HTTP2-connect-only", "connect"); //undertow specific header that tells the remote server that this request should be ignored return headers; } public static String createSettingsFrame(OptionMap options, ByteBufferPool bufferPool) { PooledByteBuffer b = bufferPool.allocate(); try { ByteBuffer currentBuffer = b.getBuffer(); if (options.contains(UndertowOptions.HTTP2_SETTINGS_HEADER_TABLE_SIZE)) { pushOption(currentBuffer, Http2Setting.SETTINGS_HEADER_TABLE_SIZE, options.get(UndertowOptions.HTTP2_SETTINGS_HEADER_TABLE_SIZE)); } if (options.contains(UndertowOptions.HTTP2_SETTINGS_ENABLE_PUSH)) { pushOption(currentBuffer, Http2Setting.SETTINGS_ENABLE_PUSH, options.get(UndertowOptions.HTTP2_SETTINGS_ENABLE_PUSH) ? 1 : 0); } if (options.contains(UndertowOptions.HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS)) { pushOption(currentBuffer, Http2Setting.SETTINGS_MAX_CONCURRENT_STREAMS, options.get(UndertowOptions.HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS)); } if (options.contains(UndertowOptions.HTTP2_SETTINGS_INITIAL_WINDOW_SIZE)) { pushOption(currentBuffer, Http2Setting.SETTINGS_INITIAL_WINDOW_SIZE, options.get(UndertowOptions.HTTP2_SETTINGS_INITIAL_WINDOW_SIZE)); } if (options.contains(UndertowOptions.HTTP2_SETTINGS_MAX_FRAME_SIZE)) { pushOption(currentBuffer, Http2Setting.SETTINGS_MAX_FRAME_SIZE, options.get(UndertowOptions.HTTP2_SETTINGS_MAX_FRAME_SIZE)); } if (options.contains(UndertowOptions.HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE)) { pushOption(currentBuffer, Http2Setting.SETTINGS_MAX_HEADER_LIST_SIZE, options.get(UndertowOptions.HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE)); } else if(options.contains(UndertowOptions.MAX_HEADER_SIZE)) { pushOption(currentBuffer, Http2Setting.SETTINGS_MAX_HEADER_LIST_SIZE, options.get(UndertowOptions.HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE)); } currentBuffer.flip(); return FlexBase64.encodeStringURL(currentBuffer, false); } finally { b.close(); } } private static void pushOption(ByteBuffer currentBuffer, int id, int value) { currentBuffer.put((byte) ((id >> 8) & 0xFF)); currentBuffer.put((byte) (id & 0xFF)); currentBuffer.put((byte) ((value >> 24) & 0xFF)); currentBuffer.put((byte) ((value >> 16) & 0xFF)); currentBuffer.put((byte) ((value >> 8) & 0xFF)); currentBuffer.put((byte) (value & 0xFF)); } private static class Http2ClearOpenListener implements ChannelListener { private final ByteBufferPool bufferPool; private final OptionMap options; private final ClientCallback listener; private final String defaultHost; Http2ClearOpenListener(ByteBufferPool bufferPool, OptionMap options, ClientCallback listener, String defaultHost) { this.bufferPool = bufferPool; this.options = options; this.listener = listener; this.defaultHost = defaultHost; } @Override public void handleEvent(StreamConnection channel) { final ClientStatisticsImpl clientStatistics; //first we set up statistics, if required if (options.get(UndertowOptions.ENABLE_STATISTICS, false)) { clientStatistics = new ClientStatisticsImpl(); channel.getSinkChannel().setConduit(new BytesSentStreamSinkConduit(channel.getSinkChannel().getConduit(), new ByteActivityCallback() { @Override public void activity(long bytes) { clientStatistics.written += bytes; } })); channel.getSourceChannel().setConduit(new BytesReceivedStreamSourceConduit(channel.getSourceChannel().getConduit(), new ByteActivityCallback() { @Override public void activity(long bytes) { clientStatistics.read += bytes; } })); } else { clientStatistics = null; } Http2Channel http2Channel = new Http2Channel(channel, null, bufferPool, null, true, true, options); Http2ClientConnection http2ClientConnection = new Http2ClientConnection(http2Channel, true, defaultHost, clientStatistics, false); listener.completed(http2ClientConnection); } } private static class FailedNotifier implements IoFuture.Notifier { private final ClientCallback listener; FailedNotifier(ClientCallback listener) { this.listener = listener; } @Override public void notify(IoFuture ioFuture, Object attachment) { if (ioFuture.getStatus() == IoFuture.Status.FAILED) { listener.failed(ioFuture.getException()); } } } private static class ClientStatisticsImpl implements ClientStatistics { private long requestCount, read, written; public long getRequestCount() { return requestCount; } public void setRequestCount(long requestCount) { this.requestCount = requestCount; } public void setRead(long read) { this.read = read; } public void setWritten(long written) { this.written = written; } @Override public long getRequests() { return requestCount; } @Override public long getRead() { return read; } @Override public long getWritten() { return written; } @Override public void reset() { read = 0; written = 0; requestCount = 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/http2/Http2ClientConnection.java000066400000000000000000000527221420065311100317060ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client.http2; import static io.undertow.protocols.http2.Http2Channel.AUTHORITY; import static io.undertow.protocols.http2.Http2Channel.METHOD; import static io.undertow.protocols.http2.Http2Channel.PATH; import static io.undertow.protocols.http2.Http2Channel.SCHEME; import static io.undertow.protocols.http2.Http2Channel.STATUS; import static io.undertow.util.Headers.CONTENT_LENGTH; import static io.undertow.util.Headers.TRANSFER_ENCODING; import java.io.IOException; import java.net.SocketAddress; import java.nio.channels.ClosedChannelException; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Supplier; import io.undertow.client.ClientStatistics; import io.undertow.protocols.http2.Http2DataStreamSinkChannel; import io.undertow.protocols.http2.Http2GoAwayStreamSourceChannel; import io.undertow.protocols.http2.Http2PushPromiseStreamSourceChannel; import io.undertow.server.protocol.http.HttpAttachments; import io.undertow.util.HeaderMap; import io.undertow.util.HeaderValues; import io.undertow.util.Methods; import io.undertow.util.Protocols; import org.xnio.ChannelExceptionHandler; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.Option; import io.undertow.connector.ByteBufferPool; import org.xnio.StreamConnection; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.Channels; import org.xnio.channels.StreamSinkChannel; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientExchange; import io.undertow.client.ClientRequest; import io.undertow.protocols.http2.AbstractHttp2StreamSourceChannel; import io.undertow.protocols.http2.Http2Channel; import io.undertow.protocols.http2.Http2HeadersStreamSinkChannel; import io.undertow.protocols.http2.Http2PingStreamSourceChannel; import io.undertow.protocols.http2.Http2RstStreamStreamSourceChannel; import io.undertow.protocols.http2.Http2StreamSourceChannel; import io.undertow.util.Headers; import io.undertow.util.HttpString; /** * @author Stuart Douglas */ public class Http2ClientConnection implements ClientConnection { private final Http2Channel http2Channel; private final ChannelListener.SimpleSetter closeSetter = new ChannelListener.SimpleSetter<>(); private final Map currentExchanges = new ConcurrentHashMap<>(); private static final AtomicLong PING_COUNTER = new AtomicLong(); private boolean initialUpgradeRequest; private final String defaultHost; private final ClientStatistics clientStatistics; private final List> closeListeners = new CopyOnWriteArrayList<>(); private final boolean secure; private final Map outstandingPings = new HashMap<>(); private final ChannelListener closeTask = new ChannelListener() { @Override public void handleEvent(Http2Channel channel) { ChannelListeners.invokeChannelListener(Http2ClientConnection.this, closeSetter.get()); for (ChannelListener listener : closeListeners) { listener.handleEvent(Http2ClientConnection.this); } for (Map.Entry entry : currentExchanges.entrySet()) { entry.getValue().failed(new ClosedChannelException()); } currentExchanges.clear(); } }; public Http2ClientConnection(Http2Channel http2Channel, boolean initialUpgradeRequest, String defaultHost, ClientStatistics clientStatistics, boolean secure) { this.http2Channel = http2Channel; this.defaultHost = defaultHost; this.clientStatistics = clientStatistics; this.secure = secure; http2Channel.getReceiveSetter().set(new Http2ReceiveListener()); http2Channel.resumeReceives(); http2Channel.addCloseTask(closeTask); this.initialUpgradeRequest = initialUpgradeRequest; } public Http2ClientConnection(Http2Channel http2Channel, ClientCallback upgradeReadyCallback, ClientRequest clientRequest, String defaultHost, ClientStatistics clientStatistics, boolean secure) { this.http2Channel = http2Channel; this.defaultHost = defaultHost; this.clientStatistics = clientStatistics; this.secure = secure; http2Channel.getReceiveSetter().set(new Http2ReceiveListener()); http2Channel.resumeReceives(); http2Channel.addCloseTask(closeTask); this.initialUpgradeRequest = false; Http2ClientExchange exchange = new Http2ClientExchange(this, null, clientRequest); exchange.setResponseListener(upgradeReadyCallback); currentExchanges.put(1, exchange); } @Override public void sendRequest(ClientRequest request, ClientCallback clientCallback) { if(!http2Channel.isOpen()) { clientCallback.failed(new ClosedChannelException()); return; } request.getRequestHeaders().put(METHOD, request.getMethod().toString()); boolean connectRequest = request.getMethod().equals(Methods.CONNECT); if(!connectRequest) { request.getRequestHeaders().put(PATH, request.getPath()); request.getRequestHeaders().put(SCHEME, secure ? "https" : "http"); } final String host = request.getRequestHeaders().getFirst(Headers.HOST); if(host != null) { request.getRequestHeaders().put(AUTHORITY, host); } else { request.getRequestHeaders().put(AUTHORITY, defaultHost); } request.getRequestHeaders().remove(Headers.HOST); boolean hasContent = true; String fixedLengthString = request.getRequestHeaders().getFirst(CONTENT_LENGTH); String transferEncodingString = request.getRequestHeaders().getLast(TRANSFER_ENCODING); if (fixedLengthString != null) { try { long length = Long.parseLong(fixedLengthString); hasContent = length != 0; } catch (NumberFormatException e) { handleError(new IOException(e)); return; } } else if (transferEncodingString == null && !connectRequest) { hasContent = false; } request.getRequestHeaders().remove(Headers.CONNECTION); request.getRequestHeaders().remove(Headers.KEEP_ALIVE); request.getRequestHeaders().remove(Headers.TRANSFER_ENCODING); Http2HeadersStreamSinkChannel sinkChannel; try { sinkChannel = http2Channel.createStream(request.getRequestHeaders()); } catch (Throwable t) { IOException e = t instanceof IOException ? (IOException) t : new IOException(t); clientCallback.failed(e); return; } Http2ClientExchange exchange = new Http2ClientExchange(this, sinkChannel, request); currentExchanges.put(sinkChannel.getStreamId(), exchange); sinkChannel.setTrailersProducer(new Http2DataStreamSinkChannel.TrailersProducer() { @Override public HeaderMap getTrailers() { HeaderMap attachment = exchange.getAttachment(HttpAttachments.RESPONSE_TRAILERS); Supplier supplier = exchange.getAttachment(HttpAttachments.RESPONSE_TRAILER_SUPPLIER); if(attachment != null && supplier == null) { return attachment; } else if(attachment == null && supplier != null) { return supplier.get(); } else if(attachment != null) { HeaderMap supplied = supplier.get(); for(HeaderValues k : supplied) { attachment.putAll(k.getHeaderName(), k); } return attachment; } else { return null; } } }); if(clientCallback != null) { clientCallback.completed(exchange); } if (!hasContent) { //if there is no content we flush the response channel. //otherwise it is up to the user try { sinkChannel.shutdownWrites(); if (!sinkChannel.flush()) { sinkChannel.getWriteSetter().set(ChannelListeners.flushingChannelListener(null, new ChannelExceptionHandler() { @Override public void handleException(StreamSinkChannel channel, IOException exception) { handleError(exception); } })); sinkChannel.resumeWrites(); } } catch (Throwable e) { handleError(e); } } } private void handleError(Throwable t) { IOException e = t instanceof IOException ? (IOException) t : new IOException(t); UndertowLogger.REQUEST_IO_LOGGER.ioException(e); IoUtils.safeClose(Http2ClientConnection.this); for (Map.Entry entry : currentExchanges.entrySet()) { try { entry.getValue().failed(e); } catch (Exception ex) { UndertowLogger.REQUEST_IO_LOGGER.ioException(new IOException(ex)); } } } @Override public StreamConnection performUpgrade() throws IOException { throw UndertowMessages.MESSAGES.upgradeNotSupported(); } @Override public ByteBufferPool getBufferPool() { return http2Channel.getBufferPool(); } @Override public SocketAddress getPeerAddress() { return http2Channel.getPeerAddress(); } @Override public A getPeerAddress(Class type) { return http2Channel.getPeerAddress(type); } @Override public ChannelListener.Setter getCloseSetter() { return closeSetter; } @Override public SocketAddress getLocalAddress() { return http2Channel.getLocalAddress(); } @Override public A getLocalAddress(Class type) { return http2Channel.getLocalAddress(type); } @Override public XnioWorker getWorker() { return http2Channel.getWorker(); } @Override public XnioIoThread getIoThread() { return http2Channel.getIoThread(); } @Override public boolean isOpen() { return http2Channel.isOpen() && !http2Channel.isPeerGoneAway() && !http2Channel.isThisGoneAway(); } @Override public void close() throws IOException { try { http2Channel.sendGoAway(0); } finally { for(Map.Entry entry : currentExchanges.entrySet()) { entry.getValue().failed(new ClosedChannelException()); } currentExchanges.clear(); } } @Override public boolean supportsOption(Option option) { return false; } @Override public T getOption(Option option) throws IOException { return null; } @Override public T setOption(Option option, T value) throws IllegalArgumentException, IOException { return null; } @Override public boolean isUpgraded() { return false; } @Override public boolean isPushSupported() { return true; } @Override public boolean isMultiplexingSupported() { return true; } @Override public ClientStatistics getStatistics() { return clientStatistics; } @Override public boolean isUpgradeSupported() { return false; } @Override public void addCloseListener(ChannelListener listener) { closeListeners.add(listener); } @Override public boolean isPingSupported() { return true; } @Override public void sendPing(PingListener listener, long timeout, TimeUnit timeUnit) { long count = PING_COUNTER.incrementAndGet(); byte[] data = new byte[8]; data[0] = (byte) count; data[1] = (byte)(count << 8); data[2] = (byte)(count << 16); data[3] = (byte)(count << 24); data[4] = (byte)(count << 32); data[5] = (byte)(count << 40); data[6] = (byte)(count << 48); data[7] = (byte)(count << 54); final PingKey key = new PingKey(data); outstandingPings.put(key, listener); if(timeout > 0) { http2Channel.getIoThread().executeAfter(() -> { PingListener listener1 = outstandingPings.remove(key); if(listener1 != null) { listener1.failed(UndertowMessages.MESSAGES.pingTimeout()); } }, timeout, timeUnit); } http2Channel.sendPing(data, (channel, exception) -> listener.failed(exception)); } private class Http2ReceiveListener implements ChannelListener { @Override public void handleEvent(Http2Channel channel) { try { AbstractHttp2StreamSourceChannel result = channel.receive(); if (result instanceof Http2StreamSourceChannel) { final Http2StreamSourceChannel streamSourceChannel = (Http2StreamSourceChannel) result; int statusCode = Integer.parseInt(streamSourceChannel.getHeaders().getFirst(STATUS)); Http2ClientExchange request = currentExchanges.get(streamSourceChannel.getStreamId()); if(statusCode < 200) { //this is an informational response 1xx response if(statusCode == 100) { //a continue response request.setContinueResponse(request.createResponse(streamSourceChannel)); } Channels.drain(result, Long.MAX_VALUE); return; } ((Http2StreamSourceChannel) result).setTrailersHandler(new Http2StreamSourceChannel.TrailersHandler() { @Override public void handleTrailers(HeaderMap headerMap) { request.putAttachment(HttpAttachments.REQUEST_TRAILERS, headerMap); } }); result.addCloseTask(new ChannelListener() { @Override public void handleEvent(AbstractHttp2StreamSourceChannel channel) { currentExchanges.remove(streamSourceChannel.getStreamId()); } }); streamSourceChannel.setCompletionListener(new ChannelListener() { @Override public void handleEvent(Http2StreamSourceChannel channel) { currentExchanges.remove(streamSourceChannel.getStreamId()); } }); if (request == null && initialUpgradeRequest) { Channels.drain(result, Long.MAX_VALUE); initialUpgradeRequest = false; return; } else if(request == null) { channel.sendGoAway(Http2Channel.ERROR_PROTOCOL_ERROR); IoUtils.safeClose(Http2ClientConnection.this); return; } request.responseReady(streamSourceChannel); } else if (result instanceof Http2PingStreamSourceChannel) { handlePing((Http2PingStreamSourceChannel) result); } else if (result instanceof Http2RstStreamStreamSourceChannel) { Http2RstStreamStreamSourceChannel rstStream = (Http2RstStreamStreamSourceChannel) result; int stream = rstStream.getStreamId(); UndertowLogger.REQUEST_LOGGER.debugf("Client received RST_STREAM for stream %s", stream); Http2ClientExchange exchange = currentExchanges.remove(stream); if(exchange != null) { //if we have not yet received a response we treat this as an error exchange.failed(UndertowMessages.MESSAGES.http2StreamWasReset()); } Channels.drain(result, Long.MAX_VALUE); } else if (result instanceof Http2PushPromiseStreamSourceChannel) { Http2PushPromiseStreamSourceChannel stream = (Http2PushPromiseStreamSourceChannel) result; Http2ClientExchange request = currentExchanges.get(stream.getAssociatedStreamId()); if(request == null) { channel.sendGoAway(Http2Channel.ERROR_PROTOCOL_ERROR); //according to the spec this is a connection error } else if(request.getPushCallback() == null) { channel.sendRstStream(stream.getPushedStreamId(), Http2Channel.ERROR_REFUSED_STREAM); } else { ClientRequest cr = new ClientRequest(); cr.setMethod(new HttpString(stream.getHeaders().getFirst(METHOD))); cr.setPath(stream.getHeaders().getFirst(PATH)); cr.setProtocol(Protocols.HTTP_1_1); for (HeaderValues header : stream.getHeaders()) { cr.getRequestHeaders().putAll(header.getHeaderName(), header); } Http2ClientExchange newExchange = new Http2ClientExchange(Http2ClientConnection.this, null, cr); if(!request.getPushCallback().handlePush(request, newExchange)) { // if no push handler just reset the stream channel.sendRstStream(stream.getPushedStreamId(), Http2Channel.ERROR_REFUSED_STREAM); IoUtils.safeClose(stream); } else if (!http2Channel.addPushPromiseStream(stream.getPushedStreamId())) { // if invalid stream id send connection error of type PROTOCOL_ERROR as spec channel.sendGoAway(Http2Channel.ERROR_PROTOCOL_ERROR); } else { // add the pushed stream to current exchanges currentExchanges.put(stream.getPushedStreamId(), newExchange); } } Channels.drain(result, Long.MAX_VALUE); } else if (result instanceof Http2GoAwayStreamSourceChannel) { close(); } else if(result != null) { Channels.drain(result, Long.MAX_VALUE); } } catch (Throwable t) { IOException e = t instanceof IOException ? (IOException) t : new IOException(t); UndertowLogger.REQUEST_IO_LOGGER.ioException(e); IoUtils.safeClose(Http2ClientConnection.this); for (Map.Entry entry : currentExchanges.entrySet()) { try { entry.getValue().failed(e); } catch (Throwable ex) { UndertowLogger.REQUEST_IO_LOGGER.ioException(new IOException(ex)); } } } } private void handlePing(Http2PingStreamSourceChannel frame) { byte[] id = frame.getData(); if (!frame.isAck()) { //server side ping, return it frame.getHttp2Channel().sendPing(id); } else { PingListener listener = outstandingPings.remove(new PingKey(id)); if(listener != null) { listener.acknowledged(); } } } } private static final class PingKey{ private final byte[] data; private PingKey(byte[] data) { this.data = data; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PingKey pingKey = (PingKey) o; return Arrays.equals(data, pingKey.data); } @Override public int hashCode() { return Arrays.hashCode(data); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/http2/Http2ClientExchange.java000066400000000000000000000107471420065311100313320ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client.http2; import java.io.IOException; import io.undertow.client.PushCallback; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientExchange; import io.undertow.client.ClientRequest; import io.undertow.client.ClientResponse; import io.undertow.client.ContinueNotification; import io.undertow.protocols.http2.Http2Channel; import io.undertow.protocols.http2.Http2StreamSinkChannel; import io.undertow.protocols.http2.Http2StreamSourceChannel; import io.undertow.util.AbstractAttachable; import io.undertow.util.HeaderMap; import io.undertow.util.Protocols; /** * @author Stuart Douglas */ public class Http2ClientExchange extends AbstractAttachable implements ClientExchange { private ClientCallback responseListener; private ContinueNotification continueNotification; private Http2StreamSourceChannel response; private ClientResponse clientResponse; private ClientResponse continueResponse; private final ClientConnection clientConnection; private final Http2StreamSinkChannel request; private final ClientRequest clientRequest; private IOException failedReason; private PushCallback pushCallback; public Http2ClientExchange(ClientConnection clientConnection, Http2StreamSinkChannel request, ClientRequest clientRequest) { this.clientConnection = clientConnection; this.request = request; this.clientRequest = clientRequest; } @Override public void setResponseListener(ClientCallback responseListener) { this.responseListener = responseListener; if(failedReason != null) { responseListener.failed(failedReason); } } @Override public void setContinueHandler(ContinueNotification continueHandler) { this.continueNotification = continueHandler; } void setContinueResponse(ClientResponse response) { this.continueResponse = response; if (continueNotification != null) { this.continueNotification.handleContinue(this); } } @Override public void setPushHandler(PushCallback pushCallback) { this.pushCallback = pushCallback; } PushCallback getPushCallback() { return pushCallback; } @Override public StreamSinkChannel getRequestChannel() { return request; } @Override public StreamSourceChannel getResponseChannel() { return response; } @Override public ClientRequest getRequest() { return clientRequest; } @Override public ClientResponse getResponse() { return clientResponse; } @Override public ClientResponse getContinueResponse() { return continueResponse; } @Override public ClientConnection getConnection() { return clientConnection; } void failed(final IOException e) { failedReason = e; if(responseListener != null) { responseListener.failed(e); } } void responseReady(Http2StreamSourceChannel result) { this.response = result; ClientResponse clientResponse = createResponse(result); this.clientResponse = clientResponse; if (responseListener != null) { responseListener.completed(this); } } ClientResponse createResponse(Http2StreamSourceChannel result) { HeaderMap headers = result.getHeaders(); final String status = result.getHeaders().getFirst(Http2Channel.STATUS); int statusCode = Integer.parseInt(status); headers.remove(Http2Channel.STATUS); return new ClientResponse(statusCode, status.substring(3), Protocols.HTTP_2_0, headers); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/client/http2/Http2ClientProvider.java000066400000000000000000000210451420065311100313730ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client.http2; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.UndertowOptions; import io.undertow.client.ALPNClientSelector; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientProvider; import io.undertow.client.ClientStatistics; import io.undertow.conduits.ByteActivityCallback; import io.undertow.conduits.BytesReceivedStreamSourceConduit; import io.undertow.conduits.BytesSentStreamSinkConduit; import io.undertow.connector.ByteBufferPool; import io.undertow.protocols.http2.Http2Channel; import org.xnio.ChannelListener; import org.xnio.IoFuture; import org.xnio.IoUtils; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.StreamConnection; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.ssl.SslConnection; import org.xnio.ssl.XnioSsl; import java.net.InetSocketAddress; import java.net.URI; import java.util.Arrays; import java.util.HashSet; import java.util.Set; /** * Plaintext HTTP2 client provider that works using HTTP upgrade * * @author Stuart Douglas */ public class Http2ClientProvider implements ClientProvider { private static final String HTTP2 = "h2"; private static final String HTTP_1_1 = "http/1.1"; private static final ChannelListener FAILED = new ChannelListener() { @Override public void handleEvent(SslConnection connection) { UndertowLogger.ROOT_LOGGER.alpnConnectionFailed(connection); IoUtils.safeClose(connection); } }; @Override public void connect(final ClientCallback listener, final URI uri, final XnioWorker worker, final XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap options) { connect(listener, null, uri, worker, ssl, bufferPool, options); } @Override public void connect(final ClientCallback listener, final URI uri, final XnioIoThread ioThread, final XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap options) { connect(listener, null, uri, ioThread, ssl, bufferPool, options); } @Override public Set handlesSchemes() { return new HashSet<>(Arrays.asList(new String[]{"h2"})); } @Override public void connect(final ClientCallback listener, InetSocketAddress bindAddress, final URI uri, final XnioWorker worker, final XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap options) { if (ssl == null) { listener.failed(UndertowMessages.MESSAGES.sslWasNull()); return; } OptionMap tlsOptions = OptionMap.builder().addAll(options).set(Options.SSL_STARTTLS, true).getMap(); if(bindAddress == null) { ssl.openSslConnection(worker, new InetSocketAddress(uri.getHost(), uri.getPort() == -1 ? 443 : uri.getPort()), createOpenListener(listener, uri, ssl, bufferPool, tlsOptions), tlsOptions).addNotifier(createNotifier(listener), null); } else { ssl.openSslConnection(worker, bindAddress, new InetSocketAddress(uri.getHost(), uri.getPort() == -1 ? 443 : uri.getPort()), createOpenListener(listener, uri, ssl, bufferPool, tlsOptions), tlsOptions).addNotifier(createNotifier(listener), null); } } @Override public void connect(final ClientCallback listener, InetSocketAddress bindAddress, final URI uri, final XnioIoThread ioThread, final XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap options) { if (ssl == null) { listener.failed(UndertowMessages.MESSAGES.sslWasNull()); return; } if(bindAddress == null) { OptionMap tlsOptions = OptionMap.builder().addAll(options).set(Options.SSL_STARTTLS, true).getMap(); ssl.openSslConnection(ioThread, new InetSocketAddress(uri.getHost(), uri.getPort() == -1 ? 443 : uri.getPort()), createOpenListener(listener, uri, ssl, bufferPool, tlsOptions), options).addNotifier(createNotifier(listener), null); } else { ssl.openSslConnection(ioThread, bindAddress, new InetSocketAddress(uri.getHost(), uri.getPort() == -1 ? 443 : uri.getPort()), createOpenListener(listener, uri, ssl, bufferPool, options), options).addNotifier(createNotifier(listener), null); } } private IoFuture.Notifier createNotifier(final ClientCallback listener) { return new IoFuture.Notifier() { @Override public void notify(IoFuture ioFuture, Object o) { if (ioFuture.getStatus() == IoFuture.Status.FAILED) { listener.failed(ioFuture.getException()); } } }; } private ChannelListener createOpenListener(final ClientCallback listener, final URI uri, final XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap options) { return new ChannelListener() { @Override public void handleEvent(StreamConnection connection) { handleConnected(connection, listener, uri, bufferPool, options); } }; } public static ALPNClientSelector.ALPNProtocol alpnProtocol(final ClientCallback listener, URI uri, ByteBufferPool bufferPool, OptionMap options) { return new ALPNClientSelector.ALPNProtocol(new ChannelListener() { @Override public void handleEvent(SslConnection connection) { listener.completed(createHttp2Channel(connection, bufferPool, options, uri.getHost())); } }, HTTP2); } private void handleConnected(StreamConnection connection, final ClientCallback listener, URI uri,ByteBufferPool bufferPool, OptionMap options) { ALPNClientSelector.runAlpn((SslConnection) connection, FAILED, listener, alpnProtocol(listener, uri, bufferPool, options)); } private static Http2ClientConnection createHttp2Channel(StreamConnection connection, ByteBufferPool bufferPool, OptionMap options, String defaultHost) { final ClientStatisticsImpl clientStatistics; //first we set up statistics, if required if (options.get(UndertowOptions.ENABLE_STATISTICS, false)) { clientStatistics = new ClientStatisticsImpl(); connection.getSinkChannel().setConduit(new BytesSentStreamSinkConduit(connection.getSinkChannel().getConduit(), new ByteActivityCallback() { @Override public void activity(long bytes) { clientStatistics.written += bytes; } })); connection.getSourceChannel().setConduit(new BytesReceivedStreamSourceConduit(connection.getSourceChannel().getConduit(), new ByteActivityCallback() { @Override public void activity(long bytes) { clientStatistics.read += bytes; } })); } else { clientStatistics = null; } Http2Channel http2Channel = new Http2Channel(connection, null, bufferPool, null, true, false, options); return new Http2ClientConnection(http2Channel, false, defaultHost, clientStatistics, true); } private static class ClientStatisticsImpl implements ClientStatistics { private long requestCount, read, written; @Override public long getRequests() { return requestCount; } @Override public long getRead() { return read; } @Override public long getWritten() { return written; } @Override public void reset() { read = 0; written = 0; requestCount = 0; } } } Http2PriorKnowledgeClientProvider.java000066400000000000000000000205651420065311100341760ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/client/http2/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client.http2; import io.undertow.UndertowOptions; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientProvider; import io.undertow.client.ClientStatistics; import io.undertow.conduits.ByteActivityCallback; import io.undertow.conduits.BytesReceivedStreamSourceConduit; import io.undertow.conduits.BytesSentStreamSinkConduit; import io.undertow.protocols.http2.Http2Channel; import org.xnio.ChannelListener; import org.xnio.IoFuture; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import org.xnio.StreamConnection; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.conduits.ConduitStreamSinkChannel; import org.xnio.ssl.XnioSsl; import java.io.IOException; import java.net.InetSocketAddress; import java.net.URI; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashSet; import java.util.Set; /** * HTTP2 client provider that connects to endpoints that are known to support HTTP2 * * @author Stuart Douglas */ public class Http2PriorKnowledgeClientProvider implements ClientProvider { private static final byte[] PRI_REQUEST = {'P','R','I',' ','*',' ','H','T','T','P','/','2','.','0','\r','\n','\r','\n','S','M','\r','\n','\r','\n'}; @Override public void connect(final ClientCallback listener, final URI uri, final XnioWorker worker, final XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap options) { connect(listener, null, uri, worker, ssl, bufferPool, options); } @Override public void connect(final ClientCallback listener, final URI uri, final XnioIoThread ioThread, final XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap options) { connect(listener, null, uri, ioThread, ssl, bufferPool, options); } @Override public Set handlesSchemes() { return new HashSet<>(Arrays.asList(new String[]{"h2c-prior"})); } @Override public void connect(final ClientCallback listener, InetSocketAddress bindAddress, final URI uri, final XnioWorker worker, final XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap options) { if (bindAddress == null) { worker.openStreamConnection(new InetSocketAddress(uri.getHost(), uri.getPort() == -1 ? 80 : uri.getPort()), createOpenListener(listener, bufferPool, options, uri.getHost()), options).addNotifier(createNotifier(listener), null); } else { worker.openStreamConnection(bindAddress, new InetSocketAddress(uri.getHost(), uri.getPort() == -1 ? 80 : uri.getPort()), createOpenListener(listener, bufferPool, options, uri.getHost()), null, options).addNotifier(createNotifier(listener), null); }} @Override public void connect(final ClientCallback listener, final InetSocketAddress bindAddress, final URI uri, final XnioIoThread ioThread, final XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap options) { if (bindAddress == null) { ioThread.openStreamConnection(new InetSocketAddress(uri.getHost(), uri.getPort() == -1 ? 80 : uri.getPort()), createOpenListener(listener, bufferPool, options, uri.getHost()), options).addNotifier(createNotifier(listener), null); } else { ioThread.openStreamConnection(bindAddress, new InetSocketAddress(uri.getHost(), uri.getPort() == -1 ? 80 : uri.getPort()), createOpenListener(listener, bufferPool, options, uri.getHost()), null, options).addNotifier(createNotifier(listener), null); } } private IoFuture.Notifier createNotifier(final ClientCallback listener) { return new IoFuture.Notifier() { @Override public void notify(IoFuture ioFuture, Object o) { if (ioFuture.getStatus() == IoFuture.Status.FAILED) { listener.failed(ioFuture.getException()); } } }; } private ChannelListener createOpenListener(final ClientCallback listener, final ByteBufferPool bufferPool, final OptionMap options, final String defaultHost) { return new ChannelListener() { @Override public void handleEvent(StreamConnection connection) { handleConnected(connection, listener, bufferPool, options, defaultHost); } }; } private void handleConnected(final StreamConnection connection, final ClientCallback listener, final ByteBufferPool bufferPool, final OptionMap options, final String defaultHost) { try { final ClientStatisticsImpl clientStatistics; //first we set up statistics, if required if (options.get(UndertowOptions.ENABLE_STATISTICS, false)) { clientStatistics = new ClientStatisticsImpl(); connection.getSinkChannel().setConduit(new BytesSentStreamSinkConduit(connection.getSinkChannel().getConduit(), new ByteActivityCallback() { @Override public void activity(long bytes) { clientStatistics.written += bytes; } })); connection.getSourceChannel().setConduit(new BytesReceivedStreamSourceConduit(connection.getSourceChannel().getConduit(), new ByteActivityCallback() { @Override public void activity(long bytes) { clientStatistics.read += bytes; } })); } else { clientStatistics = null; } final ByteBuffer pri = ByteBuffer.wrap(PRI_REQUEST); pri.flip(); ConduitStreamSinkChannel sink = connection.getSinkChannel(); sink.write(pri); if(pri.hasRemaining()) { sink.setWriteListener(new ChannelListener() { @Override public void handleEvent(ConduitStreamSinkChannel channel) { try { channel.write(pri); if(pri.hasRemaining()) { return; } listener.completed(new Http2ClientConnection(new Http2Channel(connection, null, bufferPool, null, true, false, options), false, defaultHost, clientStatistics, false)); } catch (Throwable t) { IOException e = t instanceof IOException ? (IOException) t : new IOException(t); listener.failed(e); } } }); return; } listener.completed(new Http2ClientConnection(new Http2Channel(connection, null, bufferPool, null, true, false, options), false, defaultHost, clientStatistics, false)); } catch (Throwable t) { IOException e = t instanceof IOException ? (IOException) t : new IOException(t); listener.failed(e); } } private static class ClientStatisticsImpl implements ClientStatistics { private long requestCount, read, written; @Override public long getRequests() { return requestCount; } @Override public long getRead() { return read; } @Override public long getWritten() { return written; } @Override public void reset() { read = 0; written = 0; requestCount = 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/000077500000000000000000000000001420065311100241645ustar00rootroot00000000000000AbstractFixedLengthStreamSinkConduit.java000066400000000000000000000260221420065311100341660ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import io.undertow.UndertowLogger; import org.xnio.Buffers; import org.xnio.channels.FixedLengthOverflowException; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.AbstractStreamSinkConduit; import org.xnio.conduits.Conduits; import org.xnio.conduits.StreamSinkConduit; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.util.concurrent.TimeUnit; import static java.lang.Math.min; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.allAreSet; import static org.xnio.Bits.anyAreSet; import static org.xnio.Bits.longBitMask; /** * A channel which writes a fixed amount of data. A listener is called once the data has been written. * * @author David M. Lloyd */ public abstract class AbstractFixedLengthStreamSinkConduit extends AbstractStreamSinkConduit { private int config; private long state; private boolean broken = false; private static final int CONF_FLAG_CONFIGURABLE = 1 << 0; private static final int CONF_FLAG_PASS_CLOSE = 1 << 1; private static final long FLAG_CLOSE_REQUESTED = 1L << 63L; private static final long FLAG_CLOSE_COMPLETE = 1L << 62L; private static final long FLAG_FINISHED_CALLED = 1L << 61L; private static final long MASK_COUNT = longBitMask(0, 60); /** * Construct a new instance. * * @param next the next channel * @param contentLength the content length * @param configurable {@code true} if this instance should pass configuration to the next * @param propagateClose {@code true} if this instance should pass close to the next */ public AbstractFixedLengthStreamSinkConduit(final StreamSinkConduit next, final long contentLength, final boolean configurable, final boolean propagateClose) { super(next); if (contentLength < 0L) { throw new IllegalArgumentException("Content length must be greater than or equal to zero"); } else if (contentLength > MASK_COUNT) { throw new IllegalArgumentException("Content length is too long"); } config = (configurable ? CONF_FLAG_CONFIGURABLE : 0) | (propagateClose ? CONF_FLAG_PASS_CLOSE : 0); this.state = contentLength; } protected void reset(long contentLength, boolean propagateClose) { this.state = contentLength; if (propagateClose) { config |= CONF_FLAG_PASS_CLOSE; } else { config &= ~CONF_FLAG_PASS_CLOSE; } } public int write(final ByteBuffer src) throws IOException { long val = state; final long remaining = val & MASK_COUNT; if (!src.hasRemaining()) { return 0; } if (allAreSet(val, FLAG_CLOSE_REQUESTED)) { throw new ClosedChannelException(); } int oldLimit = src.limit(); if (remaining == 0) { throw new FixedLengthOverflowException(); } else if (src.remaining() > remaining) { src.limit((int) (src.position() + remaining)); } int res = 0; try { return res = next.write(src); } catch (IOException | RuntimeException | Error e) { broken = true; throw e; } finally { src.limit(oldLimit); exitWrite(val, (long) res); } } public long write(final ByteBuffer[] srcs, final int offset, final int length) throws IOException { if (length == 0) { return 0L; } else if (length == 1) { return write(srcs[offset]); } long val = state; final long remaining = val & MASK_COUNT; if (allAreSet(val, FLAG_CLOSE_REQUESTED)) { throw new ClosedChannelException(); } long toWrite = Buffers.remaining(srcs, offset, length); if (remaining == 0) { throw new FixedLengthOverflowException(); } int[] limits = null; if (toWrite > remaining) { limits = new int[length]; long r = remaining; for (int i = offset; i < offset + length; ++i) { limits[i - offset] = srcs[i].limit(); int br = srcs[i].remaining(); if(br < r) { r -= br; } else { srcs[i].limit((int) (srcs[i].position() + r)); r = 0; } } } long res = 0L; try { return res = next.write(srcs, offset, length); } catch (IOException | RuntimeException | Error e) { broken = true; throw e; } finally { if (limits != null) { for (int i = offset; i < offset + length; ++i) { srcs[i].limit(limits[i - offset]); } } exitWrite(val, res); } } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { try { return Conduits.writeFinalBasic(this, srcs, offset, length); } catch (IOException | RuntimeException | Error e) { broken = true; throw e; } } @Override public int writeFinal(ByteBuffer src) throws IOException { try { return Conduits.writeFinalBasic(this, src); } catch (IOException | RuntimeException | Error e) { broken = true; throw e; } } public long transferFrom(final FileChannel src, final long position, final long count) throws IOException { if (count == 0L) return 0L; long val = state; if (allAreSet(val, FLAG_CLOSE_REQUESTED)) { throw new ClosedChannelException(); } if (allAreClear(val, MASK_COUNT)) { throw new FixedLengthOverflowException(); } long res = 0L; try { return res = next.transferFrom(src, position, min(count, (val & MASK_COUNT))); } catch (IOException | RuntimeException | Error e) { broken = true; throw e; } finally { exitWrite(val, res); } } public long transferFrom(final StreamSourceChannel source, final long count, final ByteBuffer throughBuffer) throws IOException { if (count == 0L) return 0L; long val = state; if (allAreSet(val, FLAG_CLOSE_REQUESTED)) { throw new ClosedChannelException(); } if (allAreClear(val, MASK_COUNT)) { throw new FixedLengthOverflowException(); } long res = 0L; try { return res = next.transferFrom(source, min(count, (val & MASK_COUNT)), throughBuffer); } catch (IOException | RuntimeException | Error e) { broken = true; throw e; } finally { exitWrite(val, res); } } public boolean flush() throws IOException { long val = state; if (anyAreSet(val, FLAG_CLOSE_COMPLETE)) { return true; } boolean flushed = false; try { return flushed = next.flush(); } catch (IOException | RuntimeException | Error e) { broken = true; throw e; } finally { exitFlush(val, flushed); } } public boolean isWriteResumed() { // not perfect but not provably wrong either... return allAreClear(state, FLAG_CLOSE_COMPLETE) && next.isWriteResumed(); } public void wakeupWrites() { long val = state; if (anyAreSet(val, FLAG_CLOSE_COMPLETE)) { return; } next.wakeupWrites(); } public void terminateWrites() throws IOException { final long val = enterShutdown(); if (anyAreSet(val, MASK_COUNT) && !broken) { UndertowLogger.REQUEST_IO_LOGGER.debugf("Fixed length stream closed with with %s bytes remaining", val & MASK_COUNT); try { next.truncateWrites(); } finally { if (!anyAreSet(state, FLAG_FINISHED_CALLED)) { state |= FLAG_FINISHED_CALLED; channelFinished(); } } } else if (allAreSet(config, CONF_FLAG_PASS_CLOSE)) { next.terminateWrites(); } } @Override public void truncateWrites() throws IOException { try { if (!anyAreSet(state, FLAG_FINISHED_CALLED)) { state |= FLAG_FINISHED_CALLED; channelFinished(); } } finally { super.truncateWrites(); } } public void awaitWritable() throws IOException { next.awaitWritable(); } public void awaitWritable(final long time, final TimeUnit timeUnit) throws IOException { next.awaitWritable(time, timeUnit); } /** * Get the number of remaining bytes in this fixed length channel. * * @return the number of remaining bytes */ public long getRemaining() { return state & MASK_COUNT; } private void exitWrite(long oldVal, long consumed) { long newVal = oldVal - consumed; state = newVal; } private void exitFlush(long oldVal, boolean flushed) { long newVal = oldVal; boolean callFinish = false; if ((anyAreSet(oldVal, FLAG_CLOSE_REQUESTED) || (newVal & MASK_COUNT) == 0L) && flushed) { newVal |= FLAG_CLOSE_COMPLETE; if (!anyAreSet(oldVal, FLAG_FINISHED_CALLED) && (newVal & MASK_COUNT) == 0L) { newVal |= FLAG_FINISHED_CALLED; callFinish = true; } state = newVal; if (callFinish) { channelFinished(); } } } protected void channelFinished() { } private long enterShutdown() { long oldVal, newVal; oldVal = state; if (anyAreSet(oldVal, FLAG_CLOSE_REQUESTED | FLAG_CLOSE_COMPLETE)) { // no action necessary return oldVal; } newVal = oldVal | FLAG_CLOSE_REQUESTED; if (anyAreSet(oldVal, MASK_COUNT)) { // error: channel not filled. set both close flags. newVal |= FLAG_CLOSE_COMPLETE; } state = newVal; return oldVal; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/AbstractFramedStreamSinkConduit.java000066400000000000000000000231011420065311100332350ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import io.undertow.UndertowMessages; import org.xnio.Buffers; import org.xnio.IoUtils; import io.undertow.connector.PooledByteBuffer; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.AbstractStreamSinkConduit; import org.xnio.conduits.ConduitWritableByteChannel; import org.xnio.conduits.Conduits; import org.xnio.conduits.StreamSinkConduit; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.ArrayDeque; import java.util.Deque; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.anyAreSet; /** * Utility class to ease the implementation of framed protocols. This call provides a queue of frames, and a callback * that can be invoked when a frame event occurs. *

* When a write takes place all frames are attempted to be written out at once via a gathering write. Frames can be * queued via {@link #queueFrame(io.undertow.conduits.AbstractFramedStreamSinkConduit.FrameCallBack, java.nio.ByteBuffer...)}. * * @author Stuart Douglas */ public class AbstractFramedStreamSinkConduit extends AbstractStreamSinkConduit { private final Deque frameQueue = new ArrayDeque<>(); /** * The total amount of data that has been queued to be written out */ private long queuedData = 0; /** * The total number of buffers that have been queued to be written out */ private int bufferCount = 0; private int state; private static final int FLAG_WRITES_TERMINATED = 1; private static final int FLAG_DELEGATE_SHUTDOWN = 2; /** * Construct a new instance. * * @param next the delegate conduit to set */ protected AbstractFramedStreamSinkConduit(StreamSinkConduit next) { super(next); } /** * Queues a frame for sending. * * @param callback * @param data */ protected void queueFrame(FrameCallBack callback, ByteBuffer... data) { queuedData += Buffers.remaining(data); bufferCount += data.length; frameQueue.add(new Frame(callback, data, 0, data.length)); } public long transferFrom(final FileChannel src, final long position, final long count) throws IOException { return src.transferTo(position, count, new ConduitWritableByteChannel(this)); } public long transferFrom(final StreamSourceChannel source, final long count, final ByteBuffer throughBuffer) throws IOException { return IoUtils.transfer(source, count, throughBuffer, new ConduitWritableByteChannel(this)); } @Override public int write(ByteBuffer src) throws IOException { if (anyAreSet(state, FLAG_WRITES_TERMINATED)) { throw UndertowMessages.MESSAGES.channelIsClosed(); } return (int) doWrite(new ByteBuffer[]{src}, 0, 1); } @Override public long write(ByteBuffer[] srcs, int offs, int len) throws IOException { if (anyAreSet(state, FLAG_WRITES_TERMINATED)) { throw UndertowMessages.MESSAGES.channelIsClosed(); } return doWrite(srcs, offs, len); } @Override public int writeFinal(ByteBuffer src) throws IOException { return Conduits.writeFinalBasic(this, src); } @Override public long writeFinal(ByteBuffer[] srcs, int offs, int len) throws IOException { return Conduits.writeFinalBasic(this, srcs, offs, len); } private long doWrite(ByteBuffer[] additionalData, int offs, int len) throws IOException { ByteBuffer[] buffers = new ByteBuffer[bufferCount + (additionalData == null ? 0 : len)]; int count = 0; for (Frame frame : frameQueue) { for (int i = frame.offs; i < frame.offs + frame.len; ++i) { buffers[count++] = frame.data[i]; } } if (additionalData != null) { for (int i = offs; i < offs + len; ++i) { buffers[count++] = additionalData[i]; } } try { long written = next.write(buffers, 0, buffers.length); if (written > this.queuedData) { this.queuedData = 0; } else { this.queuedData -= written; } long toAllocate = written; Frame frame = frameQueue.peek(); while (frame != null) { if (frame.remaining > toAllocate) { frame.remaining -= toAllocate; return 0; } else { frameQueue.poll(); //this frame is done, remove it //note that after we start calling done() we can't re-use the buffers[] array //as pooled buffers may have been returned to the pool and re-used FrameCallBack cb = frame.callback; if (cb != null) { cb.done(); } bufferCount -= frame.len; toAllocate -= frame.remaining; } frame = frameQueue.peek(); } return toAllocate; } catch (IOException | RuntimeException | Error e) { IOException ioe = e instanceof IOException ? (IOException) e : new IOException(e); //on exception we fail every item in the frame queue try { for (Frame frame : frameQueue) { FrameCallBack cb = frame.callback; if (cb != null) { cb.failed(ioe); } } frameQueue.clear(); bufferCount = 0; queuedData = 0; } finally { throw e; } } } protected long queuedDataLength() { return queuedData; } @Override public void terminateWrites() throws IOException { if (anyAreSet(state, FLAG_WRITES_TERMINATED)) { return; } queueCloseFrames(); state |= FLAG_WRITES_TERMINATED; if (queuedData == 0) { state |= FLAG_DELEGATE_SHUTDOWN; doTerminateWrites(); finished(); } } protected void doTerminateWrites() throws IOException { next.terminateWrites(); } protected boolean flushQueuedData() throws IOException { if (queuedData > 0) { doWrite(null, 0, 0); } if (queuedData > 0) { return false; } if (anyAreSet(state, FLAG_WRITES_TERMINATED) && allAreClear(state, FLAG_DELEGATE_SHUTDOWN)) { doTerminateWrites(); state |= FLAG_DELEGATE_SHUTDOWN; finished(); } return next.flush(); } @Override public void truncateWrites() throws IOException { for (Frame frame : frameQueue) { FrameCallBack cb = frame.callback; if (cb != null) { cb.failed(UndertowMessages.MESSAGES.channelIsClosed()); } } } protected boolean isWritesTerminated() { return anyAreSet(state, FLAG_WRITES_TERMINATED); } protected void queueCloseFrames() { } protected void finished() { } /** * Interface that is called when a frame event takes place. The events are: *

*

    *
  • * Done - The fame has been written out *
  • *
  • * Failed - The frame write failed *
  • *
*/ public interface FrameCallBack { void done(); void failed(final IOException e); } private static class Frame { final FrameCallBack callback; final ByteBuffer[] data; final int offs; final int len; long remaining; private Frame(FrameCallBack callback, ByteBuffer[] data, int offs, int len) { this.callback = callback; this.data = data; this.offs = offs; this.len = len; this.remaining = Buffers.remaining(data, offs, len); } } protected static class PooledBufferFrameCallback implements FrameCallBack { private final PooledByteBuffer buffer; public PooledBufferFrameCallback(PooledByteBuffer buffer) { this.buffer = buffer; } @Override public void done() { buffer.close(); } @Override public void failed(IOException e) { buffer.close(); } } protected static class PooledBuffersFrameCallback implements FrameCallBack { private final PooledByteBuffer[] buffers; public PooledBuffersFrameCallback(PooledByteBuffer... buffers) { this.buffers = buffers; } @Override public void done() { for (PooledByteBuffer buffer : buffers) { buffer.close(); } } @Override public void failed(IOException e) { done(); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/BrokenStreamSourceConduit.java000066400000000000000000000037751420065311100321460ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import org.xnio.channels.StreamSinkChannel; import org.xnio.conduits.AbstractStreamSourceConduit; import org.xnio.conduits.StreamSourceConduit; /** * @author Stuart Douglas */ public class BrokenStreamSourceConduit extends AbstractStreamSourceConduit { private final IOException exception; /** * Construct a new instance. * * @param next the delegate conduit to set * @param exception */ public BrokenStreamSourceConduit(final StreamSourceConduit next, final IOException exception) { super(next); this.exception = exception; } @Override public long transferTo(final long position, final long count, final FileChannel target) throws IOException { throw exception; } @Override public long transferTo(final long count, final ByteBuffer throughBuffer, final StreamSinkChannel target) throws IOException { throw exception; } @Override public long read(final ByteBuffer[] dsts, final int offset, final int length) throws IOException { throw exception; } @Override public int read(final ByteBuffer dst) throws IOException { throw exception; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/ByteActivityCallback.java000066400000000000000000000016511420065311100310670ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; /** * Callback that allows the bytes read from or written to a stream to be tracked * * @author Stuart Douglas */ public interface ByteActivityCallback { void activity(long bytes); } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/BytesReceivedStreamSourceConduit.java000066400000000000000000000045761420065311100334630ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import org.xnio.channels.StreamSinkChannel; import org.xnio.conduits.AbstractStreamSourceConduit; import org.xnio.conduits.StreamSourceConduit; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; /** * @author Stuart Douglas */ public class BytesReceivedStreamSourceConduit extends AbstractStreamSourceConduit { private final ByteActivityCallback callback; /** * Construct a new instance. * * @param next the delegate conduit to set * @param callback */ public BytesReceivedStreamSourceConduit(StreamSourceConduit next, ByteActivityCallback callback) { super(next); this.callback = callback; } @Override public long transferTo(long position, long count, FileChannel target) throws IOException { long l = super.transferTo(position, count, target); if (l > 0) { callback.activity(l); } return l; } @Override public long transferTo(long count, ByteBuffer throughBuffer, StreamSinkChannel target) throws IOException { long l = super.transferTo(count, throughBuffer, target); if (l > 0) { callback.activity(l); } return l; } @Override public int read(ByteBuffer dst) throws IOException { int i = super.read(dst); if (i > 0) { callback.activity(i); } return i; } @Override public long read(ByteBuffer[] dsts, int offs, int len) throws IOException { long l = super.read(dsts, offs, len); if (l > 0) { callback.activity(l); } return l; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/BytesSentStreamSinkConduit.java000066400000000000000000000054661420065311100323110ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.AbstractStreamSinkConduit; import org.xnio.conduits.StreamSinkConduit; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; /** * @author Stuart Douglas */ public class BytesSentStreamSinkConduit extends AbstractStreamSinkConduit { private final ByteActivityCallback callback; /** * Construct a new instance. * * @param next the delegate conduit to set * @param callback */ public BytesSentStreamSinkConduit(StreamSinkConduit next, ByteActivityCallback callback) { super(next); this.callback = callback; } @Override public long transferFrom(FileChannel src, long position, long count) throws IOException { long l = next.transferFrom(src, position, count); if (l > 0) { callback.activity(l); } return l; } @Override public long transferFrom(StreamSourceChannel source, long count, ByteBuffer throughBuffer) throws IOException { long l = next.transferFrom(source, count, throughBuffer); if (l > 0) { callback.activity(l); } return l; } @Override public int write(ByteBuffer src) throws IOException { int i = next.write(src); if (i > 0) { callback.activity(i); } return i; } @Override public long write(ByteBuffer[] srcs, int offs, int len) throws IOException { long l = next.write(srcs, offs, len); if (l > 0) { callback.activity(l); } return l; } @Override public int writeFinal(ByteBuffer src) throws IOException { int i = next.writeFinal(src); if (i > 0) { callback.activity(i); } return i; } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { long l = next.writeFinal(srcs, offset, length); if (l > 0) { callback.activity(l); } return l; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/ChunkReader.java000066400000000000000000000234241420065311100272270ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.anyAreSet; import static org.xnio.Bits.longBitMask; import java.io.IOException; import java.nio.ByteBuffer; import org.xnio.conduits.Conduit; import io.undertow.UndertowMessages; import io.undertow.util.Attachable; import io.undertow.util.AttachmentKey; import io.undertow.util.HeaderMap; import io.undertow.util.HttpString; /** * Utility class for reading chunked streams. * * @author Stuart Douglas */ class ChunkReader { private static final long FLAG_FINISHED = 1L << 62L; private static final long FLAG_READING_LENGTH = 1L << 61L; private static final long FLAG_READING_TILL_END_OF_LINE = 1L << 60L; private static final long FLAG_READING_NEWLINE = 1L << 59L; private static final long FLAG_READING_AFTER_LAST = 1L << 58L; private static final long MASK_COUNT = longBitMask(0, 56); private static final long LIMIT = Long.MAX_VALUE >> 4; private long state; private final Attachable attachable; private final AttachmentKey trailerAttachmentKey; /** * The trailer parser that stores the trailer parse state. If this class is not null it means * that we are in the middle of parsing trailers. */ private TrailerParser trailerParser; private final T conduit; ChunkReader(final Attachable attachable, final AttachmentKey trailerAttachmentKey, T conduit) { this.attachable = attachable; this.trailerAttachmentKey = trailerAttachmentKey; this.conduit = conduit; this.state = FLAG_READING_LENGTH; } public long readChunk(final ByteBuffer buf) throws IOException { long oldVal = state; long chunkRemaining = state & MASK_COUNT; if (chunkRemaining > 0 && !anyAreSet(state, FLAG_READING_AFTER_LAST | FLAG_READING_LENGTH | FLAG_READING_NEWLINE | FLAG_READING_TILL_END_OF_LINE)) { return chunkRemaining; } long newVal = oldVal & ~MASK_COUNT; try { if (anyAreSet(oldVal, FLAG_READING_AFTER_LAST)) { int ret = handleChunkedRequestEnd(buf); if (ret == -1) { newVal |= FLAG_FINISHED & ~FLAG_READING_AFTER_LAST; return -1; } return 0; } while (anyAreSet(newVal, FLAG_READING_NEWLINE)) { while (buf.hasRemaining()) { byte b = buf.get(); if (b == '\n') { newVal = newVal & ~FLAG_READING_NEWLINE | FLAG_READING_LENGTH; break; } } if (anyAreSet(newVal, FLAG_READING_NEWLINE)) { return 0; } } while (anyAreSet(newVal, FLAG_READING_LENGTH)) { while (buf.hasRemaining()) { byte b = buf.get(); if ((b >= '0' && b <= '9') || (b >= 'a' && b <= 'f') || (b >= 'A' && b <= 'F')) { if (chunkRemaining > LIMIT) { throw UndertowMessages.MESSAGES.chunkSizeTooLarge(); } chunkRemaining <<= 4; //shift it 4 bytes and then add the next value to the end chunkRemaining += Character.digit((char) b, 16); } else { if (b == '\n') { newVal = newVal & ~FLAG_READING_LENGTH; } else { newVal = newVal & ~FLAG_READING_LENGTH | FLAG_READING_TILL_END_OF_LINE; } break; } } if (anyAreSet(newVal, FLAG_READING_LENGTH)) { return 0; } } while (anyAreSet(newVal, FLAG_READING_TILL_END_OF_LINE)) { while (buf.hasRemaining()) { if (buf.get() == '\n') { newVal = newVal & ~FLAG_READING_TILL_END_OF_LINE; break; } } if (anyAreSet(newVal, FLAG_READING_TILL_END_OF_LINE)) { return 0; } } //we have our chunk size, check to make sure it was not the last chunk if (allAreClear(newVal, FLAG_READING_NEWLINE | FLAG_READING_LENGTH | FLAG_READING_TILL_END_OF_LINE) && chunkRemaining == 0) { newVal |= FLAG_READING_AFTER_LAST; int ret = handleChunkedRequestEnd(buf); if (ret == -1) { newVal |= FLAG_FINISHED & ~FLAG_READING_AFTER_LAST; return -1; } return 0; } return chunkRemaining; } finally { state = newVal | chunkRemaining; } } public long getChunkRemaining() { if (anyAreSet(state, FLAG_FINISHED)) { return -1; } if (anyAreSet(state, FLAG_READING_LENGTH | FLAG_READING_TILL_END_OF_LINE | FLAG_READING_NEWLINE | FLAG_READING_AFTER_LAST)) { return 0; } return state & MASK_COUNT; } public void setChunkRemaining(final long remaining) { if (remaining < 0 || anyAreSet(state, FLAG_READING_LENGTH | FLAG_READING_TILL_END_OF_LINE | FLAG_READING_NEWLINE | FLAG_READING_AFTER_LAST)) { return; } long old = state; long oldRemaining = old & MASK_COUNT; if (remaining == 0 && oldRemaining != 0) { //if oldRemaining is zero it could be that no data has been read yet //and the correct state is READING_LENGTH old |= FLAG_READING_NEWLINE; } state = (old & ~MASK_COUNT) | remaining; } private int handleChunkedRequestEnd(ByteBuffer buffer) throws IOException { if (trailerParser != null) { return trailerParser.handle(buffer); } while (buffer.hasRemaining()) { byte b = buffer.get(); if (b == '\n') { return -1; } else if (b != '\r') { buffer.position(buffer.position() - 1); trailerParser = new TrailerParser(); return trailerParser.handle(buffer); } } return 0; } /** * Class that parses HTTP trailers. We don't just re-use the http parser code because it is complicated enough * already, and this is not used very often so the performance benefits should not matter. */ private final class TrailerParser { private HeaderMap headerMap = new HeaderMap(); private StringBuilder builder = new StringBuilder(); private HttpString httpString; int state = 0; private static final int STATE_TRAILER_NAME = 0; private static final int STATE_TRAILER_VALUE = 1; private static final int STATE_ENDING = 2; public int handle(ByteBuffer buf) throws IOException { while (buf.hasRemaining()) { final byte b = buf.get(); if (state == STATE_TRAILER_NAME) { if (b == '\r') { if (builder.length() == 0) { state = STATE_ENDING; } else { throw UndertowMessages.MESSAGES.couldNotDecodeTrailers(); } } else if (b == '\n') { if (builder.length() == 0) { attachable.putAttachment(trailerAttachmentKey, headerMap); return -1; } else { throw UndertowMessages.MESSAGES.couldNotDecodeTrailers(); } } else if (b == ':') { httpString = HttpString.tryFromString(builder.toString().trim()); state = STATE_TRAILER_VALUE; builder.setLength(0); } else { builder.append((char) b); } } else if (state == STATE_TRAILER_VALUE) { if (b == '\n') { headerMap.put(httpString, builder.toString().trim()); httpString = null; builder.setLength(0); state = STATE_TRAILER_NAME; } else if (b != '\r') { builder.append((char) b); } } else if (state == STATE_ENDING) { if (b == '\n') { if (attachable != null) { attachable.putAttachment(trailerAttachmentKey, headerMap); } return -1; } else { throw UndertowMessages.MESSAGES.couldNotDecodeTrailers(); } } else { throw new IllegalStateException(); } } return 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/ChunkedStreamSinkConduit.java000066400000000000000000000374641420065311100317550ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import io.undertow.UndertowLogger; import io.undertow.server.protocol.http.HttpAttachments; import io.undertow.util.Attachable; import io.undertow.util.AttachmentKey; import io.undertow.util.HeaderMap; import io.undertow.util.HeaderValues; import io.undertow.util.Headers; import io.undertow.util.ImmediatePooledByteBuffer; import org.xnio.IoUtils; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.AbstractStreamSinkConduit; import org.xnio.conduits.ConduitWritableByteChannel; import org.xnio.conduits.Conduits; import org.xnio.conduits.StreamSinkConduit; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.anyAreSet; /** * Channel that implements HTTP chunked transfer coding. * * @author Stuart Douglas */ public class ChunkedStreamSinkConduit extends AbstractStreamSinkConduit { /** * Trailers that are to be attached to the end of the HTTP response. Note that it is the callers responsibility * to make sure the client understands trailers (i.e. they have provided a TE header), and to set the 'Trailers:' * header appropriately. *

* This attachment must be set before the {@link #terminateWrites()} method is called. */ @Deprecated public static final AttachmentKey TRAILERS = HttpAttachments.RESPONSE_TRAILERS; private final HeaderMap responseHeaders; private final ConduitListener finishListener; private final int config; private final ByteBufferPool bufferPool; /** * "0\r\n" as bytes in US ASCII encoding. */ private static final byte[] LAST_CHUNK = new byte[] {(byte) 48, (byte) 13, (byte) 10}; /** * "\r\n" as bytes in US ASCII encoding. */ private static final byte[] CRLF = new byte[] {(byte) 13, (byte) 10}; private final Attachable attachable; private int state; private int chunkleft = 0; private final ByteBuffer chunkingBuffer = ByteBuffer.allocate(12); //12 is the most private final ByteBuffer chunkingSepBuffer; private PooledByteBuffer lastChunkBuffer; private static final int CONF_FLAG_CONFIGURABLE = 1 << 0; private static final int CONF_FLAG_PASS_CLOSE = 1 << 1; /** * Flag that is set when {@link #terminateWrites()} or @{link #close()} is called */ private static final int FLAG_WRITES_SHUTDOWN = 1; private static final int FLAG_NEXT_SHUTDOWN = 1 << 2; private static final int FLAG_WRITTEN_FIRST_CHUNK = 1 << 3; private static final int FLAG_FIRST_DATA_WRITTEN = 1 << 4; //set on first flush or write call private static final int FLAG_FINISHED = 1 << 5; /** * Construct a new instance. * * @param next the channel to wrap * @param configurable {@code true} to allow configuration of the next channel, {@code false} otherwise * @param passClose {@code true} to close the underlying channel when this channel is closed, {@code false} otherwise * @param responseHeaders The response headers * @param finishListener The finish listener * @param attachable The attachable */ public ChunkedStreamSinkConduit(final StreamSinkConduit next, final ByteBufferPool bufferPool, final boolean configurable, final boolean passClose, HeaderMap responseHeaders, final ConduitListener finishListener, final Attachable attachable) { super(next); this.bufferPool = bufferPool; this.responseHeaders = responseHeaders; this.finishListener = finishListener; this.attachable = attachable; config = (configurable ? CONF_FLAG_CONFIGURABLE : 0) | (passClose ? CONF_FLAG_PASS_CLOSE : 0); chunkingSepBuffer = ByteBuffer.allocate(2); chunkingSepBuffer.flip(); } @Override public int write(final ByteBuffer src) throws IOException { return doWrite(src); } int doWrite(final ByteBuffer src) throws IOException { if (anyAreSet(state, FLAG_WRITES_SHUTDOWN)) { throw new ClosedChannelException(); } if(src.remaining() == 0) { return 0; } this.state |= FLAG_FIRST_DATA_WRITTEN; int oldLimit = src.limit(); boolean dataRemaining = false; //set to true if there is data in src that still needs to be written out if (chunkleft == 0 && !chunkingSepBuffer.hasRemaining()) { chunkingBuffer.clear(); putIntAsHexString(chunkingBuffer, src.remaining()); chunkingBuffer.put(CRLF); chunkingBuffer.flip(); chunkingSepBuffer.clear(); chunkingSepBuffer.put(CRLF); chunkingSepBuffer.flip(); state |= FLAG_WRITTEN_FIRST_CHUNK; chunkleft = src.remaining(); } else { if (src.remaining() > chunkleft) { dataRemaining = true; src.limit(chunkleft + src.position()); } } try { int chunkingSize = chunkingBuffer.remaining(); int chunkingSepSize = chunkingSepBuffer.remaining(); if (chunkingSize > 0 || chunkingSepSize > 0 || lastChunkBuffer != null) { int originalRemaining = src.remaining(); long result; if (lastChunkBuffer == null || dataRemaining) { final ByteBuffer[] buf = new ByteBuffer[]{chunkingBuffer, src, chunkingSepBuffer}; result = next.write(buf, 0, buf.length); } else { final ByteBuffer[] buf = new ByteBuffer[]{chunkingBuffer, src, lastChunkBuffer.getBuffer()}; if (anyAreSet(state, CONF_FLAG_PASS_CLOSE)) { result = next.writeFinal(buf, 0, buf.length); } else { result = next.write(buf, 0, buf.length); } if (!src.hasRemaining()) { state |= FLAG_WRITES_SHUTDOWN; } if (!lastChunkBuffer.getBuffer().hasRemaining()) { state |= FLAG_NEXT_SHUTDOWN; lastChunkBuffer.close(); } } int srcWritten = originalRemaining - src.remaining(); chunkleft -= srcWritten; if (result < chunkingSize) { return 0; } else { return srcWritten; } } else { int result = next.write(src); chunkleft -= result; return result; } } finally { src.limit(oldLimit); } } @Override public void truncateWrites() throws IOException { try { if (lastChunkBuffer != null) { lastChunkBuffer.close(); } if (allAreClear(state, FLAG_FINISHED)) { invokeFinishListener(); } } finally { super.truncateWrites(); } } @Override public long write(final ByteBuffer[] srcs, final int offset, final int length) throws IOException { for (int i = offset; i < length; ++i) { if (srcs[i].hasRemaining()) { return write(srcs[i]); } } return 0; } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { return Conduits.writeFinalBasic(this, srcs, offset, length); } @Override public int writeFinal(ByteBuffer src) throws IOException { //todo: we could optimise this to just set a content length if no data has been written if(!src.hasRemaining()) { terminateWrites(); return 0; } if (lastChunkBuffer == null) { createLastChunk(true); } return doWrite(src); } @Override public long transferFrom(final FileChannel src, final long position, final long count) throws IOException { if (anyAreSet(state, FLAG_WRITES_SHUTDOWN)) { throw new ClosedChannelException(); } return src.transferTo(position, count, new ConduitWritableByteChannel(this)); } @Override public long transferFrom(final StreamSourceChannel source, final long count, final ByteBuffer throughBuffer) throws IOException { if (anyAreSet(state, FLAG_WRITES_SHUTDOWN)) { throw new ClosedChannelException(); } return IoUtils.transfer(source, count, throughBuffer, new ConduitWritableByteChannel(this)); } @Override public boolean flush() throws IOException { this.state |= FLAG_FIRST_DATA_WRITTEN; if (anyAreSet(state, FLAG_WRITES_SHUTDOWN)) { if (anyAreSet(state, FLAG_NEXT_SHUTDOWN)) { boolean val = next.flush(); if (val && allAreClear(state, FLAG_FINISHED)) { invokeFinishListener(); } return val; } else { next.write(lastChunkBuffer.getBuffer()); if (!lastChunkBuffer.getBuffer().hasRemaining()) { lastChunkBuffer.close(); if (anyAreSet(config, CONF_FLAG_PASS_CLOSE)) { next.terminateWrites(); } state |= FLAG_NEXT_SHUTDOWN; boolean val = next.flush(); if (val && allAreClear(state, FLAG_FINISHED)) { invokeFinishListener(); } return val; } else { return false; } } } else { return next.flush(); } } private void invokeFinishListener() { state |= FLAG_FINISHED; if (finishListener != null) { finishListener.handleEvent(this); } } @Override public void terminateWrites() throws IOException { if(anyAreSet(state, FLAG_WRITES_SHUTDOWN)) { return; } if (this.chunkleft != 0) { UndertowLogger.REQUEST_IO_LOGGER.debugf("Channel closed mid-chunk"); next.truncateWrites(); } if (!anyAreSet(state, FLAG_FIRST_DATA_WRITTEN)) { //if no data was actually sent we just remove the transfer encoding header, and set content length 0 //TODO: is this the best way to do it? //todo: should we make this behaviour configurable? responseHeaders.put(Headers.CONTENT_LENGTH, "0"); //according to the spec we don't actually need this, but better to be safe responseHeaders.remove(Headers.TRANSFER_ENCODING); state |= FLAG_NEXT_SHUTDOWN | FLAG_WRITES_SHUTDOWN; if(anyAreSet(state, CONF_FLAG_PASS_CLOSE)) { next.terminateWrites(); } } else { createLastChunk(false); state |= FLAG_WRITES_SHUTDOWN; } } private void createLastChunk(final boolean writeFinal) throws UnsupportedEncodingException { PooledByteBuffer lastChunkBufferPooled = bufferPool.allocate(); ByteBuffer lastChunkBuffer = lastChunkBufferPooled.getBuffer(); if (writeFinal) { lastChunkBuffer.put(CRLF); } else if(chunkingSepBuffer.hasRemaining()) { //the end of chunk /r/n has not been written yet //just add it to this buffer to make managing state easier lastChunkBuffer.put(chunkingSepBuffer); } lastChunkBuffer.put(LAST_CHUNK); //we just assume it will fit HeaderMap attachment = attachable.getAttachment(HttpAttachments.RESPONSE_TRAILERS); final HeaderMap trailers; Supplier supplier = attachable.getAttachment(HttpAttachments.RESPONSE_TRAILER_SUPPLIER); if(attachment != null && supplier == null) { trailers = attachment; } else if(attachment == null && supplier != null) { trailers = supplier.get(); } else if(attachment != null) { HeaderMap supplied = supplier.get(); for(HeaderValues k : supplied) { attachment.putAll(k.getHeaderName(), k); } trailers = attachment; } else { trailers = null; } if (trailers != null && trailers.size() != 0) { for (HeaderValues trailer : trailers) { for (String val : trailer) { trailer.getHeaderName().appendTo(lastChunkBuffer); lastChunkBuffer.put((byte) ':'); lastChunkBuffer.put((byte) ' '); lastChunkBuffer.put(val.getBytes(StandardCharsets.US_ASCII)); lastChunkBuffer.put(CRLF); } } lastChunkBuffer.put(CRLF); } else { lastChunkBuffer.put(CRLF); } //horrible hack //there is a situation where we can get a buffer leak here if the connection is terminated abnormaly //this should be fixed once this channel has its lifecycle tied to the connection, same as fixed length lastChunkBuffer.flip(); ByteBuffer data = ByteBuffer.allocate(lastChunkBuffer.remaining()); data.put(lastChunkBuffer); data.flip(); this.lastChunkBuffer = new ImmediatePooledByteBuffer(data); lastChunkBufferPooled.close(); } @Override public void awaitWritable() throws IOException { next.awaitWritable(); } @Override public void awaitWritable(final long time, final TimeUnit timeUnit) throws IOException { next.awaitWritable(time, timeUnit); } private static void putIntAsHexString(final ByteBuffer buf, final int v) { byte int3 = (byte) (v >> 24); byte int2 = (byte) (v >> 16); byte int1 = (byte) (v >> 8); byte int0 = (byte) (v ); boolean nonZeroFound = false; if (int3 != 0) { buf.put(DIGITS[(0xF0 & int3) >>> 4]) .put(DIGITS[0x0F & int3]); nonZeroFound = true; } if (nonZeroFound || int2 != 0) { buf.put(DIGITS[(0xF0 & int2) >>> 4]) .put(DIGITS[0x0F & int2]); nonZeroFound = true; } if (nonZeroFound || int1 != 0) { buf.put(DIGITS[(0xF0 & int1) >>> 4]) .put(DIGITS[0x0F & int1]); } buf.put(DIGITS[(0xF0 & int0) >>> 4]) .put(DIGITS[0x0F & int0]); } /** * hexadecimal digits "0123456789abcdef" as bytes in US ASCII encoding. */ private static final byte[] DIGITS = new byte[] { (byte) 48, (byte) 49, (byte) 50, (byte) 51, (byte) 52, (byte) 53, (byte) 54, (byte) 55, (byte) 56, (byte) 57, (byte) 97, (byte) 98, (byte) 99, (byte) 100, (byte) 101, (byte) 102}; } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/ChunkedStreamSourceConduit.java000066400000000000000000000277121420065311100323040ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import io.undertow.UndertowMessages; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.Connectors; import io.undertow.server.HttpServerExchange; import io.undertow.server.protocol.http.HttpAttachments; import io.undertow.server.protocol.http.HttpServerConnection; import io.undertow.util.Attachable; import io.undertow.util.AttachmentKey; import io.undertow.util.HeaderMap; import io.undertow.util.PooledAdaptor; import org.xnio.IoUtils; import org.xnio.channels.StreamSinkChannel; import org.xnio.conduits.AbstractStreamSourceConduit; import org.xnio.conduits.ConduitReadableByteChannel; import org.xnio.conduits.PushBackStreamSourceConduit; import org.xnio.conduits.StreamSourceConduit; import java.io.Closeable; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; /** * Channel to de-chunkify data * * @author Stuart Douglas */ public class ChunkedStreamSourceConduit extends AbstractStreamSourceConduit { /** * If the response has HTTP footers they are attached to the exchange under this key. They will only be available once the exchange has been fully read. */ @Deprecated public static final AttachmentKey TRAILERS = HttpAttachments.REQUEST_TRAILERS; private final BufferWrapper bufferWrapper; private final ConduitListener finishListener; private final HttpServerExchange exchange; private final Closeable closeable; private boolean closed; private boolean finishListenerInvoked; private long remainingAllowed; private final ChunkReader chunkReader; private final PushBackStreamSourceConduit channel; public ChunkedStreamSourceConduit(final StreamSourceConduit next, final PushBackStreamSourceConduit channel, final ByteBufferPool pool, final ConduitListener finishListener, Attachable attachable, Closeable closeable) { this(next, new BufferWrapper() { @Override public PooledByteBuffer allocate() { return pool.allocate(); } @Override public void pushBack(PooledByteBuffer pooled) { channel.pushBack(new PooledAdaptor(pooled)); } }, finishListener, attachable, null, closeable, channel); } public ChunkedStreamSourceConduit(final StreamSourceConduit next, final HttpServerExchange exchange, final ConduitListener finishListener) { this(next, new BufferWrapper() { @Override public PooledByteBuffer allocate() { return exchange.getConnection().getByteBufferPool().allocate(); } @Override public void pushBack(PooledByteBuffer pooled) { ((HttpServerConnection) exchange.getConnection()).ungetRequestBytes(pooled); } }, finishListener, exchange, exchange, exchange.getConnection(), null); } protected ChunkedStreamSourceConduit(final StreamSourceConduit next, final BufferWrapper bufferWrapper, final ConduitListener finishListener, final Attachable attachable, final HttpServerExchange exchange, final Closeable closeable, PushBackStreamSourceConduit channel) { super(next); this.bufferWrapper = bufferWrapper; this.finishListener = finishListener; this.remainingAllowed = Long.MIN_VALUE; this.chunkReader = new ChunkReader<>(attachable, HttpAttachments.REQUEST_TRAILERS, this); this.exchange = exchange; this.closeable = closeable; this.channel = channel; } public long transferTo(final long position, final long count, final FileChannel target) throws IOException { try { return target.transferFrom(new ConduitReadableByteChannel(this), position, count); } catch (IOException | RuntimeException | Error e) { IoUtils.safeClose(closeable); throw e; } } private void updateRemainingAllowed(final int written) throws IOException { if (remainingAllowed == Long.MIN_VALUE) { if (exchange == null) { return; } else { long maxEntitySize = exchange.getMaxEntitySize(); if (maxEntitySize <= 0) { return; } remainingAllowed = maxEntitySize; } } remainingAllowed -= written; if (remainingAllowed < 0) { //max entity size is exceeded Connectors.terminateRequest(exchange); closed = true; exchange.setPersistent(false); finishListener.handleEvent(this); throw UndertowMessages.MESSAGES.requestEntityWasTooLarge(exchange.getMaxEntitySize()); } } @Override public long transferTo(final long count, final ByteBuffer throughBuffer, final StreamSinkChannel target) throws IOException { try { return IoUtils.transfer(new ConduitReadableByteChannel(this), count, throughBuffer, target); } catch (IOException | RuntimeException | Error e) { IoUtils.safeClose(closeable); throw e; } } @Override public long read(final ByteBuffer[] dsts, final int offset, final int length) throws IOException { for (int i = offset; i < length; ++i) { if (dsts[i].hasRemaining()) { return read(dsts[i]); } } return 0; } @Override public void terminateReads() throws IOException { super.terminateReads(); if (channel != null) channel.terminateReads(); if (!isFinished()) { exchange.setPersistent(false); super.terminateReads(); throw UndertowMessages.MESSAGES.chunkedChannelClosedMidChunk(); } } @Override public int read(final ByteBuffer dst) throws IOException { boolean invokeFinishListener = false; try { long chunkRemaining = chunkReader.getChunkRemaining(); //we have read the last chunk, we just return EOF if (chunkRemaining == -1) { if(!finishListenerInvoked) { invokeFinishListener = true; } return -1; } if (closed) { throw new ClosedChannelException(); } PooledByteBuffer pooled = bufferWrapper.allocate(); ByteBuffer buf = pooled.getBuffer(); boolean free = true; try { //we need to do our initial read into a int r = next.read(buf); buf.flip(); if (r == -1) { //Channel is broken, not sure how best to report it throw new ClosedChannelException(); } else if (r == 0) { return 0; } if (chunkRemaining == 0) { chunkRemaining = chunkReader.readChunk(buf); if (chunkRemaining <= 0) { if(buf.hasRemaining()) { free = false; } if(!finishListenerInvoked && chunkRemaining < 0) { invokeFinishListener = true; } return (int) chunkRemaining; } } final int originalLimit = dst.limit(); try { //now we may have some stuff in the raw buffer //or the raw buffer may be exhausted, and we should read directly into the destination buffer //from the next int read = 0; long chunkInBuffer = Math.min(buf.remaining(), chunkRemaining); int remaining = dst.remaining(); if (chunkInBuffer > remaining) { //it won't fit int orig = buf.limit(); buf.limit(buf.position() + remaining); dst.put(buf); buf.limit(orig); chunkRemaining -= remaining; updateRemainingAllowed(remaining); free = false; return remaining; } else if (buf.hasRemaining()) { int old = buf.limit(); buf.limit((int) Math.min(old, buf.position() + chunkInBuffer)); try { dst.put(buf); } finally { buf.limit(old); } read += chunkInBuffer; chunkRemaining -= chunkInBuffer; } //there is still more to read //we attempt to just read it directly into the destination buffer //adjusting the limit as necessary to make sure we do not read too much if (chunkRemaining > 0) { int old = dst.limit(); try { if (chunkRemaining < dst.remaining()) { dst.limit((int) (dst.position() + chunkRemaining)); } int c = 0; do { c = next.read(dst); if (c > 0) { read += c; chunkRemaining -= c; } } while (c > 0 && chunkRemaining > 0); if (c == -1) { throw new ClosedChannelException(); } } finally { dst.limit(old); } } else { free = false; } updateRemainingAllowed(read); return read; } finally { //buffer will be freed if not needed in exitRead dst.limit(originalLimit); } } finally { if (chunkRemaining >= 0) { chunkReader.setChunkRemaining(chunkRemaining); } if (!free && buf.hasRemaining()) { bufferWrapper.pushBack(pooled); } else { pooled.close(); } } } catch (IOException | RuntimeException | Error e) { IoUtils.safeClose(closeable); throw e; } finally { if(invokeFinishListener) { finishListenerInvoked = true; finishListener.handleEvent(this); } } } public boolean isFinished() { return closed || chunkReader.getChunkRemaining() == -1; } interface BufferWrapper { PooledByteBuffer allocate(); void pushBack(PooledByteBuffer pooled); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/ConduitListener.java000066400000000000000000000020461420065311100301440ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import java.util.EventListener; import org.xnio.conduits.Conduit; /** * @author Stuart Douglas */ public interface ConduitListener extends EventListener { /** * Handle the event on this conduit. * * @param channel the channel event */ void handleEvent(T channel); } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/DebuggingStreamSinkConduit.java000066400000000000000000000070641420065311100322600ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import org.xnio.Buffers; import org.xnio.IoUtils; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.AbstractStreamSinkConduit; import org.xnio.conduits.ConduitWritableByteChannel; import org.xnio.conduits.Conduits; import org.xnio.conduits.StreamSinkConduit; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** * Conduit that saves all the data that is written through it and can dump it to the console *

* Obviously this should not be used in production. * * @author Stuart Douglas */ public class DebuggingStreamSinkConduit extends AbstractStreamSinkConduit { private static final List data = new CopyOnWriteArrayList<>(); /** * Construct a new instance. * * @param next the delegate conduit to set */ public DebuggingStreamSinkConduit(StreamSinkConduit next) { super(next); } @Override public int write(ByteBuffer src) throws IOException { int pos = src.position(); int res = super.write(src); if (res > 0) { byte[] d = new byte[res]; for (int i = 0; i < res; ++i) { d[i] = src.get(i + pos); } data.add(d); } return res; } @Override public long write(ByteBuffer[] dsts, int offs, int len) throws IOException { for (int i = offs; i < len; ++i) { if (dsts[i].hasRemaining()) { return write(dsts[i]); } } return 0; } @Override public long transferFrom(final FileChannel src, final long position, final long count) throws IOException { return src.transferTo(position, count, new ConduitWritableByteChannel(this)); } @Override public long transferFrom(final StreamSourceChannel source, final long count, final ByteBuffer throughBuffer) throws IOException { return IoUtils.transfer(source, count, throughBuffer, new ConduitWritableByteChannel(this)); } @Override public int writeFinal(ByteBuffer src) throws IOException { return Conduits.writeFinalBasic(this, src); } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { return Conduits.writeFinalBasic(this, srcs, offset, length); } public static void dump() { for (int i = 0; i < data.size(); ++i) { System.out.println("Write Buffer " + i); StringBuilder sb = new StringBuilder(); try { Buffers.dump(ByteBuffer.wrap(data.get(i)), sb, 0, 20); } catch (IOException e) { throw new RuntimeException(e); } System.out.println(sb); System.out.println(); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/DebuggingStreamSourceConduit.java000066400000000000000000000062741420065311100326160ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import org.xnio.Buffers; import org.xnio.IoUtils; import org.xnio.channels.StreamSinkChannel; import org.xnio.conduits.AbstractStreamSourceConduit; import org.xnio.conduits.ConduitReadableByteChannel; import org.xnio.conduits.StreamSourceConduit; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** * Conduit that saves all the data that is written through it and can dump it to the console *

* Obviously this should not be used in production. * * @author Stuart Douglas */ public class DebuggingStreamSourceConduit extends AbstractStreamSourceConduit { private static final List data = new CopyOnWriteArrayList<>(); /** * Construct a new instance. * * @param next the delegate conduit to set */ public DebuggingStreamSourceConduit(StreamSourceConduit next) { super(next); } public long transferTo(final long position, final long count, final FileChannel target) throws IOException { return target.transferFrom(new ConduitReadableByteChannel(this), position, count); } public long transferTo(final long count, final ByteBuffer throughBuffer, final StreamSinkChannel target) throws IOException { return IoUtils.transfer(new ConduitReadableByteChannel(this), count, throughBuffer, target); } @Override public int read(ByteBuffer dst) throws IOException { int pos = dst.position(); int res = super.read(dst); if (res > 0) { byte[] d = new byte[res]; for (int i = 0; i < res; ++i) { d[i] = dst.get(i + pos); } data.add(d); } return res; } @Override public long read(ByteBuffer[] dsts, int offs, int len) throws IOException { for (int i = offs; i < len; ++i) { if (dsts[i].hasRemaining()) { return read(dsts[i]); } } return 0; } public static void dump() { for (int i = 0; i < data.size(); ++i) { System.out.println("Buffer " + i); StringBuilder sb = new StringBuilder(); try { Buffers.dump(ByteBuffer.wrap(data.get(i)), sb, 0, 20); } catch (IOException e) { throw new RuntimeException(e); } System.out.println(sb); System.out.println(); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/DeflatingStreamSinkConduit.java000066400000000000000000000467201420065311100322640ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.allAreSet; import static org.xnio.Bits.anyAreSet; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.util.concurrent.TimeUnit; import java.util.zip.Deflater; import io.undertow.server.Connectors; import org.xnio.IoUtils; import io.undertow.connector.PooledByteBuffer; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.ConduitWritableByteChannel; import org.xnio.conduits.Conduits; import org.xnio.conduits.StreamSinkConduit; import org.xnio.conduits.WriteReadyHandler; import io.undertow.UndertowLogger; import io.undertow.server.HttpServerExchange; import io.undertow.util.ConduitFactory; import io.undertow.util.NewInstanceObjectPool; import io.undertow.util.ObjectPool; import io.undertow.util.Headers; import io.undertow.util.PooledObject; import io.undertow.util.SimpleObjectPool; /** * Channel that handles deflate compression * * @author Stuart Douglas */ public class DeflatingStreamSinkConduit implements StreamSinkConduit { protected volatile Deflater deflater; protected final PooledObject pooledObject; private final ConduitFactory conduitFactory; private final HttpServerExchange exchange; private StreamSinkConduit next; private WriteReadyHandler writeReadyHandler; /** * The streams buffer. This is freed when the next is shutdown */ protected PooledByteBuffer currentBuffer; /** * there may have been some additional data that did not fit into the first buffer */ private ByteBuffer additionalBuffer; private int state = 0; private static final int SHUTDOWN = 1; private static final int NEXT_SHUTDOWN = 1 << 1; private static final int FLUSHING_BUFFER = 1 << 2; private static final int WRITES_RESUMED = 1 << 3; private static final int CLOSED = 1 << 4; private static final int WRITTEN_TRAILER = 1 << 5; public DeflatingStreamSinkConduit(final ConduitFactory conduitFactory, final HttpServerExchange exchange) { this(conduitFactory, exchange, Deflater.DEFLATED); } public DeflatingStreamSinkConduit(final ConduitFactory conduitFactory, final HttpServerExchange exchange, int deflateLevel) { this(conduitFactory, exchange, newInstanceDeflaterPool(deflateLevel)); } public DeflatingStreamSinkConduit(final ConduitFactory conduitFactory, final HttpServerExchange exchange, ObjectPool deflaterPool) { this.pooledObject = deflaterPool.allocate(); this.deflater = pooledObject.getObject(); this.currentBuffer = exchange.getConnection().getByteBufferPool().allocate(); this.exchange = exchange; this.conduitFactory = conduitFactory; setWriteReadyHandler(new WriteReadyHandler.ChannelListenerHandler<>(Connectors.getConduitSinkChannel(exchange))); } public static ObjectPool newInstanceDeflaterPool(int deflateLevel) { return new NewInstanceObjectPool(() -> new Deflater(deflateLevel, true), Deflater::end); } public static ObjectPool simpleDeflaterPool(int poolSize, int deflateLevel) { return new SimpleObjectPool(poolSize, () -> new Deflater(deflateLevel, true), Deflater::reset, Deflater::end); } @Override public int write(final ByteBuffer src) throws IOException { if (anyAreSet(state, SHUTDOWN | CLOSED) || currentBuffer == null) { throw new ClosedChannelException(); } try { if (!performFlushIfRequired()) { return 0; } if (src.remaining() == 0) { return 0; } //we may already have some input, if so compress it if (!deflater.needsInput()) { deflateData(false); if (!deflater.needsInput()) { return 0; } } byte[] data = new byte[src.remaining()]; src.get(data); preDeflate(data); deflater.setInput(data); Connectors.updateResponseBytesSent(exchange, 0 - data.length); deflateData(false); return data.length; } catch (IOException | RuntimeException | Error e) { freeBuffer(); throw e; } } protected void preDeflate(byte[] data) { } @Override public long write(final ByteBuffer[] srcs, final int offset, final int length) throws IOException { if (anyAreSet(state, SHUTDOWN | CLOSED) || currentBuffer == null) { throw new ClosedChannelException(); } try { int total = 0; for (int i = offset; i < offset + length; ++i) { if (srcs[i].hasRemaining()) { int ret = write(srcs[i]); total += ret; if (ret == 0) { return total; } } } return total; } catch (IOException | RuntimeException | Error e) { freeBuffer(); throw e; } } @Override public int writeFinal(ByteBuffer src) throws IOException { return Conduits.writeFinalBasic(this, src); } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { return Conduits.writeFinalBasic(this, srcs, offset, length); } @Override public long transferFrom(final FileChannel src, final long position, final long count) throws IOException { if (anyAreSet(state, SHUTDOWN | CLOSED)) { throw new ClosedChannelException(); } if (!performFlushIfRequired()) { return 0; } return src.transferTo(position, count, new ConduitWritableByteChannel(this)); } @Override public long transferFrom(final StreamSourceChannel source, final long count, final ByteBuffer throughBuffer) throws IOException { if (anyAreSet(state, SHUTDOWN | CLOSED)) { throw new ClosedChannelException(); } if (!performFlushIfRequired()) { return 0; } return IoUtils.transfer(source, count, throughBuffer, new ConduitWritableByteChannel(this)); } @Override public XnioWorker getWorker() { return exchange.getConnection().getWorker(); } @Override public void suspendWrites() { if (next == null) { state = state & ~WRITES_RESUMED; } else { next.suspendWrites(); } } @Override public boolean isWriteResumed() { if (next == null) { return anyAreSet(state, WRITES_RESUMED); } else { return next.isWriteResumed(); } } @Override public void wakeupWrites() { if (next == null) { resumeWrites(); } else { next.wakeupWrites(); } } @Override public void resumeWrites() { if (next == null) { state |= WRITES_RESUMED; queueWriteListener(); } else { next.resumeWrites(); } } private void queueWriteListener() { exchange.getConnection().getIoThread().execute(new Runnable() { @Override public void run() { if (writeReadyHandler != null) { try { writeReadyHandler.writeReady(); } finally { //if writes are still resumed queue up another one if (next == null && isWriteResumed()) { queueWriteListener(); } } } } }); } @Override public void terminateWrites() throws IOException { if (deflater != null) { deflater.finish(); } state |= SHUTDOWN; } @Override public boolean isWriteShutdown() { return anyAreSet(state, SHUTDOWN); } @Override public void awaitWritable() throws IOException { if (next == null) { return; } else { next.awaitWritable(); } } @Override public void awaitWritable(final long time, final TimeUnit timeUnit) throws IOException { if (next == null) { return; } else { next.awaitWritable(time, timeUnit); } } @Override public XnioIoThread getWriteThread() { return exchange.getConnection().getIoThread(); } @Override public void setWriteReadyHandler(final WriteReadyHandler handler) { this.writeReadyHandler = handler; } @Override public boolean flush() throws IOException { if (currentBuffer == null) { if (anyAreSet(state, NEXT_SHUTDOWN)) { return next.flush(); } else { return true; } } try { boolean nextCreated = false; try { if (anyAreSet(state, SHUTDOWN)) { if (anyAreSet(state, NEXT_SHUTDOWN)) { return next.flush(); } else { if (!performFlushIfRequired()) { return false; } //if the deflater has not been fully flushed we need to flush it if (!deflater.finished()) { deflateData(false); //if could not fully flush if (!deflater.finished()) { return false; } } final ByteBuffer buffer = currentBuffer.getBuffer(); if (allAreClear(state, WRITTEN_TRAILER)) { state |= WRITTEN_TRAILER; byte[] data = getTrailer(); if (data != null) { Connectors.updateResponseBytesSent(exchange, data.length); if(additionalBuffer != null) { byte[] newData = new byte[additionalBuffer.remaining() + data.length]; int pos = 0; while (additionalBuffer.hasRemaining()) { newData[pos++] = additionalBuffer.get(); } for (byte aData : data) { newData[pos++] = aData; } this.additionalBuffer = ByteBuffer.wrap(newData); } else if(anyAreSet(state, FLUSHING_BUFFER) && buffer.capacity() - buffer.remaining() >= data.length) { buffer.compact(); buffer.put(data); buffer.flip(); } else if (data.length <= buffer.remaining() && !anyAreSet(state, FLUSHING_BUFFER)) { buffer.put(data); } else { additionalBuffer = ByteBuffer.wrap(data); } } } //ok the deflater is flushed, now we need to flush the buffer if (!anyAreSet(state, FLUSHING_BUFFER)) { buffer.flip(); state |= FLUSHING_BUFFER; if (next == null) { nextCreated = true; this.next = createNextChannel(); } } if (performFlushIfRequired()) { state |= NEXT_SHUTDOWN; freeBuffer(); next.terminateWrites(); return next.flush(); } else { return false; } } } else { if(allAreClear(state, FLUSHING_BUFFER)) { if (next == null) { nextCreated = true; this.next = createNextChannel(); } deflateData(true); if(allAreClear(state, FLUSHING_BUFFER)) { //deflateData can cause this to be change currentBuffer.getBuffer().flip(); this.state |= FLUSHING_BUFFER; } } if(!performFlushIfRequired()) { return false; } return next.flush(); } } finally { if (nextCreated) { if (anyAreSet(state, WRITES_RESUMED) && !anyAreSet(state ,NEXT_SHUTDOWN)) { try { next.resumeWrites(); } catch (Throwable e) { UndertowLogger.REQUEST_LOGGER.debug("Failed to resume", e); } } } } } catch (IOException | RuntimeException | Error e) { freeBuffer(); throw e; } } /** * called before the stream is finally flushed. */ protected byte[] getTrailer() { return null; } /** * The we are in the flushing state then we flush to the underlying stream, otherwise just return true * * @return false if there is still more to flush */ private boolean performFlushIfRequired() throws IOException { if (anyAreSet(state, FLUSHING_BUFFER)) { final ByteBuffer[] bufs = new ByteBuffer[additionalBuffer == null ? 1 : 2]; long totalLength = 0; bufs[0] = currentBuffer.getBuffer(); totalLength += bufs[0].remaining(); if (additionalBuffer != null) { bufs[1] = additionalBuffer; totalLength += bufs[1].remaining(); } if (totalLength > 0) { long total = 0; long res = 0; do { res = next.write(bufs, 0, bufs.length); total += res; if (res == 0) { return false; } } while (total < totalLength); } additionalBuffer = null; currentBuffer.getBuffer().clear(); state = state & ~FLUSHING_BUFFER; } return true; } private StreamSinkConduit createNextChannel() { if (deflater.finished() && allAreSet(state, WRITTEN_TRAILER)) { //the deflater was fully flushed before we created the channel. This means that what is in the buffer is //all there is int remaining = currentBuffer.getBuffer().remaining(); if (additionalBuffer != null) { remaining += additionalBuffer.remaining(); } if(!exchange.getResponseHeaders().contains(Headers.TRANSFER_ENCODING)) { exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, Integer.toString(remaining)); } } else { exchange.getResponseHeaders().remove(Headers.CONTENT_LENGTH); } return conduitFactory.create(); } /** * Runs the current data through the deflater. As much as possible this will be buffered in the current output * stream. * * @throws IOException */ private void deflateData(boolean force) throws IOException { //we don't need to flush here, as this should have been called already by the time we get to //this point boolean nextCreated = false; try (PooledByteBuffer arrayPooled = this.exchange.getConnection().getByteBufferPool().getArrayBackedPool().allocate()) { PooledByteBuffer pooled = this.currentBuffer; final ByteBuffer outputBuffer = pooled.getBuffer(); final boolean shutdown = anyAreSet(state, SHUTDOWN); ByteBuffer buf = arrayPooled.getBuffer(); while (force || !deflater.needsInput() || (shutdown && !deflater.finished())) { int count = deflater.deflate(buf.array(), buf.arrayOffset(), buf.remaining(), force ? Deflater.SYNC_FLUSH: Deflater.NO_FLUSH); Connectors.updateResponseBytesSent(exchange, count); if (count != 0) { int remaining = outputBuffer.remaining(); if (remaining > count) { outputBuffer.put(buf.array(), buf.arrayOffset(), count); } else { if (remaining == count) { outputBuffer.put(buf.array(), buf.arrayOffset(), count); } else { outputBuffer.put(buf.array(), buf.arrayOffset(), remaining); additionalBuffer = ByteBuffer.allocate(count - remaining); additionalBuffer.put(buf.array(), buf.arrayOffset() + remaining, count - remaining); additionalBuffer.flip(); } outputBuffer.flip(); this.state |= FLUSHING_BUFFER; if (next == null) { nextCreated = true; this.next = createNextChannel(); } if (!performFlushIfRequired()) { return; } } } else { force = false; } } } finally { if (nextCreated) { if (anyAreSet(state, WRITES_RESUMED)) { next.resumeWrites(); } } } } @Override public void truncateWrites() throws IOException { freeBuffer(); state |= CLOSED; next.truncateWrites(); } private void freeBuffer() { if (currentBuffer != null) { currentBuffer.close(); currentBuffer = null; state = state & ~FLUSHING_BUFFER; } if (deflater != null) { deflater = null; pooledObject.close(); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/EmptyStreamSourceConduit.java000066400000000000000000000071501420065311100320130ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.concurrent.TimeUnit; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.StreamSinkChannel; import org.xnio.conduits.ReadReadyHandler; import org.xnio.conduits.StreamSourceConduit; /** * A stream source conduit which is always empty. * * Temporary copy from XNIO, see https://issues.jboss.org/browse/XNIO-199 * * @author David M. Lloyd */ public final class EmptyStreamSourceConduit implements StreamSourceConduit { private final XnioWorker worker; private final XnioIoThread readThread; private ReadReadyHandler readReadyHandler; private boolean shutdown; private boolean resumed; /** * Construct a new instance. * * @param readThread the read thread for this conduit */ public EmptyStreamSourceConduit(final XnioIoThread readThread) { this.worker = readThread.getWorker(); this.readThread = readThread; } public void setReadReadyHandler(final ReadReadyHandler handler) { readReadyHandler = handler; } public long transferTo(final long position, final long count, final FileChannel target) throws IOException { return 0; } public long transferTo(final long count, final ByteBuffer throughBuffer, final StreamSinkChannel target) throws IOException { resumed = false; return -1L; } public int read(final ByteBuffer dst) throws IOException { resumed = false; return -1; } public long read(final ByteBuffer[] dsts, final int offs, final int len) throws IOException { resumed = false; return -1L; } public boolean isReadShutdown() { return shutdown; } public void resumeReads() { resumed = true; readThread.execute(new Runnable() { public void run() { final ReadReadyHandler handler = readReadyHandler; if (handler != null) { handler.readReady(); } } }); } public void suspendReads() { resumed = false; } public void wakeupReads() { resumeReads(); } public boolean isReadResumed() { return resumed; } public void awaitReadable() throws IOException { // always ready } public void awaitReadable(final long time, final TimeUnit timeUnit) throws IOException { // always ready } public void terminateReads() throws IOException { if (! shutdown) { shutdown = true; if(readReadyHandler != null) { readReadyHandler.terminated(); } } } public XnioIoThread getReadThread() { return readThread; } public XnioWorker getWorker() { return worker; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/FinishableStreamSinkConduit.java000066400000000000000000000053511420065311100324260ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import java.io.IOException; import java.nio.ByteBuffer; import org.xnio.Buffers; import org.xnio.conduits.AbstractStreamSinkConduit; import org.xnio.conduits.StreamSinkConduit; /** * @author David M. Lloyd */ public final class FinishableStreamSinkConduit extends AbstractStreamSinkConduit { private final ConduitListener finishListener; //0 = open //1 = writes shutdown //2 = finish listener invoked private int shutdownState = 0; public FinishableStreamSinkConduit(final StreamSinkConduit delegate, final ConduitListener finishListener) { super(delegate); this.finishListener = finishListener; } @Override public int writeFinal(ByteBuffer src) throws IOException { int res = next.writeFinal(src); if(!src.hasRemaining()) { if (shutdownState == 0) { shutdownState = 1; } } return res; } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { long res = next.writeFinal(srcs, offset, length); if(!Buffers.hasRemaining(srcs, offset, length)) { if (shutdownState == 0) { shutdownState = 1; } } return res; } public void terminateWrites() throws IOException { super.terminateWrites(); if (shutdownState == 0) { shutdownState = 1; } } @Override public void truncateWrites() throws IOException { next.truncateWrites(); if (shutdownState != 2) { shutdownState = 2; finishListener.handleEvent(this); } } public boolean flush() throws IOException { final boolean val = next.flush(); if (val && shutdownState == 1) { shutdownState = 2; finishListener.handleEvent(this); } return val; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/FinishableStreamSourceConduit.java000066400000000000000000000056461420065311100327710ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import org.xnio.channels.StreamSinkChannel; import org.xnio.conduits.AbstractStreamSourceConduit; import org.xnio.conduits.StreamSourceConduit; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; /** * A conduit that calls a finish listener when there is no data left in the underlying conduit. * * @author Stuart Douglas */ public final class FinishableStreamSourceConduit extends AbstractStreamSourceConduit { private final ConduitListener finishListener; private boolean finishCalled = false; public FinishableStreamSourceConduit(final StreamSourceConduit next, final ConduitListener finishListener) { super(next); this.finishListener = finishListener; } public long transferTo(final long position, final long count, final FileChannel target) throws IOException { long res = 0; try { return res = next.transferTo(position, count, target); } finally { exitRead(res); } } public long transferTo(final long count, final ByteBuffer throughBuffer, final StreamSinkChannel target) throws IOException { long res = 0; try { return res = next.transferTo(count, throughBuffer, target); } finally { exitRead(res); } } public long read(final ByteBuffer[] dsts, final int offset, final int length) throws IOException { long res = 0; try { return res = next.read(dsts, offset, length); } finally { exitRead(res); } } public int read(final ByteBuffer dst) throws IOException { int res = 0; try { return res = next.read(dst); } finally { exitRead(res); } } /** * Exit a read method. * * @param consumed the number of bytes consumed by this call (may be 0) */ private void exitRead(long consumed) { if (consumed == -1) { if (!finishCalled) { finishCalled = true; finishListener.handleEvent(this); } } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/FixedLengthStreamSourceConduit.java000066400000000000000000000336771420065311100331330ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import io.undertow.UndertowMessages; import io.undertow.server.Connectors; import io.undertow.server.HttpServerExchange; import org.xnio.IoUtils; import org.xnio.channels.StreamSinkChannel; import org.xnio.conduits.AbstractStreamSourceConduit; import org.xnio.conduits.StreamSourceConduit; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.concurrent.TimeUnit; import static java.lang.Math.min; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.allAreSet; import static org.xnio.Bits.anyAreClear; import static org.xnio.Bits.anyAreSet; import static org.xnio.Bits.longBitMask; /** * A channel which reads data of a fixed length and calls a finish listener. When the finish listener is called, * it should examine the result of {@link #getRemaining()} to see if more bytes were pending when the channel was * closed. * * @author David M. Lloyd * @author Flavia Rainone */ /* * Implementation notes * -------------------- * The {@code exhausted} flag is set once a method returns -1 and signifies that the read listener should no longer be * called. The {@code finishListener} is called when remaining is reduced to 0 or when the channel is closed explicitly. * If there are 0 remaining bytes but {@code FLAG_FINISHED} has not yet been set, the channel is considered "ready" until * the EOF -1 value is read or the channel is closed. Since this is a half-duplex channel, shutting down reads is * identical to closing the channel. */ public final class FixedLengthStreamSourceConduit extends AbstractStreamSourceConduit { private final ConduitListener finishListener; @SuppressWarnings("unused") private long state; private static final long FLAG_CLOSED = 1L << 63L; private static final long FLAG_FINISHED = 1L << 62L; private static final long FLAG_LENGTH_CHECKED = 1L << 61L; private static final long MASK_COUNT = longBitMask(0, 60); private final HttpServerExchange exchange; /** * Construct a new instance. The given listener is called once all the bytes are read from the stream * or the stream is closed. This listener should cause the remaining data to be drained from the * underlying stream if the underlying stream is to be reused. *

* Calling this constructor will replace the read listener of the underlying channel. The listener should be * restored from the {@code finishListener} object. The underlying stream should not be closed while this wrapper * stream is active. * * @param next the stream source channel to read from * @param contentLength the amount of content to read * @param finishListener the listener to call once the stream is exhausted or closed * @param exchange The server exchange. This is used to determine the max size */ public FixedLengthStreamSourceConduit(final StreamSourceConduit next, final long contentLength, final ConduitListener finishListener, final HttpServerExchange exchange) { super(next); this.finishListener = finishListener; if (contentLength < 0L) { throw new IllegalArgumentException("Content length must be greater than or equal to zero"); } else if (contentLength > MASK_COUNT) { throw new IllegalArgumentException("Content length is too long"); } state = contentLength; this.exchange = exchange; } /** * Construct a new instance. The given listener is called once all the bytes are read from the stream * or the stream is closed. This listener should cause the remaining data to be drained from the * underlying stream if the underlying stream is to be reused. *

* Calling this constructor will replace the read listener of the underlying channel. The listener should be * restored from the {@code finishListener} object. The underlying stream should not be closed while this wrapper * stream is active. * * @param next the stream source channel to read from * @param contentLength the amount of content to read * @param finishListener the listener to call once the stream is exhausted or closed */ public FixedLengthStreamSourceConduit(final StreamSourceConduit next, final long contentLength, final ConduitListener finishListener) { this(next, contentLength, finishListener, null); } public long transferTo(final long position, final long count, final FileChannel target) throws IOException { long val = state; checkMaxSize(val); if (anyAreSet(val, FLAG_CLOSED | FLAG_FINISHED) || allAreClear(val, MASK_COUNT)) { if (allAreClear(val, FLAG_FINISHED)) { invokeFinishListener(); } return -1L; } long res = 0L; Throwable transferError = null; try { return res = next.transferTo(position, min(count, val & MASK_COUNT), target); } catch (IOException | RuntimeException | Error e) { closeConnection(); transferError = e; throw e; } finally { exitRead(res, transferError); } } public long transferTo(final long count, final ByteBuffer throughBuffer, final StreamSinkChannel target) throws IOException { if (count == 0L) { return 0L; } long val = state; checkMaxSize(val); if (anyAreSet(val, FLAG_CLOSED | FLAG_FINISHED) || allAreClear(val, MASK_COUNT)) { if (allAreClear(val, FLAG_FINISHED)) { invokeFinishListener(); } return -1; } long res = 0L; Throwable transferError = null; try { return res = next.transferTo(min(count, val & MASK_COUNT), throughBuffer, target); } catch (IOException | RuntimeException | Error e) { closeConnection(); transferError = e; throw e; } finally { exitRead(res + throughBuffer.remaining(), transferError); } } private void checkMaxSize(long state) throws IOException { if (anyAreClear(state, FLAG_LENGTH_CHECKED)) { HttpServerExchange exchange = this.exchange; if (exchange != null) { if (exchange.getMaxEntitySize() > 0 && exchange.getMaxEntitySize() < (state & MASK_COUNT)) { //max entity size is exceeded //we need to forcibly close the read side Connectors.terminateRequest(exchange); exchange.setPersistent(false); finishListener.handleEvent(this); this.state |= FLAG_FINISHED | FLAG_CLOSED; throw UndertowMessages.MESSAGES.requestEntityWasTooLarge(exchange.getMaxEntitySize()); } } this.state |= FLAG_LENGTH_CHECKED; } } public long read(final ByteBuffer[] dsts, final int offset, final int length) throws IOException { if (length == 0) { return 0L; } else if (length == 1) { return read(dsts[offset]); } long val = state; checkMaxSize(val); if (allAreSet(val, FLAG_CLOSED) || allAreClear(val, MASK_COUNT)) { if (allAreClear(val, FLAG_FINISHED)) { invokeFinishListener(); } return -1; } long res = 0L; Throwable readError = null; try { if ((val & MASK_COUNT) == 0L) { return -1L; } int lim; // The total amount of buffer space discovered so far. long t = 0L; for (int i = 0; i < length; i++) { final ByteBuffer buffer = dsts[i + offset]; // Grow the discovered buffer space by the remaining size of the current buffer. // We want to capture the limit so we calculate "remaining" ourselves. t += (lim = buffer.limit()) - buffer.position(); if (t > (val & MASK_COUNT)) { // only read up to this point, and trim the last buffer by the number of extra bytes buffer.limit(lim - (int) (t - (val & MASK_COUNT))); try { return res = next.read(dsts, offset, i + 1); } finally { // restore the original limit buffer.limit(lim); } } } // the total buffer space is less than the remaining count. return res = next.read(dsts, offset, length); } catch (IOException | RuntimeException | Error e) { closeConnection(); readError = e; throw e; } finally { exitRead(res, readError); } } public long read(final ByteBuffer[] dsts) throws IOException { return read(dsts, 0, dsts.length); } public int read(final ByteBuffer dst) throws IOException { long val = state; checkMaxSize(val); if (allAreSet(val, FLAG_CLOSED) || allAreClear(val, MASK_COUNT)) { if (allAreClear(val, FLAG_FINISHED)) { invokeFinishListener(); } return -1; } int res = 0; final long remaining = val & MASK_COUNT; Throwable readError = null; try { final int lim = dst.limit(); final int pos = dst.position(); if (lim - pos > remaining) { dst.limit((int) (remaining + (long) pos)); try { return res = next.read(dst); } finally { dst.limit(lim); } } else { return res = next.read(dst); } } catch (IOException | RuntimeException | Error e) { closeConnection(); readError = e; throw e; } finally { exitRead(res, readError); } } public boolean isReadResumed() { return allAreClear(state, FLAG_CLOSED) && next.isReadResumed(); } public void wakeupReads() { long val = state; if (anyAreSet(val, FLAG_CLOSED | FLAG_FINISHED)) { return; } next.wakeupReads(); } @Override public void terminateReads() throws IOException { long val = enterShutdownReads(); if (allAreSet(val, FLAG_CLOSED)) { return; } exitShutdownReads(val); } public void awaitReadable() throws IOException { final long val = state; if (allAreSet(val, FLAG_CLOSED) || val == 0L) { return; } next.awaitReadable(); } public void awaitReadable(final long time, final TimeUnit timeUnit) throws IOException { final long val = state; if (allAreSet(val, FLAG_CLOSED) || val == 0L) { return; } try { next.awaitReadable(time, timeUnit); } catch (IOException | RuntimeException | Error e) { closeConnection(); throw e; } } /** * Get the number of remaining bytes. * * @return the number of remaining bytes */ public long getRemaining() { return state & MASK_COUNT; } private long enterShutdownReads() { long oldVal, newVal; oldVal = state; if (anyAreSet(oldVal, FLAG_CLOSED)) { return oldVal; } newVal = oldVal | FLAG_CLOSED; state = newVal; return oldVal; } private void exitShutdownReads(long oldVal) { if (!allAreClear(oldVal, MASK_COUNT)) { invokeFinishListener(); } } /** * Exit a read method. * * @param consumed the number of bytes consumed by this call (may be 0) * @param readError IOException, RuntimeException or Error thrown during read operation * @throws IOException if this conduit has not finished reading all the bytes. In this case, * if {@code readError} is not {@code null}, it is added as a suppressed throwable of * this exception */ private void exitRead(long consumed, Throwable readError) throws IOException { long oldVal = state; if(consumed == -1) { if (anyAreSet(oldVal, MASK_COUNT)) { invokeFinishListener(); state &= ~MASK_COUNT; final IOException couldNotReadAll = UndertowMessages.MESSAGES.couldNotReadContentLengthData(); if (readError != null) { couldNotReadAll.addSuppressed(readError); } throw couldNotReadAll; } return; } long newVal = oldVal - consumed; state = newVal; } private void invokeFinishListener() { this.state |= FLAG_FINISHED; finishListener.handleEvent(this); } private void closeConnection() { HttpServerExchange exchange = this.exchange; if (exchange != null) { IoUtils.safeClose(exchange.getConnection()); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/GzipStreamSinkConduit.java000066400000000000000000000067421420065311100313000ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import io.undertow.server.Connectors; import io.undertow.server.HttpServerExchange; import io.undertow.util.ConduitFactory; import io.undertow.util.ObjectPool; import org.xnio.conduits.StreamSinkConduit; import java.util.zip.CRC32; import java.util.zip.Deflater; /** * @author Stuart Douglas */ public class GzipStreamSinkConduit extends DeflatingStreamSinkConduit { /* * GZIP header magic number. */ private static final int GZIP_MAGIC = 0x8b1f; private static final byte[] HEADER = new byte[]{ (byte) GZIP_MAGIC, // Magic number (short) (byte) (GZIP_MAGIC >> 8), // Magic number (short) Deflater.DEFLATED, // Compression method (CM) 0, // Flags (FLG) 0, // Modification time MTIME (int) 0, // Modification time MTIME (int) 0, // Modification time MTIME (int) 0, // Modification time MTIME (int) 0, // Extra flags (XFLG) 0 // Operating system (OS) }; /** * CRC-32 of uncompressed data. */ protected CRC32 crc = new CRC32(); public GzipStreamSinkConduit(ConduitFactory conduitFactory, HttpServerExchange exchange) { this(conduitFactory, exchange, Deflater.DEFAULT_COMPRESSION); } public GzipStreamSinkConduit( ConduitFactory conduitFactory, HttpServerExchange exchange, int deflateLevel) { this(conduitFactory, exchange, newInstanceDeflaterPool(deflateLevel)); } public GzipStreamSinkConduit( ConduitFactory conduitFactory, HttpServerExchange exchange, ObjectPool deflaterPool) { super(conduitFactory, exchange, deflaterPool); writeHeader(); Connectors.updateResponseBytesSent(exchange, HEADER.length); } private void writeHeader() { currentBuffer.getBuffer().put(HEADER); } @Override protected void preDeflate(byte[] data) { crc.update(data); } @Override protected byte[] getTrailer() { byte[] ret = new byte[8]; int checksum = (int) crc.getValue(); int total = deflater.getTotalIn(); ret[0] = (byte) ((checksum) & 0xFF); ret[1] = (byte) ((checksum >> 8) & 0xFF); ret[2] = (byte) ((checksum >> 16) & 0xFF); ret[3] = (byte) ((checksum >> 24) & 0xFF); ret[4] = (byte) ((total) & 0xFF); ret[5] = (byte) ((total >> 8) & 0xFF); ret[6] = (byte) ((total >> 16) & 0xFF); ret[7] = (byte) ((total >> 24) & 0xFF); return ret; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/GzipStreamSourceConduit.java000066400000000000000000000110451420065311100316240ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import java.io.IOException; import java.nio.ByteBuffer; import java.util.zip.CRC32; import java.util.zip.Deflater; import java.util.zip.Inflater; import org.xnio.conduits.StreamSourceConduit; import io.undertow.UndertowMessages; import io.undertow.server.ConduitWrapper; import io.undertow.server.HttpServerExchange; import io.undertow.util.ConduitFactory; import io.undertow.util.ObjectPool; /** * @author Stuart Douglas */ public class GzipStreamSourceConduit extends InflatingStreamSourceConduit { public static final ConduitWrapper WRAPPER = new ConduitWrapper() { @Override public StreamSourceConduit wrap(ConduitFactory factory, HttpServerExchange exchange) { return new GzipStreamSourceConduit(exchange, factory.create()); } }; private static final int GZIP_MAGIC = 0x8b1f; private static final byte[] HEADER = new byte[]{ (byte) GZIP_MAGIC, // Magic number (short) (byte) (GZIP_MAGIC >> 8), // Magic number (short) Deflater.DEFLATED, // Compression method (CM) 0, // Flags (FLG) 0, // Modification time MTIME (int) 0, // Modification time MTIME (int) 0, // Modification time MTIME (int) 0, // Modification time MTIME (int) 0, // Extra flags (XFLG) 0 // Operating system (OS) }; private final CRC32 crc = new CRC32(); public GzipStreamSourceConduit(HttpServerExchange exchange, StreamSourceConduit next) { super(exchange, next); } public GzipStreamSourceConduit( HttpServerExchange exchange, StreamSourceConduit next, ObjectPool inflaterPool) { super(exchange, next, inflaterPool); } private int totalOut; private int headerRead = 0; private int footerRead = 0; byte[] expectedFooter; protected boolean readHeader(ByteBuffer headerData) throws IOException { while (headerRead < HEADER.length && headerData.hasRemaining()) { byte data = headerData.get(); if (headerRead == 0 && data != HEADER[0]) { throw UndertowMessages.MESSAGES.invalidGzipHeader(); } else if (headerRead == 1 && data != HEADER[1]) { throw UndertowMessages.MESSAGES.invalidGzipHeader(); } headerRead++; } return headerRead == HEADER.length; } protected void readFooter(ByteBuffer buf) throws IOException { if (expectedFooter == null) { byte[] ret = new byte[8]; int checksum = (int) crc.getValue(); int total = totalOut; ret[0] = (byte) ((checksum) & 0xFF); ret[1] = (byte) ((checksum >> 8) & 0xFF); ret[2] = (byte) ((checksum >> 16) & 0xFF); ret[3] = (byte) ((checksum >> 24) & 0xFF); ret[4] = (byte) ((total) & 0xFF); ret[5] = (byte) ((total >> 8) & 0xFF); ret[6] = (byte) ((total >> 16) & 0xFF); ret[7] = (byte) ((total >> 24) & 0xFF); expectedFooter = ret; } while (buf.hasRemaining() && footerRead < expectedFooter.length) { byte data = buf.get(); if (expectedFooter[footerRead++] != data) { throw UndertowMessages.MESSAGES.invalidGZIPFooter(); } } if (buf.hasRemaining() && footerRead == expectedFooter.length) { throw UndertowMessages.MESSAGES.invalidGZIPFooter(); } } protected void dataDeflated(byte[] data, int off, int len) { crc.update(data, off, len); totalOut += len; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/HeadStreamSinkConduit.java000066400000000000000000000146271420065311100312310ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import org.xnio.IoUtils; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.AbstractStreamSinkConduit; import org.xnio.conduits.ConduitWritableByteChannel; import org.xnio.conduits.Conduits; import org.xnio.conduits.StreamSinkConduit; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.anyAreSet; /** * A conduit that discards all data written to it. This allows head requests to 'just work', as all data written * will be discarded. * * @author Stuart Douglas */ public final class HeadStreamSinkConduit extends AbstractStreamSinkConduit { private final ConduitListener finishListener; private int state; private final boolean shutdownDelegate; private static final int FLAG_CLOSE_REQUESTED = 1; private static final int FLAG_CLOSE_COMPLETE = 1 << 1; private static final int FLAG_FINISHED_CALLED = 1 << 2; /** * Construct a new instance. * * @param next the next channel * @param finishListener the listener to call when the channel is closed or the length is reached */ public HeadStreamSinkConduit(final StreamSinkConduit next, final ConduitListener finishListener) { this(next, finishListener, false); } /** * Construct a new instance. * * @param next the next channel * @param finishListener the listener to call when the channel is closed or the length is reached */ public HeadStreamSinkConduit(final StreamSinkConduit next, final ConduitListener finishListener, boolean shutdownDelegate) { super(next); this.finishListener = finishListener; this.shutdownDelegate = shutdownDelegate; } public int write(final ByteBuffer src) throws IOException { if (anyAreSet(state, FLAG_CLOSE_COMPLETE)) { throw new ClosedChannelException(); } int remaining = src.remaining(); src.position(src.position() + remaining); return remaining; } public long write(final ByteBuffer[] srcs, final int offset, final int length) throws IOException { if (anyAreSet(state, FLAG_CLOSE_COMPLETE)) { throw new ClosedChannelException(); } long total = 0; for (int i = offset; i < offset + length; ++i) { ByteBuffer src = srcs[i]; int remaining = src.remaining(); total += remaining; src.position(src.position() + remaining); } return total; } @Override public int writeFinal(ByteBuffer src) throws IOException { return Conduits.writeFinalBasic(this, src); } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { return Conduits.writeFinalBasic(this, srcs, offset, length); } @Override public long transferFrom(final FileChannel src, final long position, final long count) throws IOException { if (anyAreSet(state, FLAG_CLOSE_COMPLETE)) { throw new ClosedChannelException(); } return src.transferTo(position, count, new ConduitWritableByteChannel(this)); } @Override public long transferFrom(final StreamSourceChannel source, final long count, final ByteBuffer throughBuffer) throws IOException { if (anyAreSet(state, FLAG_CLOSE_COMPLETE)) { throw new ClosedChannelException(); } return IoUtils.transfer(source, count, throughBuffer, new ConduitWritableByteChannel(this)); } public boolean flush() throws IOException { int val = state; if (anyAreSet(val, FLAG_CLOSE_COMPLETE)) { return true; } boolean flushed = false; try { return flushed = next.flush(); } finally { exitFlush(val, flushed); } } public void suspendWrites() { long val = state; if (anyAreSet(val, FLAG_CLOSE_COMPLETE)) { return; } next.suspendWrites(); } public void resumeWrites() { long val = state; if (anyAreSet(val, FLAG_CLOSE_COMPLETE)) { return; } next.resumeWrites(); } public boolean isWriteResumed() { // not perfect but not provably wrong either... return allAreClear(state, FLAG_CLOSE_COMPLETE) && next.isWriteResumed(); } public void wakeupWrites() { long val = state; if (anyAreSet(val, FLAG_CLOSE_COMPLETE)) { return; } next.wakeupWrites(); } public void terminateWrites() throws IOException { int oldVal, newVal; oldVal = state; if (anyAreSet(oldVal, FLAG_CLOSE_REQUESTED | FLAG_CLOSE_COMPLETE)) { // no action necessary return; } newVal = oldVal | FLAG_CLOSE_REQUESTED; state = newVal; if(shutdownDelegate) { next.terminateWrites(); } } private void exitFlush(int oldVal, boolean flushed) { int newVal = oldVal; boolean callFinish = false; if (anyAreSet(oldVal, FLAG_CLOSE_REQUESTED) && flushed) { newVal |= FLAG_CLOSE_COMPLETE; if (!anyAreSet(oldVal, FLAG_FINISHED_CALLED)) { newVal |= FLAG_FINISHED_CALLED; callFinish = true; } state = newVal; if (callFinish) { if (finishListener != null) { finishListener.handleEvent(this); } } } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/IdleTimeoutConduit.java000066400000000000000000000274351420065311100306140ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import io.undertow.UndertowLogger; import io.undertow.util.WorkerUtils; import org.xnio.Buffers; import org.xnio.StreamConnection; import org.xnio.XnioExecutor; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.ReadReadyHandler; import org.xnio.conduits.StreamSinkConduit; import org.xnio.conduits.StreamSourceConduit; import org.xnio.conduits.WriteReadyHandler; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.util.concurrent.TimeUnit; /** * Conduit that adds support to close a channel once for a specified time no * reads and no writes were performed. * * @author Norman Maurer */ public class IdleTimeoutConduit implements StreamSinkConduit, StreamSourceConduit { private static final int DELTA = 100; private volatile XnioExecutor.Key handle; private volatile long idleTimeout; private volatile long expireTime = -1; private volatile boolean timedOut = false; private final StreamSinkConduit sink; private final StreamSourceConduit source; private volatile WriteReadyHandler writeReadyHandler; private volatile ReadReadyHandler readReadyHandler; private final Runnable timeoutCommand = new Runnable() { @Override public void run() { handle = null; if(expireTime == -1) { return; } long current = System.currentTimeMillis(); if(current < expireTime) { //timeout has been bumped, re-schedule handle = WorkerUtils.executeAfter(getWriteThread(), timeoutCommand, (expireTime - current) + DELTA, TimeUnit.MILLISECONDS); return; } UndertowLogger.REQUEST_LOGGER.trace("Timing out channel due to inactivity"); timedOut = true; doClose(); if (sink.isWriteResumed()) { if(writeReadyHandler != null) { writeReadyHandler.writeReady(); } } if (source.isReadResumed()) { if(readReadyHandler != null) { readReadyHandler.readReady(); } } } }; protected void doClose() { safeClose(sink); safeClose(source); } public IdleTimeoutConduit(StreamConnection connection) { this.sink = connection.getSinkChannel().getConduit(); this.source = connection.getSourceChannel().getConduit(); setWriteReadyHandler(new WriteReadyHandler.ChannelListenerHandler<>(connection.getSinkChannel())); setReadReadyHandler(new ReadReadyHandler.ChannelListenerHandler<>(connection.getSourceChannel())); } private void handleIdleTimeout() throws ClosedChannelException { if(timedOut) { return; } long idleTimeout = this.idleTimeout; if(idleTimeout <= 0) { return; } long currentTime = System.currentTimeMillis(); long expireTimeVar = expireTime; if(expireTimeVar != -1 && currentTime > expireTimeVar) { timedOut = true; doClose(); throw new ClosedChannelException(); } expireTime = currentTime + idleTimeout; } @Override public int write(ByteBuffer src) throws IOException { handleIdleTimeout(); int w = sink.write(src); return w; } @Override public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { handleIdleTimeout(); long w = sink.write(srcs, offset, length); return w; } @Override public int writeFinal(ByteBuffer src) throws IOException { handleIdleTimeout(); int w = sink.writeFinal(src); if(source.isReadShutdown() && !src.hasRemaining()) { if(handle != null) { handle.remove(); handle = null; } } return w; } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { handleIdleTimeout(); long w = sink.writeFinal(srcs, offset, length); if(source.isReadShutdown() && !Buffers.hasRemaining(srcs, offset, length)) { if(handle != null) { handle.remove(); handle = null; } } return w; } @Override public long transferTo(long position, long count, FileChannel target) throws IOException { handleIdleTimeout(); long w = source.transferTo(position, count, target); if(sink.isWriteShutdown() && w == -1) { if(handle != null) { handle.remove(); handle = null; } } return w; } @Override public long transferTo(long count, ByteBuffer throughBuffer, StreamSinkChannel target) throws IOException { handleIdleTimeout(); long w = source.transferTo(count, throughBuffer, target); if(sink.isWriteShutdown() && w == -1) { if(handle != null) { handle.remove(); handle = null; } } return w; } @Override public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { handleIdleTimeout(); long r = source.read(dsts, offset, length); if(sink.isWriteShutdown() && r == -1) { if(handle != null) { handle.remove(); handle = null; } } return r; } @Override public int read(ByteBuffer dst) throws IOException { handleIdleTimeout(); int r = source.read(dst); if(sink.isWriteShutdown() && r == -1) { if(handle != null) { handle.remove(); handle = null; } } return r; } @Override public long transferFrom(FileChannel src, long position, long count) throws IOException { handleIdleTimeout(); return sink.transferFrom(src, position, count); } @Override public long transferFrom(StreamSourceChannel source, long count, ByteBuffer throughBuffer) throws IOException { handleIdleTimeout(); return sink.transferFrom(source, count, throughBuffer); } @Override public void suspendReads() { source.suspendReads(); XnioExecutor.Key handle = this.handle; if(handle != null && !isWriteResumed()) { handle.remove(); this.handle = null; } } @Override public void terminateReads() throws IOException { source.terminateReads(); if(sink.isWriteShutdown()) { if(handle != null) { handle.remove(); handle = null; } } } @Override public boolean isReadShutdown() { return source.isReadShutdown(); } @Override public void resumeReads() { source.resumeReads(); handleResumeTimeout(); } @Override public boolean isReadResumed() { return source.isReadResumed(); } @Override public void wakeupReads() { source.wakeupReads(); handleResumeTimeout(); } @Override public void awaitReadable() throws IOException { source.awaitReadable(); } @Override public void awaitReadable(long time, TimeUnit timeUnit) throws IOException { source.awaitReadable(time, timeUnit); } @Override public XnioIoThread getReadThread() { return source.getReadThread(); } @Override public void setReadReadyHandler(ReadReadyHandler handler) { this.readReadyHandler = handler; source.setReadReadyHandler(handler); } private static void safeClose(final StreamSourceConduit sink) { try { sink.terminateReads(); } catch (IOException e) { } } private static void safeClose(final StreamSinkConduit sink) { try { sink.truncateWrites(); } catch (IOException e) { } } @Override public void terminateWrites() throws IOException { sink.terminateWrites(); if(source.isReadShutdown()) { if(handle != null) { handle.remove(); handle = null; } } } @Override public boolean isWriteShutdown() { return sink.isWriteShutdown(); } @Override public void resumeWrites() { sink.resumeWrites(); handleResumeTimeout(); } @Override public void suspendWrites() { sink.suspendWrites(); XnioExecutor.Key handle = this.handle; if(handle != null && !isReadResumed()) { handle.remove(); this.handle = null; } } @Override public void wakeupWrites() { sink.wakeupWrites(); handleResumeTimeout(); } private void handleResumeTimeout() { long timeout = getIdleTimeout(); if (timeout <= 0) { return; } long currentTime = System.currentTimeMillis(); long newExpireTime = currentTime + timeout; boolean shorter = newExpireTime < expireTime; if(shorter && handle != null) { handle.remove(); handle = null; } expireTime = newExpireTime; XnioExecutor.Key key = handle; if (key == null) { handle = WorkerUtils.executeAfter(getWriteThread(), timeoutCommand, timeout, TimeUnit.MILLISECONDS); } } @Override public boolean isWriteResumed() { return sink.isWriteResumed(); } @Override public void awaitWritable() throws IOException { sink.awaitWritable(); } @Override public void awaitWritable(long time, TimeUnit timeUnit) throws IOException { sink.awaitWritable(); } @Override public XnioIoThread getWriteThread() { return sink.getWriteThread(); } @Override public void setWriteReadyHandler(WriteReadyHandler handler) { this.writeReadyHandler = handler; sink.setWriteReadyHandler(handler); } @Override public void truncateWrites() throws IOException { sink.truncateWrites(); if(source.isReadShutdown()) { if(handle != null) { handle.remove(); handle = null; } } } @Override public boolean flush() throws IOException { return sink.flush(); } @Override public XnioWorker getWorker() { return sink.getWorker(); } public long getIdleTimeout() { return idleTimeout; } public void setIdleTimeout(long idleTimeout) { this.idleTimeout = idleTimeout; if(idleTimeout > 0) { expireTime = System.currentTimeMillis() + idleTimeout; if(isReadResumed() || isWriteResumed()) { handleResumeTimeout(); } } else { expireTime = -1; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/InflatingStreamSourceConduit.java000066400000000000000000000205201420065311100326240ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.util.zip.DataFormatException; import java.util.zip.Inflater; import org.xnio.Buffers; import org.xnio.IoUtils; import org.xnio.channels.StreamSinkChannel; import org.xnio.conduits.AbstractStreamSourceConduit; import org.xnio.conduits.ConduitReadableByteChannel; import org.xnio.conduits.StreamSourceConduit; import io.undertow.UndertowLogger; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.ConduitWrapper; import io.undertow.server.HttpServerExchange; import io.undertow.util.ConduitFactory; import io.undertow.util.NewInstanceObjectPool; import io.undertow.util.ObjectPool; import io.undertow.util.PooledObject; import io.undertow.util.SimpleObjectPool; /** * @author Stuart Douglas */ public class InflatingStreamSourceConduit extends AbstractStreamSourceConduit { public static final ConduitWrapper WRAPPER = new ConduitWrapper() { @Override public StreamSourceConduit wrap(ConduitFactory factory, HttpServerExchange exchange) { return new InflatingStreamSourceConduit(exchange, factory.create()); } }; private volatile Inflater inflater; private final PooledObject pooledObject; private final HttpServerExchange exchange; private PooledByteBuffer compressed; private PooledByteBuffer uncompressed; private boolean nextDone = false; private boolean headerDone = false; public InflatingStreamSourceConduit(HttpServerExchange exchange, StreamSourceConduit next) { this(exchange, next, newInstanceInflaterPool()); } public InflatingStreamSourceConduit( HttpServerExchange exchange, StreamSourceConduit next, ObjectPool inflaterPool) { super(next); this.exchange = exchange; this.pooledObject = inflaterPool.allocate(); this.inflater = pooledObject.getObject(); } public static ObjectPool newInstanceInflaterPool() { return new NewInstanceObjectPool(() -> new Inflater(true), Inflater::end); } public static ObjectPool simpleInflaterPool(int poolSize) { return new SimpleObjectPool(poolSize, () -> new Inflater(true), Inflater::reset, Inflater::end); } @Override public int read(ByteBuffer dst) throws IOException { if (isReadShutdown()) { throw new ClosedChannelException(); } if (uncompressed != null) { int ret = Buffers.copy(dst, uncompressed.getBuffer()); if (!uncompressed.getBuffer().hasRemaining()) { uncompressed.close(); uncompressed = null; } return ret; } for(;;) { if (compressed == null && !nextDone) { compressed = exchange.getConnection().getByteBufferPool().getArrayBackedPool().allocate(); ByteBuffer buf = compressed.getBuffer(); int res = next.read(buf); if (res == -1) { nextDone = true; compressed.close(); compressed = null; } else if (res == 0) { compressed.close(); compressed = null; return 0; } else { buf.flip(); if (!headerDone) { headerDone = readHeader(buf); } inflater.setInput(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining()); } } if (nextDone && inflater.needsInput() && !inflater.finished()) { throw UndertowLogger.ROOT_LOGGER.unexpectedEndOfCompressedInput(); } else if (nextDone && inflater.finished()) { done(); return -1; } else if (inflater.finished() && compressed != null) { int rem = inflater.getRemaining(); ByteBuffer buf = compressed.getBuffer(); buf.position(buf.limit() - rem); readFooter(buf); int res; do { buf.clear(); res = next.read(buf); buf.flip(); if (res == -1) { done(); nextDone = true; return -1; } else if (res > 0) { readFooter(buf); } } while (res != 0); compressed.close(); compressed = null; return 0; } else if (compressed == null) { throw new RuntimeException(); } uncompressed = exchange.getConnection().getByteBufferPool().getArrayBackedPool().allocate(); try { int read = inflater.inflate(uncompressed.getBuffer().array(), uncompressed.getBuffer().arrayOffset(), uncompressed.getBuffer().limit()); uncompressed.getBuffer().limit(read); dataDeflated(uncompressed.getBuffer().array(), uncompressed.getBuffer().arrayOffset(), read); if (inflater.needsInput()) { compressed.close(); compressed = null; } int ret = Buffers.copy(dst, uncompressed.getBuffer()); if (!uncompressed.getBuffer().hasRemaining()) { uncompressed.close(); uncompressed = null; } if(ret > 0) { return ret; } } catch (DataFormatException e) { done(); throw new IOException(e); } } } protected void readFooter(ByteBuffer buf) throws IOException { } protected boolean readHeader(ByteBuffer byteBuffer) throws IOException { return true; } protected void dataDeflated(byte[] data, int off, int len) { } private void done() { if (compressed != null) { compressed.close(); } if (uncompressed != null) { uncompressed.close(); } if (inflater != null) { pooledObject.close(); inflater = null; } } public long transferTo(final long position, final long count, final FileChannel target) throws IOException { try { return target.transferFrom(new ConduitReadableByteChannel(this), position, count); } catch (IOException | RuntimeException | Error e) { IoUtils.safeClose(exchange.getConnection()); throw e; } } @Override public long transferTo(final long count, final ByteBuffer throughBuffer, final StreamSinkChannel target) throws IOException { try { return IoUtils.transfer(new ConduitReadableByteChannel(this), count, throughBuffer, target); } catch (IOException | RuntimeException | Error e) { IoUtils.safeClose(exchange.getConnection()); throw e; } } @Override public long read(final ByteBuffer[] dsts, final int offset, final int length) throws IOException { for (int i = offset; i < length; ++i) { if (dsts[i].hasRemaining()) { return read(dsts[i]); } } return 0; } @Override public void terminateReads() throws IOException { done(); next.terminateReads(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/PreChunkedStreamSinkConduit.java000066400000000000000000000156701420065311100324170ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import io.undertow.UndertowMessages; import io.undertow.server.protocol.http.HttpAttachments; import io.undertow.util.Attachable; import org.xnio.IoUtils; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.AbstractStreamSinkConduit; import org.xnio.conduits.ConduitWritableByteChannel; import org.xnio.conduits.Conduits; import org.xnio.conduits.StreamSinkConduit; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.util.concurrent.TimeUnit; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.anyAreSet; /** * Channel that implements HTTP chunked transfer coding for data streams that already have chunk markers. * * @author Stuart Douglas */ public class PreChunkedStreamSinkConduit extends AbstractStreamSinkConduit { private final ConduitListener finishListener; /** * Flag that is set when {@link #terminateWrites()} or @{link #close()} is called */ private static final int FLAG_WRITES_SHUTDOWN = 1; private static final int FLAG_FINISHED = 1 << 2; int state = 0; final ChunkReader chunkReader; /** * Construct a new instance. * * @param next the channel to wrap * @param finishListener The finish listener * @param attachable The attachable */ public PreChunkedStreamSinkConduit(final StreamSinkConduit next, final ConduitListener finishListener, final Attachable attachable) { super(next); //we don't want the reader to call the finish listener, so we pass null this.chunkReader = new ChunkReader<>(attachable, HttpAttachments.RESPONSE_TRAILERS, this); this.finishListener = finishListener; } @Override public int write(final ByteBuffer src) throws IOException { return doWrite(src); } int doWrite(final ByteBuffer src) throws IOException { if (anyAreSet(state, FLAG_WRITES_SHUTDOWN)) { throw new ClosedChannelException(); } if (chunkReader.getChunkRemaining() == -1) { throw UndertowMessages.MESSAGES.extraDataWrittenAfterChunkEnd(); } if (src.remaining() == 0) { return 0; } int oldPos = src.position(); int oldLimit = src.limit(); int ret = next.write(src); if(ret == 0) { return ret; } int newPos = src.position(); src.position(oldPos); src.limit(oldPos + ret); try { while (true) { long chunkRemaining = chunkReader.readChunk(src); if (chunkRemaining == -1) { if (src.remaining() == 0) { return ret; } else { throw UndertowMessages.MESSAGES.extraDataWrittenAfterChunkEnd(); } } else if(chunkRemaining == 0) { return ret; } int remaining; if (src.remaining() >= chunkRemaining) { src.position((int) (src.position() + chunkRemaining)); remaining = 0; } else { remaining = (int) (chunkRemaining - src.remaining()); src.position(src.limit()); } chunkReader.setChunkRemaining(remaining); if (!src.hasRemaining()) { break; } } } finally { src.position(newPos); src.limit(oldLimit); } return ret; } @Override public long write(final ByteBuffer[] srcs, final int offset, final int length) throws IOException { for (int i = offset; i < length; ++i) { if (srcs[i].hasRemaining()) { return write(srcs[i]); } } return 0; } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { return Conduits.writeFinalBasic(this, srcs, offset, length); } @Override public int writeFinal(ByteBuffer src) throws IOException { if (!src.hasRemaining()) { terminateWrites(); return 0; } int ret = doWrite(src); terminateWrites(); return ret; } @Override public long transferFrom(final FileChannel src, final long position, final long count) throws IOException { if (anyAreSet(state, FLAG_WRITES_SHUTDOWN)) { throw new ClosedChannelException(); } return src.transferTo(position, count, new ConduitWritableByteChannel(this)); } @Override public long transferFrom(final StreamSourceChannel source, final long count, final ByteBuffer throughBuffer) throws IOException { if (anyAreSet(state, FLAG_WRITES_SHUTDOWN)) { throw new ClosedChannelException(); } return IoUtils.transfer(source, count, throughBuffer, new ConduitWritableByteChannel(this)); } @Override public boolean flush() throws IOException { if (anyAreSet(state, FLAG_WRITES_SHUTDOWN)) { boolean val = next.flush(); if (val && allAreClear(state, FLAG_FINISHED)) { invokeFinishListener(); } return val; } else { return next.flush(); } } private void invokeFinishListener() { state |= FLAG_FINISHED; if (finishListener != null) { finishListener.handleEvent(this); } } @Override public void terminateWrites() throws IOException { if (anyAreSet(state, FLAG_WRITES_SHUTDOWN)) { return; } if (chunkReader.getChunkRemaining() != -1) { throw UndertowMessages.MESSAGES.chunkedChannelClosedMidChunk(); } state |= FLAG_WRITES_SHUTDOWN; } @Override public void awaitWritable() throws IOException { next.awaitWritable(); } @Override public void awaitWritable(final long time, final TimeUnit timeUnit) throws IOException { next.awaitWritable(time, timeUnit); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/RangeStreamSinkConduit.java000066400000000000000000000105131420065311100314120ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import org.xnio.IoUtils; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.AbstractStreamSinkConduit; import org.xnio.conduits.ConduitWritableByteChannel; import org.xnio.conduits.Conduits; import org.xnio.conduits.StreamSinkConduit; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; /** * @author Stuart Douglas */ public class RangeStreamSinkConduit extends AbstractStreamSinkConduit { private final long start, end; private final long originalResponseLength; private long written; public RangeStreamSinkConduit(StreamSinkConduit next, long start, long end, long originalResponseLength) { super(next); this.start = start; this.end = end; this.originalResponseLength = originalResponseLength; } @Override public int write(ByteBuffer src) throws IOException { boolean currentInclude = written >= start && written <= end; long bytesRemaining = written < start ? start - written : written <= end ? end - written + 1 : Long.MAX_VALUE; if (currentInclude) { int old = src.limit(); src.limit((int) Math.min(src.position() + bytesRemaining, src.limit())); int written; int toConsume = 0; try { written = super.write(src); this.written += written; } finally { if (!src.hasRemaining()) { //we wrote everything out src.limit(old); if (src.hasRemaining()) { toConsume = src.remaining(); //but there was still some data that fell outside the range, so we discard it this.written += toConsume; src.position(src.limit()); } } else { src.limit(old); } } return written + toConsume; } else { if (src.remaining() <= bytesRemaining) { int rem = src.remaining(); this.written += rem; src.position(src.limit()); return rem; } else { this.written += bytesRemaining; src.position((int) (src.position() + bytesRemaining)); return (int) bytesRemaining + write(src); } } } @Override public long write(ByteBuffer[] srcs, int offs, int len) throws IOException { long ret = 0; //todo: a more efficent impl for (int i = offs; i < offs + len; ++i) { ByteBuffer buf = srcs[i]; if (buf.remaining() > 0) { ret += write(buf); if (buf.hasRemaining()) { return ret; } } } return ret; } @Override public long transferFrom(StreamSourceChannel source, long count, ByteBuffer throughBuffer) throws IOException { return IoUtils.transfer(source, count, throughBuffer, new ConduitWritableByteChannel(this)); } @Override public long transferFrom(FileChannel src, long position, long count) throws IOException { return src.transferTo(position, count, new ConduitWritableByteChannel(this)); } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { return Conduits.writeFinalBasic(this, srcs, offset, length); } @Override public int writeFinal(ByteBuffer src) throws IOException { return Conduits.writeFinalBasic(this, src); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/RateLimitingStreamSinkConduit.java000066400000000000000000000216201420065311100327470ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.AbstractStreamSinkConduit; import org.xnio.conduits.StreamSinkConduit; import java.io.IOException; import java.io.InterruptedIOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.concurrent.TimeUnit; import io.undertow.util.WorkerUtils; /** * Class that implements the token bucket algorithm. *

* Allows send speed to be throttled *

* Note that throttling is applied after an initial write, so if a big write is performed initially * it may be a while before it can write again. * * @author Stuart Douglas */ public class RateLimitingStreamSinkConduit extends AbstractStreamSinkConduit { private final long time; private final int bytes; private boolean writesResumed = false; private int byteCount = 0; private long startTime = 0; private long nextSendTime = 0; private boolean scheduled = false; /** * @param next The next conduit * @param bytes The number of bytes that are allowed per time frame * @param time The time frame * @param timeUnit The time unit */ public RateLimitingStreamSinkConduit(StreamSinkConduit next, int bytes, long time, TimeUnit timeUnit) { super(next); writesResumed = next.isWriteResumed(); this.time = timeUnit.toMillis(time); this.bytes = bytes; } @Override public int write(ByteBuffer src) throws IOException { if (!canSend()) { return 0; } int bytes = this.bytes - this.byteCount; int old = src.limit(); if (src.remaining() > bytes) { src.limit(src.position() + bytes); } try { int written = super.write(src); handleWritten(written); return written; } finally { src.limit(old); } } @Override public long transferFrom(FileChannel src, long position, long count) throws IOException { if (!canSend()) { return 0; } int bytes = this.bytes - this.byteCount; long written = super.transferFrom(src, position, Math.min(count, bytes)); handleWritten(written); return written; } @Override public long transferFrom(StreamSourceChannel source, long count, ByteBuffer throughBuffer) throws IOException { if (!canSend()) { return 0; } int bytes = this.bytes - this.byteCount; long written = super.transferFrom(source, Math.min(count, bytes), throughBuffer); handleWritten(written); return written; } @Override public long write(ByteBuffer[] srcs, int offs, int len) throws IOException { if (!canSend()) { return 0; } int old = 0; int adjPos = -1; long rem = bytes - byteCount; for (int i = offs; i < offs + len; ++i) { ByteBuffer buf = srcs[i]; rem -= buf.remaining(); if (rem < 0) { adjPos = i; old = buf.limit(); buf.limit((int) (buf.limit() + rem)); break; } } try { long written; if (adjPos == -1) { written = super.write(srcs, offs, len); } else { written = super.write(srcs, offs, adjPos - offs + 1); } handleWritten(written); return written; } finally { if (adjPos != -1) { ByteBuffer buf = srcs[adjPos]; buf.limit(old); } } } @Override public int writeFinal(ByteBuffer src) throws IOException { if (!canSend()) { return 0; } int bytes = this.bytes - this.byteCount; int old = src.limit(); if (src.remaining() > bytes) { src.limit(src.position() + bytes); } try { int written = super.writeFinal(src); handleWritten(written); return written; } finally { src.limit(old); } } @Override public long writeFinal(ByteBuffer[] srcs, int offs, int len) throws IOException { if (!canSend()) { return 0; } int old = 0; int adjPos = -1; long rem = bytes - byteCount; for (int i = offs; i < offs + len; ++i) { ByteBuffer buf = srcs[i]; rem -= buf.remaining(); if (rem < 0) { adjPos = i; old = buf.limit(); buf.limit((int) (buf.limit() + rem)); break; } } try { long written; if (adjPos == -1) { written = super.writeFinal(srcs, offs, len); } else { written = super.writeFinal(srcs, offs, adjPos - offs + 1); } handleWritten(written); return written; } finally { if (adjPos != -1) { ByteBuffer buf = srcs[adjPos]; buf.limit(old); } } } @Override public void resumeWrites() { writesResumed = true; if (canSend()) { super.resumeWrites(); } } @Override public void suspendWrites() { writesResumed = false; super.suspendWrites(); } @Override public void wakeupWrites() { writesResumed = true; if (canSend()) { super.wakeupWrites(); } } @Override public boolean isWriteResumed() { return writesResumed; } @Override public void awaitWritable() throws IOException { long toGo = nextSendTime - System.currentTimeMillis(); if (toGo > 0) { try { Thread.sleep(toGo); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InterruptedIOException(); } } super.awaitWritable(); } @Override public void awaitWritable(long time, TimeUnit timeUnit) throws IOException { long toGo = nextSendTime - System.currentTimeMillis(); if (toGo > 0) { try { Thread.sleep(Math.min(toGo, timeUnit.toMillis(time))); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InterruptedIOException(); } return; } super.awaitWritable(time, timeUnit); } private boolean canSend() { if (byteCount < bytes) { return true; } if (System.currentTimeMillis() > nextSendTime) { byteCount = 0; startTime = 0; nextSendTime = 0; return true; } if (writesResumed) { handleWritesResumedWhenBlocked(); } return false; } private void handleWritten(long written) { if (written == 0) { return; } byteCount += written; if (byteCount < bytes) { //we are still allowed to send if (startTime == 0) { startTime = System.currentTimeMillis(); nextSendTime = System.currentTimeMillis() + time; } } else { //we have gone over, we need to wait till we are allowed to send again if (startTime == 0) { startTime = System.currentTimeMillis(); } nextSendTime = startTime + time; if (writesResumed) { handleWritesResumedWhenBlocked(); } } } private void handleWritesResumedWhenBlocked() { if (scheduled) { return; } scheduled = true; next.suspendWrites(); long millis = nextSendTime - System.currentTimeMillis(); WorkerUtils.executeAfter(getWriteThread(), new Runnable() { @Override public void run() { scheduled = false; if (writesResumed) { next.wakeupWrites(); } } }, millis, TimeUnit.MILLISECONDS); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/ReadDataStreamSourceConduit.java000066400000000000000000000072011420065311100323570ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import io.undertow.server.AbstractServerConnection; import org.xnio.Buffers; import org.xnio.IoUtils; import io.undertow.connector.PooledByteBuffer; import org.xnio.channels.StreamSinkChannel; import org.xnio.conduits.AbstractStreamSourceConduit; import org.xnio.conduits.ConduitReadableByteChannel; import org.xnio.conduits.StreamSourceConduit; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.concurrent.TimeUnit; /** * @author Stuart Douglas */ public class ReadDataStreamSourceConduit extends AbstractStreamSourceConduit { private final AbstractServerConnection connection; public ReadDataStreamSourceConduit(final StreamSourceConduit next, final AbstractServerConnection connection) { super(next); this.connection = connection; } public long transferTo(final long position, final long count, final FileChannel target) throws IOException { return target.transferFrom(new ConduitReadableByteChannel(this), position, count); } public long transferTo(final long count, final ByteBuffer throughBuffer, final StreamSinkChannel target) throws IOException { return IoUtils.transfer(new ConduitReadableByteChannel(this), count, throughBuffer, target); } @Override public int read(final ByteBuffer dst) throws IOException { PooledByteBuffer eb = connection.getExtraBytes(); if (eb != null) { final ByteBuffer buffer = eb.getBuffer(); int result = Buffers.copy(dst, buffer); if (!buffer.hasRemaining()) { eb.close(); connection.setExtraBytes(null); } return result; } else { return super.read(dst); } } @Override public long read(final ByteBuffer[] dsts, final int offs, final int len) throws IOException { PooledByteBuffer eb = connection.getExtraBytes(); if (eb != null) { final ByteBuffer buffer = eb.getBuffer(); int result = Buffers.copy(dsts, offs, len, buffer); if (!buffer.hasRemaining()) { eb.close(); connection.setExtraBytes(null); } return result; } else { return super.read(dsts, offs, len); } } @Override public void resumeReads() { if (connection.getExtraBytes() != null) { wakeupReads(); } else { super.resumeReads(); } } @Override public void awaitReadable() throws IOException { if (connection.getExtraBytes() != null) { return; } super.awaitReadable(); } @Override public void awaitReadable(final long time, final TimeUnit timeUnit) throws IOException { if (connection.getExtraBytes() != null) { return; } super.awaitReadable(time, timeUnit); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/ReadTimeoutStreamSourceConduit.java000066400000000000000000000206331420065311100331400ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.concurrent.TimeUnit; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.UndertowOptions; import io.undertow.server.OpenListener; import io.undertow.util.WorkerUtils; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.Options; import org.xnio.StreamConnection; import org.xnio.XnioExecutor; import org.xnio.channels.ReadTimeoutException; import org.xnio.channels.StreamSinkChannel; import org.xnio.conduits.AbstractStreamSourceConduit; import org.xnio.conduits.ConduitStreamSourceChannel; import org.xnio.conduits.ReadReadyHandler; import org.xnio.conduits.StreamSourceConduit; /** * Wrapper for read timeout. This should always be the first wrapper applied to the underlying channel. * * @author Stuart Douglas * @see org.xnio.Options#READ_TIMEOUT */ public final class ReadTimeoutStreamSourceConduit extends AbstractStreamSourceConduit { private XnioExecutor.Key handle; private final StreamConnection connection; private volatile long expireTime = -1; private final OpenListener openListener; private static final int FUZZ_FACTOR = 50; //we add 50ms to the timeout to make sure the underlying channel has actually timed out private volatile boolean expired; private final Runnable timeoutCommand = new Runnable() { @Override public void run() { handle = null; if (expireTime == -1) { return; } long current = System.currentTimeMillis(); if (current < expireTime) { //timeout has been bumped, re-schedule handle = WorkerUtils.executeAfter(connection.getIoThread(),timeoutCommand, (expireTime - current) + FUZZ_FACTOR, TimeUnit.MILLISECONDS); return; } UndertowLogger.REQUEST_LOGGER.tracef("Timing out channel %s due to inactivity", connection.getSourceChannel()); synchronized (ReadTimeoutStreamSourceConduit.this) { expired = true; } boolean readResumed = connection.getSourceChannel().isReadResumed(); ChannelListener readListener = connection.getSourceChannel().getReadListener(); if (readResumed) { ChannelListeners.invokeChannelListener(connection.getSourceChannel(), readListener); } if (connection.getSinkChannel().isWriteResumed()) { ChannelListeners.invokeChannelListener(connection.getSinkChannel(), connection.getSinkChannel().getWriteListener()); } // close only after invoking listeners, to allow space for listener getting ReadTimeoutException IoUtils.safeClose(connection); } }; public ReadTimeoutStreamSourceConduit(final StreamSourceConduit delegate, StreamConnection connection, OpenListener openListener) { super(delegate); this.connection = connection; this.openListener = openListener; final ReadReadyHandler handler = new ReadReadyHandler.ChannelListenerHandler<>(connection.getSourceChannel()); delegate.setReadReadyHandler(new ReadReadyHandler() { @Override public void readReady() { handler.readReady(); } @Override public void forceTermination() { cleanup(); handler.forceTermination(); } @Override public void terminated() { cleanup(); handler.terminated(); } }); } private void handleReadTimeout(final long ret) throws IOException { if (!connection.isOpen()) { cleanup(); return; } if (ret == -1) { cleanup(); return; } Integer timeout = getTimeout(); if (timeout == null || timeout <= 0) { return; } final long currentTime = System.currentTimeMillis(); if (ret == 0) { final long expireTimeVar = expireTime; if (expireTimeVar != -1 && currentTime > expireTimeVar) { IoUtils.safeClose(connection); throw UndertowMessages.MESSAGES.readTimedOut(this.getTimeout()); } } expireTime = currentTime + timeout; if (handle == null) { handle = connection.getIoThread().executeAfter(timeoutCommand, timeout, TimeUnit.MILLISECONDS); } } @Override public long transferTo(final long position, final long count, final FileChannel target) throws IOException { checkExpired(); long ret = super.transferTo(position, count, target); handleReadTimeout(ret); return ret; } @Override public long transferTo(final long count, final ByteBuffer throughBuffer, final StreamSinkChannel target) throws IOException { checkExpired(); long ret = super.transferTo(count, throughBuffer, target); handleReadTimeout(ret); return ret; } @Override public long read(final ByteBuffer[] dsts, final int offset, final int length) throws IOException { checkExpired(); long ret = super.read(dsts, offset, length); handleReadTimeout(ret); return ret; } @Override public int read(final ByteBuffer dst) throws IOException { checkExpired(); int ret = super.read(dst); handleReadTimeout(ret); return ret; } @Override public void awaitReadable() throws IOException { checkExpired(); Integer timeout = getTimeout(); if (timeout != null && timeout > 0) { super.awaitReadable(timeout + FUZZ_FACTOR, TimeUnit.MILLISECONDS); } else { super.awaitReadable(); } } @Override public void awaitReadable(long time, TimeUnit timeUnit) throws IOException { checkExpired(); Integer timeout = getTimeout(); if (timeout != null && timeout > 0) { long millis = timeUnit.toMillis(time); super.awaitReadable(Math.min(millis, timeout + FUZZ_FACTOR), TimeUnit.MILLISECONDS); } else { super.awaitReadable(time, timeUnit); } } private Integer getTimeout() { Integer timeout = 0; try { timeout = connection.getSourceChannel().getOption(Options.READ_TIMEOUT); } catch (IOException ignore) { // should never happen } Integer idleTimeout = openListener.getUndertowOptions().get(UndertowOptions.IDLE_TIMEOUT); if ((timeout == null || timeout <= 0) && idleTimeout != null) { timeout = idleTimeout; } else if (timeout != null && idleTimeout != null && idleTimeout > 0) { timeout = Math.min(timeout, idleTimeout); } return timeout; } @Override public void terminateReads() throws IOException { checkExpired(); super.terminateReads(); cleanup(); } private void cleanup() { if (handle != null) { handle.remove(); handle = null; expireTime = -1; } } @Override public void suspendReads() { super.suspendReads(); cleanup(); } private void checkExpired() throws ReadTimeoutException { synchronized (this) { if (expired) { throw UndertowMessages.MESSAGES.readTimedOut(System.currentTimeMillis()); } } } public String toString() { return super.toString() + " (next: " + next + ")"; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/StoredResponseStreamSinkConduit.java000066400000000000000000000124671420065311100333470ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import org.xnio.IoUtils; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.AbstractStreamSinkConduit; import org.xnio.conduits.ConduitWritableByteChannel; import org.xnio.conduits.StreamSinkConduit; import io.undertow.UndertowMessages; import io.undertow.server.HttpServerExchange; import io.undertow.util.AttachmentKey; /** * @author Stuart Douglas */ public final class StoredResponseStreamSinkConduit extends AbstractStreamSinkConduit { public static final AttachmentKey RESPONSE = AttachmentKey.create(byte[].class); private ByteArrayOutputStream outputStream; private final HttpServerExchange exchange; /** * Construct a new instance. * * @param next the delegate conduit to set * @param exchange */ public StoredResponseStreamSinkConduit(StreamSinkConduit next, HttpServerExchange exchange) { super(next); this.exchange = exchange; long length = exchange.getResponseContentLength(); if (length <= 0L) { outputStream = new ByteArrayOutputStream(); } else { if (length > Integer.MAX_VALUE) { throw UndertowMessages.MESSAGES.responseTooLargeToBuffer(length); } outputStream = new ByteArrayOutputStream((int) length); } } @Override public long transferFrom(StreamSourceChannel source, long count, ByteBuffer throughBuffer) throws IOException { return IoUtils.transfer(source, count, throughBuffer, new ConduitWritableByteChannel(this)); } @Override public long transferFrom(FileChannel src, long position, long count) throws IOException { return src.transferTo(position, count, new ConduitWritableByteChannel(this)); } @Override public int write(ByteBuffer src) throws IOException { int start = src.position(); int ret = super.write(src); if (outputStream != null) { for (int i = start; i < start + ret; ++i) { outputStream.write(src.get(i)); } } return ret; } @Override public long write(ByteBuffer[] srcs, int offs, int len) throws IOException { int[] starts = new int[len]; for (int i = 0; i < len; ++i) { starts[i] = srcs[i + offs].position(); } long ret = super.write(srcs, offs, len); long rem = ret; if (outputStream != null) { for (int i = 0; i < len; ++i) { ByteBuffer buf = srcs[i + offs]; int pos = starts[i]; while (rem > 0 && pos < buf.position()) { outputStream.write(buf.get(pos)); pos++; rem--; } } } return ret; } @Override public int writeFinal(ByteBuffer src) throws IOException { int start = src.position(); int ret = super.writeFinal(src); if (outputStream != null) { for (int i = start; i < start + ret; ++i) { outputStream.write(src.get(i)); } if (!src.hasRemaining()) { exchange.putAttachment(RESPONSE, outputStream.toByteArray()); outputStream = null; } } return ret; } @Override public long writeFinal(ByteBuffer[] srcs, int offs, int len) throws IOException { int[] starts = new int[len]; long toWrite = 0; for (int i = 0; i < len; ++i) { starts[i] = srcs[i + offs].position(); toWrite += srcs[i + offs].remaining(); } long ret = super.write(srcs, offs, len); long rem = ret; if (outputStream != null) { for (int i = 0; i < len; ++i) { ByteBuffer buf = srcs[i + offs]; int pos = starts[i]; while (rem > 0 && pos < buf.position()) { outputStream.write(buf.get(pos)); pos++; rem--; } } if (toWrite == ret) { exchange.putAttachment(RESPONSE, outputStream.toByteArray()); outputStream = null; } } return ret; } @Override public void terminateWrites() throws IOException { if (outputStream != null) { exchange.putAttachment(RESPONSE, outputStream.toByteArray()); outputStream = null; } super.terminateWrites(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/conduits/WriteTimeoutStreamSinkConduit.java000066400000000000000000000202311420065311100330150ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.conduits; import io.undertow.UndertowLogger; import io.undertow.UndertowOptions; import io.undertow.server.OpenListener; import io.undertow.util.WorkerUtils; import org.xnio.Buffers; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.Options; import org.xnio.StreamConnection; import org.xnio.XnioExecutor; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.AbstractStreamSinkConduit; import org.xnio.conduits.StreamSinkConduit; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.util.concurrent.TimeUnit; /** * Wrapper for write timeout. This should always be the first wrapper applied to the underlying channel. * * @author Stuart Douglas * @see org.xnio.Options#WRITE_TIMEOUT */ public final class WriteTimeoutStreamSinkConduit extends AbstractStreamSinkConduit { private XnioExecutor.Key handle; private final StreamConnection connection; private volatile long expireTime = -1; private final OpenListener openListener; private static final int FUZZ_FACTOR = 50; //we add 50ms to the timeout to make sure the underlying channel has actually timed out private final Runnable timeoutCommand = new Runnable() { @Override public void run() { handle = null; if (expireTime == -1) { return; } long current = System.currentTimeMillis(); if (current < expireTime) { //timeout has been bumped, re-schedule handle = WorkerUtils.executeAfter(getWriteThread(),timeoutCommand, (expireTime - current) + FUZZ_FACTOR, TimeUnit.MILLISECONDS); return; } UndertowLogger.REQUEST_LOGGER.tracef("Timing out channel %s due to inactivity", connection.getSinkChannel()); IoUtils.safeClose(connection); if (connection.getSourceChannel().isReadResumed()) { ChannelListeners.invokeChannelListener(connection.getSourceChannel(), connection.getSourceChannel().getReadListener()); } if (connection.getSinkChannel().isWriteResumed()) { ChannelListeners.invokeChannelListener(connection.getSinkChannel(), connection.getSinkChannel().getWriteListener()); } } }; public WriteTimeoutStreamSinkConduit(final StreamSinkConduit delegate, StreamConnection connection, OpenListener openListener) { super(delegate); this.connection = connection; this.openListener = openListener; } private void handleWriteTimeout(final long ret) throws IOException { if (!connection.isOpen()) { return; } if (ret == 0 && handle != null) { return; } Integer timeout = getTimeout(); if (timeout == null || timeout <= 0) { return; } long currentTime = System.currentTimeMillis(); long expireTimeVar = expireTime; if (expireTimeVar != -1 && currentTime > expireTimeVar) { IoUtils.safeClose(connection); throw new ClosedChannelException(); } expireTime = currentTime + timeout; } @Override public int write(final ByteBuffer src) throws IOException { int ret = super.write(src); handleWriteTimeout(ret); return ret; } @Override public long write(final ByteBuffer[] srcs, final int offset, final int length) throws IOException { long ret = super.write(srcs, offset, length); handleWriteTimeout(ret); return ret; } @Override public int writeFinal(ByteBuffer src) throws IOException { int ret = super.writeFinal(src); handleWriteTimeout(ret); if(!src.hasRemaining()) { if(handle != null) { handle.remove(); handle = null; } } return ret; } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { long ret = super.writeFinal(srcs, offset, length); handleWriteTimeout(ret); if(!Buffers.hasRemaining(srcs)) { if(handle != null) { handle.remove(); handle = null; } } return ret; } @Override public long transferFrom(final FileChannel src, final long position, final long count) throws IOException { long ret = super.transferFrom(src, position, count); handleWriteTimeout(ret); return ret; } @Override public long transferFrom(final StreamSourceChannel source, final long count, final ByteBuffer throughBuffer) throws IOException { long ret = super.transferFrom(source, count, throughBuffer); handleWriteTimeout(ret); return ret; } @Override public void awaitWritable() throws IOException { Integer timeout = getTimeout(); if (timeout != null && timeout > 0) { super.awaitWritable(timeout + FUZZ_FACTOR, TimeUnit.MILLISECONDS); } else { super.awaitWritable(); } } @Override public void awaitWritable(long time, TimeUnit timeUnit) throws IOException { Integer timeout = getTimeout(); if (timeout != null && timeout > 0) { long millis = timeUnit.toMillis(time); super.awaitWritable(Math.min(millis, timeout + FUZZ_FACTOR), TimeUnit.MILLISECONDS); } else { super.awaitWritable(time, timeUnit); } } private Integer getTimeout() { Integer timeout = 0; try { timeout = connection.getSourceChannel().getOption(Options.WRITE_TIMEOUT); } catch (IOException ignore) { // should never happen, ignoring } Integer idleTimeout = openListener.getUndertowOptions().get(UndertowOptions.IDLE_TIMEOUT); if ((timeout == null || timeout <= 0) && idleTimeout != null) { timeout = idleTimeout; } else if (timeout != null && idleTimeout != null && idleTimeout > 0) { timeout = Math.min(timeout, idleTimeout); } return timeout; } @Override public void terminateWrites() throws IOException { super.terminateWrites(); if(handle != null) { handle.remove(); handle = null; } } @Override public void truncateWrites() throws IOException { super.truncateWrites(); if(handle != null) { handle.remove(); handle = null; } } @Override public void resumeWrites() { super.resumeWrites(); handleResumeTimeout(); } @Override public void suspendWrites() { super.suspendWrites(); XnioExecutor.Key handle = this.handle; if(handle != null) { handle.remove(); this.handle = null; } } @Override public void wakeupWrites() { super.wakeupWrites(); handleResumeTimeout(); } private void handleResumeTimeout() { Integer timeout = getTimeout(); if (timeout == null || timeout <= 0) { return; } long currentTime = System.currentTimeMillis(); expireTime = currentTime + timeout; XnioExecutor.Key key = handle; if (key == null) { handle = connection.getIoThread().executeAfter(timeoutCommand, timeout, TimeUnit.MILLISECONDS); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/connector/000077500000000000000000000000001420065311100243265ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/connector/ByteBufferPool.java000066400000000000000000000024351420065311100300640ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.connector; import java.io.Closeable; /** * A pool of byte buffers * * @author Stuart Douglas */ public interface ByteBufferPool extends Closeable { PooledByteBuffer allocate(); /** * If this byte buffer pool corresponds to an array backed pool then this will return itself. * * Otherwise it will return an array backed pool that contains buffers of the same size. * * @return An array backed pool of the same size */ ByteBufferPool getArrayBackedPool(); void close(); int getBufferSize(); boolean isDirect(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/connector/PooledByteBuffer.java000066400000000000000000000017201420065311100303710ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.connector; import java.io.Closeable; import java.nio.ByteBuffer; /** * @author Stuart Douglas */ public interface PooledByteBuffer extends AutoCloseable, Closeable { ByteBuffer getBuffer(); void close(); boolean isOpen(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/io/000077500000000000000000000000001420065311100227435ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/io/AsyncReceiverImpl.java000066400000000000000000000650311420065311100271770ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.io; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.Connectors; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import org.xnio.ChannelListener; import org.xnio.channels.StreamSourceChannel; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.StandardCharsets; /** * @author Stuart Douglas */ public class AsyncReceiverImpl implements Receiver { private static final ErrorCallback END_EXCHANGE = new ErrorCallback() { @Override public void error(HttpServerExchange exchange, IOException e) { e.printStackTrace(); exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); UndertowLogger.REQUEST_IO_LOGGER.ioException(e); exchange.endExchange(); } }; public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; private final HttpServerExchange exchange; private final StreamSourceChannel channel; private int maxBufferSize = -1; private boolean paused = false; private boolean done = false; public AsyncReceiverImpl(HttpServerExchange exchange) { this.exchange = exchange; this.channel = exchange.getRequestChannel(); if (channel == null) { throw UndertowMessages.MESSAGES.requestChannelAlreadyProvided(); } } @Override public void setMaxBufferSize(int maxBufferSize) { this.maxBufferSize = maxBufferSize; } @Override public void receiveFullString(final FullStringCallback callback, ErrorCallback errorCallback) { receiveFullString(callback, errorCallback, StandardCharsets.ISO_8859_1); } @Override public void receiveFullString(FullStringCallback callback) { receiveFullString(callback, END_EXCHANGE, StandardCharsets.ISO_8859_1); } @Override public void receivePartialString(PartialStringCallback callback, ErrorCallback errorCallback) { receivePartialString(callback, errorCallback, StandardCharsets.ISO_8859_1); } @Override public void receivePartialString(PartialStringCallback callback) { receivePartialString(callback, END_EXCHANGE, StandardCharsets.ISO_8859_1); } @Override public void receiveFullString(final FullStringCallback callback, final ErrorCallback errorCallback, final Charset charset) { if(done) { throw UndertowMessages.MESSAGES.requestBodyAlreadyRead(); } final ErrorCallback error = errorCallback == null ? END_EXCHANGE : errorCallback; if (callback == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("callback"); } if (exchange.isRequestComplete()) { callback.handle(exchange, ""); return; } String contentLengthString = exchange.getRequestHeaders().getFirst(Headers.CONTENT_LENGTH); long contentLength; final ByteArrayOutputStream sb; if (contentLengthString != null) { contentLength = Long.parseLong(contentLengthString); if (contentLength > Integer.MAX_VALUE) { error.error(exchange, new RequestToLargeException()); return; } sb = new ByteArrayOutputStream((int) contentLength); } else { contentLength = -1; sb = new ByteArrayOutputStream(); } if (maxBufferSize > 0) { if (contentLength > maxBufferSize) { error.error(exchange, new RequestToLargeException()); return; } } PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().allocate(); final ByteBuffer buffer = pooled.getBuffer(); try { int res; do { try { buffer.clear(); res = channel.read(buffer); if (res == -1) { done = true; callback.handle(exchange, sb.toString(charset.name())); return; } else if (res == 0) { channel.getReadSetter().set(new ChannelListener() { @Override public void handleEvent(StreamSourceChannel channel) { if(done) { return; } PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().allocate(); final ByteBuffer buffer = pooled.getBuffer(); try { int res; do { try { buffer.clear(); res = channel.read(buffer); if (res == -1) { done = true; Connectors.executeRootHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { callback.handle(exchange, sb.toString(charset.name())); } }, exchange); return; } else if (res == 0) { return; } else { buffer.flip(); while (buffer.hasRemaining()) { sb.write(buffer.get()); } if (maxBufferSize > 0 && sb.size() > maxBufferSize) { Connectors.executeRootHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { error.error(exchange, new RequestToLargeException()); } }, exchange); return; } } } catch (final IOException e) { Connectors.executeRootHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { error.error(exchange, e); } }, exchange); return; } } while (true); } finally { pooled.close(); } } }); channel.resumeReads(); return; } else { buffer.flip(); while (buffer.hasRemaining()) { sb.write(buffer.get()); } if (maxBufferSize > 0 && sb.size() > maxBufferSize) { error.error(exchange, new RequestToLargeException()); return; } } } catch (IOException e) { error.error(exchange, e); return; } } while (true); } finally { pooled.close(); } } @Override public void receiveFullString(FullStringCallback callback, Charset charset) { receiveFullString(callback, END_EXCHANGE, charset); } @Override public void receivePartialString(final PartialStringCallback callback, final ErrorCallback errorCallback, Charset charset) { if(done) { throw UndertowMessages.MESSAGES.requestBodyAlreadyRead(); } final ErrorCallback error = errorCallback == null ? END_EXCHANGE : errorCallback; if (callback == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("callback"); } if (exchange.isRequestComplete()) { callback.handle(exchange, "", true); return; } String contentLengthString = exchange.getRequestHeaders().getFirst(Headers.CONTENT_LENGTH); long contentLength; if (contentLengthString != null) { contentLength = Long.parseLong(contentLengthString); if (contentLength > Integer.MAX_VALUE) { error.error(exchange, new RequestToLargeException()); return; } } else { contentLength = -1; } if (maxBufferSize > 0) { if (contentLength > maxBufferSize) { error.error(exchange, new RequestToLargeException()); return; } } final CharsetDecoder decoder = charset.newDecoder(); PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().allocate(); final ByteBuffer buffer = pooled.getBuffer(); channel.getReadSetter().set(new ChannelListener() { @Override public void handleEvent(final StreamSourceChannel channel) { if(done || paused) { return; } PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().allocate(); final ByteBuffer buffer = pooled.getBuffer(); try { int res; do { if(paused) { return; } try { buffer.clear(); res = channel.read(buffer); if (res == -1) { done = true; Connectors.executeRootHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { callback.handle(exchange, "", true); } }, exchange); return; } else if (res == 0) { return; } else { buffer.flip(); final CharBuffer cb = decoder.decode(buffer); Connectors.executeRootHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { callback.handle(exchange, cb.toString(), false); if (!paused) { channel.resumeReads(); } else { System.out.println("paused"); } } }, exchange); } } catch (final IOException e) { Connectors.executeRootHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { error.error(exchange, e); } }, exchange); return; } } while (true); } finally { pooled.close(); } } }); try { int res; do { try { buffer.clear(); res = channel.read(buffer); if (res == -1) { done = true; callback.handle(exchange, "", true); return; } else if (res == 0) { channel.resumeReads(); return; } else { buffer.flip(); CharBuffer cb = decoder.decode(buffer); callback.handle(exchange, cb.toString(), false); if(paused) { return; } } } catch (IOException e) { error.error(exchange, e); return; } } while (true); } finally { pooled.close(); } } @Override public void receivePartialString(PartialStringCallback callback, Charset charset) { receivePartialString(callback, END_EXCHANGE, charset); } @Override public void receiveFullBytes(final FullBytesCallback callback, final ErrorCallback errorCallback) { if(done) { throw UndertowMessages.MESSAGES.requestBodyAlreadyRead(); } final ErrorCallback error = errorCallback == null ? END_EXCHANGE : errorCallback; if (callback == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("callback"); } if (exchange.isRequestComplete()) { callback.handle(exchange, EMPTY_BYTE_ARRAY); return; } String contentLengthString = exchange.getRequestHeaders().getFirst(Headers.CONTENT_LENGTH); long contentLength; final ByteArrayOutputStream sb; if (contentLengthString != null) { contentLength = Long.parseLong(contentLengthString); if (contentLength > Integer.MAX_VALUE) { error.error(exchange, new RequestToLargeException()); return; } sb = new ByteArrayOutputStream((int) contentLength); } else { contentLength = -1; sb = new ByteArrayOutputStream(); } if (maxBufferSize > 0) { if (contentLength > maxBufferSize) { error.error(exchange, new RequestToLargeException()); return; } } PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().allocate(); final ByteBuffer buffer = pooled.getBuffer(); try { int res; do { try { buffer.clear(); res = channel.read(buffer); if (res == -1) { done = true; callback.handle(exchange, sb.toByteArray()); return; } else if (res == 0) { channel.getReadSetter().set(new ChannelListener() { @Override public void handleEvent(StreamSourceChannel channel) { if(done) { return; } PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().allocate(); final ByteBuffer buffer = pooled.getBuffer(); try { int res; do { try { buffer.clear(); res = channel.read(buffer); if (res == -1) { done = true; Connectors.executeRootHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { callback.handle(exchange, sb.toByteArray()); } }, exchange); return; } else if (res == 0) { return; } else { buffer.flip(); while (buffer.hasRemaining()) { sb.write(buffer.get()); } if (maxBufferSize > 0 && sb.size() > maxBufferSize) { Connectors.executeRootHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { error.error(exchange, new RequestToLargeException()); } }, exchange); return; } } } catch (final Exception e) { Connectors.executeRootHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { error.error(exchange, new IOException(e)); } }, exchange); return; } } while (true); } finally { pooled.close(); } } }); channel.resumeReads(); return; } else { buffer.flip(); while (buffer.hasRemaining()) { sb.write(buffer.get()); } if (maxBufferSize > 0 && sb.size() > maxBufferSize) { error.error(exchange, new RequestToLargeException()); return; } } } catch (IOException e) { error.error(exchange, e); return; } } while (true); } finally { pooled.close(); } } @Override public void receiveFullBytes(FullBytesCallback callback) { receiveFullBytes(callback, END_EXCHANGE); } @Override public void receivePartialBytes(final PartialBytesCallback callback, final ErrorCallback errorCallback) { if(done) { throw UndertowMessages.MESSAGES.requestBodyAlreadyRead(); } final ErrorCallback error = errorCallback == null ? END_EXCHANGE : errorCallback; if (callback == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("callback"); } if (exchange.isRequestComplete()) { callback.handle(exchange, EMPTY_BYTE_ARRAY, true); return; } String contentLengthString = exchange.getRequestHeaders().getFirst(Headers.CONTENT_LENGTH); long contentLength; if (contentLengthString != null) { contentLength = Long.parseLong(contentLengthString); if (contentLength > Integer.MAX_VALUE) { error.error(exchange, new RequestToLargeException()); return; } } else { contentLength = -1; } if (maxBufferSize > 0) { if (contentLength > maxBufferSize) { error.error(exchange, new RequestToLargeException()); return; } } PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().allocate(); final ByteBuffer buffer = pooled.getBuffer(); channel.getReadSetter().set(new ChannelListener() { @Override public void handleEvent(final StreamSourceChannel channel) { if(done || paused) { return; } PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().allocate(); final ByteBuffer buffer = pooled.getBuffer(); try { int res; do { if(paused) { return; } try { buffer.clear(); res = channel.read(buffer); if (res == -1) { done = true; Connectors.executeRootHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { callback.handle(exchange, EMPTY_BYTE_ARRAY, true); } }, exchange); return; } else if (res == 0) { return; } else { buffer.flip(); final byte[] data = new byte[buffer.remaining()]; buffer.get(data); Connectors.executeRootHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { callback.handle(exchange, data, false); if (!paused) { channel.resumeReads(); } } }, exchange); } } catch (final IOException e) { Connectors.executeRootHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { error.error(exchange, e); } }, exchange); return; } } while (true); } finally { pooled.close(); } } }); try { int res; do { try { buffer.clear(); res = channel.read(buffer); if (res == -1) { done = true; callback.handle(exchange, EMPTY_BYTE_ARRAY, true); return; } else if (res == 0) { channel.resumeReads(); return; } else { buffer.flip(); byte[] data = new byte[buffer.remaining()]; buffer.get(data); callback.handle(exchange, data, false); if(paused) { return; } } } catch (IOException e) { error.error(exchange, e); return; } } while (true); } finally { pooled.close(); } } @Override public void receivePartialBytes(PartialBytesCallback callback) { receivePartialBytes(callback, END_EXCHANGE); } @Override public void pause() { this.paused = true; channel.suspendReads(); } @Override public void resume() { this.paused = false; channel.wakeupReads(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/io/AsyncSenderImpl.java000066400000000000000000000440571420065311100266600ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.io; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import org.xnio.Buffers; import org.xnio.ChannelExceptionHandler; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.channels.StreamSinkChannel; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; /** * @author Stuart Douglas */ public class AsyncSenderImpl implements Sender { private StreamSinkChannel channel; private final HttpServerExchange exchange; private PooledByteBuffer[] pooledBuffers = null; private FileChannel fileChannel; private IoCallback callback; private ByteBuffer[] buffer; /** * This object is not intended to be used in a multi threaded manner * however as we run code after the callback it is possible that another * thread may call send while we are still running * we use the 'writeThread' state guard to protect against this happening. * * During a send() call the 'writeThread' object is set first, followed by the * buffer. The inCallback variable is used to determine if the current thread * is in the process of running a callback. * * After the callback has been invoked the thread that initiated the callback * will only continue to process if it is the writeThread. * */ private volatile Thread writeThread; private volatile Thread inCallback; private ChannelListener writeListener; public class TransferTask implements Runnable, ChannelListener { public boolean run(boolean complete) { try { FileChannel source = fileChannel; long pos = source.position(); long size = source.size(); StreamSinkChannel dest = channel; if (dest == null) { if (callback == IoCallback.END_EXCHANGE) { if (exchange.getResponseContentLength() == -1 && !exchange.getResponseHeaders().contains(Headers.TRANSFER_ENCODING)) { exchange.setResponseContentLength(size); } } channel = dest = exchange.getResponseChannel(); if (dest == null) { throw UndertowMessages.MESSAGES.responseChannelAlreadyProvided(); } } while (size - pos > 0) { long ret = dest.transferFrom(source, pos, size - pos); pos += ret; if (ret == 0) { source.position(pos); dest.getWriteSetter().set(this); dest.resumeWrites(); return false; } } if (complete) { invokeOnComplete(); } } catch (IOException e) { invokeOnException(callback, e); } return true; } @Override public void handleEvent(StreamSinkChannel channel) { channel.suspendWrites(); channel.getWriteSetter().set(null); exchange.dispatch(this); } @Override public void run() { run(true); } } private TransferTask transferTask; public AsyncSenderImpl(final HttpServerExchange exchange) { this.exchange = exchange; } @Override public void send(final ByteBuffer buffer, final IoCallback callback) { writeThread = Thread.currentThread(); if (callback == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("callback"); } if(!exchange.getConnection().isOpen()) { invokeOnException(callback, new ClosedChannelException()); return; } if(exchange.isResponseComplete()) { invokeOnException(callback, new IOException(UndertowMessages.MESSAGES.responseComplete())); } if (this.buffer != null || this.fileChannel != null) { throw UndertowMessages.MESSAGES.dataAlreadyQueued(); } long responseContentLength = exchange.getResponseContentLength(); if(responseContentLength > 0 && buffer.remaining() > responseContentLength) { invokeOnException(callback, UndertowLogger.ROOT_LOGGER.dataLargerThanContentLength(buffer.remaining(), responseContentLength)); return; } StreamSinkChannel channel = this.channel; if (channel == null) { if (callback == IoCallback.END_EXCHANGE) { if (responseContentLength == -1 && !exchange.getResponseHeaders().contains(Headers.TRANSFER_ENCODING)) { exchange.setResponseContentLength(buffer.remaining()); } } this.channel = channel = exchange.getResponseChannel(); if (channel == null) { throw UndertowMessages.MESSAGES.responseChannelAlreadyProvided(); } } this.callback = callback; if (inCallback == Thread.currentThread()) { this.buffer = new ByteBuffer[]{buffer}; return; } try { do { if (buffer.remaining() == 0) { callback.onComplete(exchange, this); return; } int res = channel.write(buffer); if (res == 0) { this.buffer = new ByteBuffer[]{buffer}; this.callback = callback; if(writeListener == null) { initWriteListener(); } channel.getWriteSetter().set(writeListener); channel.resumeWrites(); return; } } while (buffer.hasRemaining()); invokeOnComplete(); } catch (IOException e) { invokeOnException(callback, e); } } @Override public void send(final ByteBuffer[] buffer, final IoCallback callback) { writeThread = Thread.currentThread(); if (callback == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("callback"); } if(!exchange.getConnection().isOpen()) { invokeOnException(callback, new ClosedChannelException()); return; } if(exchange.isResponseComplete()) { invokeOnException(callback, new IOException(UndertowMessages.MESSAGES.responseComplete())); } if (this.buffer != null) { throw UndertowMessages.MESSAGES.dataAlreadyQueued(); } this.callback = callback; if (inCallback == Thread.currentThread()) { this.buffer = buffer; return; } long totalToWrite = Buffers.remaining(buffer); long responseContentLength = exchange.getResponseContentLength(); if(responseContentLength > 0 && totalToWrite > responseContentLength) { invokeOnException(callback, UndertowLogger.ROOT_LOGGER.dataLargerThanContentLength(totalToWrite, responseContentLength)); return; } StreamSinkChannel channel = this.channel; if (channel == null) { if (callback == IoCallback.END_EXCHANGE) { if (responseContentLength == -1 && !exchange.getResponseHeaders().contains(Headers.TRANSFER_ENCODING)) { exchange.setResponseContentLength(totalToWrite); } } this.channel = channel = exchange.getResponseChannel(); if (channel == null) { throw UndertowMessages.MESSAGES.responseChannelAlreadyProvided(); } } final long total = totalToWrite; long written = 0; try { do { long res = channel.write(buffer); written += res; if (res == 0) { this.buffer = buffer; this.callback = callback; if(writeListener == null) { initWriteListener(); } channel.getWriteSetter().set(writeListener); channel.resumeWrites(); return; } } while (written < total); invokeOnComplete(); } catch (IOException e) { invokeOnException(callback, e); } } @Override public void transferFrom(FileChannel source, IoCallback callback) { writeThread = Thread.currentThread(); if (callback == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("callback"); } if(!exchange.getConnection().isOpen()) { invokeOnException(callback, new ClosedChannelException()); return; } if(exchange.isResponseComplete()) { invokeOnException(callback, new IOException(UndertowMessages.MESSAGES.responseComplete())); } if (this.fileChannel != null || this.buffer != null) { throw UndertowMessages.MESSAGES.dataAlreadyQueued(); } this.callback = callback; this.fileChannel = source; if (inCallback == Thread.currentThread()) { return; } if(transferTask == null) { transferTask = new TransferTask(); } if (exchange.isInIoThread()) { exchange.dispatch(transferTask); return; } transferTask.run(); } @Override public void send(final ByteBuffer buffer) { send(buffer, IoCallback.END_EXCHANGE); } @Override public void send(final ByteBuffer[] buffer) { send(buffer, IoCallback.END_EXCHANGE); } @Override public void send(final String data, final IoCallback callback) { send(data, StandardCharsets.UTF_8, callback); } @Override public void send(final String data, final Charset charset, final IoCallback callback) { writeThread = Thread.currentThread(); if(!exchange.getConnection().isOpen()) { invokeOnException(callback, new ClosedChannelException()); return; } if(exchange.isResponseComplete()) { invokeOnException(callback, new IOException(UndertowMessages.MESSAGES.responseComplete())); } ByteBuffer bytes = ByteBuffer.wrap(data.getBytes(charset)); if (bytes.remaining() == 0) { callback.onComplete(exchange, this); } else { int i = 0; ByteBuffer[] bufs = null; while (bytes.hasRemaining()) { PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().allocate(); if (bufs == null) { int noBufs = (bytes.remaining() + pooled.getBuffer().remaining() - 1) / pooled.getBuffer().remaining(); //round up division trick pooledBuffers = new PooledByteBuffer[noBufs]; bufs = new ByteBuffer[noBufs]; } pooledBuffers[i] = pooled; bufs[i] = pooled.getBuffer(); Buffers.copy(pooled.getBuffer(), bytes); pooled.getBuffer().flip(); ++i; } send(bufs, callback); } } @Override public void send(final String data) { send(data, IoCallback.END_EXCHANGE); } @Override public void send(final String data, final Charset charset) { send(data, charset, IoCallback.END_EXCHANGE); } @Override public void close(final IoCallback callback) { try { StreamSinkChannel channel = this.channel; if (channel == null) { if (exchange.getResponseContentLength() == -1 && !exchange.getResponseHeaders().contains(Headers.TRANSFER_ENCODING)) { exchange.setResponseContentLength(0); } this.channel = channel = exchange.getResponseChannel(); if (channel == null) { throw UndertowMessages.MESSAGES.responseChannelAlreadyProvided(); } } channel.shutdownWrites(); if (!channel.flush()) { channel.getWriteSetter().set(ChannelListeners.flushingChannelListener( new ChannelListener() { @Override public void handleEvent(final StreamSinkChannel channel) { if(callback != null) { callback.onComplete(exchange, AsyncSenderImpl.this); } } }, new ChannelExceptionHandler() { @Override public void handleException(final StreamSinkChannel channel, final IOException exception) { try { if(callback != null) { invokeOnException(callback, exception); } } finally { IoUtils.safeClose(channel); } } } )); channel.resumeWrites(); } else { if (callback != null) { callback.onComplete(exchange, this); } } } catch (IOException e) { if (callback != null) { invokeOnException(callback, e); } } } @Override public void close() { close(null); } /** * Invokes the onComplete method. If send is called again in onComplete then * we loop and write it out. This prevents possible stack overflows due to recursion */ private void invokeOnComplete() { for (; ; ) { if (pooledBuffers != null) { for (PooledByteBuffer buffer : pooledBuffers) { buffer.close(); } pooledBuffers = null; } IoCallback callback = this.callback; this.buffer = null; this.fileChannel = null; this.callback = null; writeThread = null; inCallback = Thread.currentThread(); try { callback.onComplete(exchange, this); } finally { inCallback = null; } if (Thread.currentThread() != writeThread) { return; } StreamSinkChannel channel = this.channel; if (this.buffer != null) { long t = Buffers.remaining(buffer); final long total = t; long written = 0; try { do { long res = channel.write(buffer); written += res; if (res == 0) { if(writeListener == null) { initWriteListener(); } channel.getWriteSetter().set(writeListener); channel.resumeWrites(); return; } } while (written < total); //we loop and invoke onComplete again } catch (IOException e) { invokeOnException(callback, e); } } else if (this.fileChannel != null) { if(transferTask == null) { transferTask = new TransferTask(); } if (!transferTask.run(false)) { return; } } else { return; } } } private void invokeOnException(IoCallback callback, IOException e) { if (pooledBuffers != null) { for (PooledByteBuffer buffer : pooledBuffers) { buffer.close(); } pooledBuffers = null; } callback.onException(exchange, this, e); } private void initWriteListener() { writeListener = new ChannelListener() { @Override public void handleEvent(final StreamSinkChannel streamSinkChannel) { try { long toWrite = Buffers.remaining(buffer); long written = 0; while (written < toWrite) { long res = streamSinkChannel.write(buffer, 0, buffer.length); written += res; if (res == 0) { return; } } streamSinkChannel.suspendWrites(); invokeOnComplete(); } catch (IOException e) { streamSinkChannel.suspendWrites(); invokeOnException(callback, e); } } }; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/io/BlockingReceiverImpl.java000066400000000000000000000262611420065311100276540ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.io; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.StandardCharsets; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; /** * @author Stuart Douglas */ public class BlockingReceiverImpl implements Receiver { private static final ErrorCallback END_EXCHANGE = new ErrorCallback() { @Override public void error(HttpServerExchange exchange, IOException e) { if(!exchange.isResponseStarted()) { exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); } exchange.setPersistent(false); UndertowLogger.REQUEST_IO_LOGGER.ioException(e); exchange.endExchange(); } }; public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; private final HttpServerExchange exchange; private final InputStream inputStream; private int maxBufferSize = -1; private boolean done = false; public BlockingReceiverImpl(HttpServerExchange exchange, InputStream inputStream) { this.exchange = exchange; this.inputStream = inputStream; } @Override public void setMaxBufferSize(int maxBufferSize) { this.maxBufferSize = maxBufferSize; } @Override public void receiveFullString(final FullStringCallback callback, ErrorCallback errorCallback) { receiveFullString(callback, errorCallback, StandardCharsets.ISO_8859_1); } @Override public void receiveFullString(FullStringCallback callback) { receiveFullString(callback, END_EXCHANGE, StandardCharsets.ISO_8859_1); } @Override public void receivePartialString(PartialStringCallback callback, ErrorCallback errorCallback) { receivePartialString(callback, errorCallback, StandardCharsets.ISO_8859_1); } @Override public void receivePartialString(PartialStringCallback callback) { receivePartialString(callback, END_EXCHANGE, StandardCharsets.ISO_8859_1); } @Override public void receiveFullString(final FullStringCallback callback, final ErrorCallback errorCallback, final Charset charset) { if(done) { throw UndertowMessages.MESSAGES.requestBodyAlreadyRead(); } final ErrorCallback error = errorCallback == null ? END_EXCHANGE : errorCallback; if (callback == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("callback"); } if (exchange.isRequestComplete()) { callback.handle(exchange, ""); return; } String contentLengthString = exchange.getRequestHeaders().getFirst(Headers.CONTENT_LENGTH); long contentLength; final ByteArrayOutputStream sb; if (contentLengthString != null) { contentLength = Long.parseLong(contentLengthString); if (contentLength > Integer.MAX_VALUE) { error.error(exchange, new RequestToLargeException()); return; } sb = new ByteArrayOutputStream((int) contentLength); } else { contentLength = -1; sb = new ByteArrayOutputStream(); } if (maxBufferSize > 0) { if (contentLength > maxBufferSize) { error.error(exchange, new RequestToLargeException()); return; } } int s; try (PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().getArrayBackedPool().allocate()) { while ((s = inputStream.read(pooled.getBuffer().array(), pooled.getBuffer().arrayOffset(), pooled.getBuffer().remaining())) > 0) { sb.write(pooled.getBuffer().array(), pooled.getBuffer().arrayOffset(), s); } callback.handle(exchange, sb.toString(charset.name())); } catch (IOException e) { error.error(exchange, e); } } @Override public void receiveFullString(FullStringCallback callback, Charset charset) { receiveFullString(callback, END_EXCHANGE, charset); } @Override public void receivePartialString(final PartialStringCallback callback, final ErrorCallback errorCallback, Charset charset) { if(done) { throw UndertowMessages.MESSAGES.requestBodyAlreadyRead(); } final ErrorCallback error = errorCallback == null ? END_EXCHANGE : errorCallback; if (callback == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("callback"); } if (exchange.isRequestComplete()) { callback.handle(exchange, "", true); return; } String contentLengthString = exchange.getRequestHeaders().getFirst(Headers.CONTENT_LENGTH); long contentLength; if (contentLengthString != null) { contentLength = Long.parseLong(contentLengthString); if (contentLength > Integer.MAX_VALUE) { error.error(exchange, new RequestToLargeException()); return; } } else { contentLength = -1; } if (maxBufferSize > 0) { if (contentLength > maxBufferSize) { error.error(exchange, new RequestToLargeException()); return; } } CharsetDecoder decoder = charset.newDecoder(); int s; try (PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().getArrayBackedPool().allocate()) { while ((s = inputStream.read(pooled.getBuffer().array(), pooled.getBuffer().arrayOffset(), pooled.getBuffer().remaining())) > 0) { pooled.getBuffer().limit(s); CharBuffer res = decoder.decode(pooled.getBuffer()); callback.handle(exchange, res.toString(), false); pooled.getBuffer().clear(); } callback.handle(exchange, "", true); } catch (IOException e) { error.error(exchange, e); } } @Override public void receivePartialString(PartialStringCallback callback, Charset charset) { receivePartialString(callback, END_EXCHANGE, charset); } @Override public void receiveFullBytes(final FullBytesCallback callback, final ErrorCallback errorCallback) { if(done) { throw UndertowMessages.MESSAGES.requestBodyAlreadyRead(); } final ErrorCallback error = errorCallback == null ? END_EXCHANGE : errorCallback; if (callback == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("callback"); } if (exchange.isRequestComplete()) { callback.handle(exchange, EMPTY_BYTE_ARRAY); return; } String contentLengthString = exchange.getRequestHeaders().getFirst(Headers.CONTENT_LENGTH); long contentLength; final ByteArrayOutputStream sb; if (contentLengthString != null) { contentLength = Long.parseLong(contentLengthString); if (contentLength > Integer.MAX_VALUE) { error.error(exchange, new RequestToLargeException()); return; } sb = new ByteArrayOutputStream((int) contentLength); } else { contentLength = -1; sb = new ByteArrayOutputStream(); } if (maxBufferSize > 0) { if (contentLength > maxBufferSize) { error.error(exchange, new RequestToLargeException()); return; } } int s; try (PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().getArrayBackedPool().allocate()) { while ((s = inputStream.read(pooled.getBuffer().array(), pooled.getBuffer().arrayOffset(), pooled.getBuffer().remaining())) > 0) { sb.write(pooled.getBuffer().array(), pooled.getBuffer().arrayOffset(), s); } callback.handle(exchange, sb.toByteArray()); } catch (IOException e) { error.error(exchange, e); } } @Override public void receiveFullBytes(FullBytesCallback callback) { receiveFullBytes(callback, END_EXCHANGE); } @Override public void receivePartialBytes(final PartialBytesCallback callback, final ErrorCallback errorCallback) { if(done) { throw UndertowMessages.MESSAGES.requestBodyAlreadyRead(); } final ErrorCallback error = errorCallback == null ? END_EXCHANGE : errorCallback; if (callback == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("callback"); } if (exchange.isRequestComplete()) { callback.handle(exchange, EMPTY_BYTE_ARRAY, true); return; } String contentLengthString = exchange.getRequestHeaders().getFirst(Headers.CONTENT_LENGTH); long contentLength; if (contentLengthString != null) { contentLength = Long.parseLong(contentLengthString); if (contentLength > Integer.MAX_VALUE) { error.error(exchange, new RequestToLargeException()); return; } } else { contentLength = -1; } if (maxBufferSize > 0) { if (contentLength > maxBufferSize) { error.error(exchange, new RequestToLargeException()); return; } } int s; try (PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().getArrayBackedPool().allocate()) { while ((s = inputStream.read(pooled.getBuffer().array(), pooled.getBuffer().arrayOffset(), pooled.getBuffer().remaining())) > 0) { byte[] newData = new byte[s]; System.arraycopy(pooled.getBuffer().array(), pooled.getBuffer().arrayOffset(), newData, 0, s); callback.handle(exchange, newData, false); } callback.handle(exchange, EMPTY_BYTE_ARRAY, true); } catch (IOException e) { error.error(exchange, e); } } @Override public void receivePartialBytes(PartialBytesCallback callback) { receivePartialBytes(callback, END_EXCHANGE); } @Override public void pause() { //noop for blocking receiver } @Override public void resume() { //noop for blocking receiver } } undertow-2.2.16.Final/core/src/main/java/io/undertow/io/BlockingSenderImpl.java000066400000000000000000000305131420065311100273230ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.io; import java.io.EOFException; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; import org.xnio.Buffers; import org.xnio.IoUtils; /** * A sender that uses an output stream. * * @author Stuart Douglas */ public class BlockingSenderImpl implements Sender { private final HttpServerExchange exchange; private final OutputStream outputStream; private volatile Thread inCall; private volatile Thread sendThread; private ByteBuffer[] next; private FileChannel pendingFile; private IoCallback queuedCallback; public BlockingSenderImpl(final HttpServerExchange exchange, final OutputStream outputStream) { this.exchange = exchange; this.outputStream = outputStream; } @Override public void send(final ByteBuffer buffer, final IoCallback callback) { sendThread = Thread.currentThread(); if (inCall == Thread.currentThread()) { queue(new ByteBuffer[]{buffer}, callback); return; } else { long responseContentLength = exchange.getResponseContentLength(); if(responseContentLength > 0 && buffer.remaining() > responseContentLength) { callback.onException(exchange, this, UndertowLogger.ROOT_LOGGER.dataLargerThanContentLength(buffer.remaining(), responseContentLength)); return; } if (!exchange.isResponseStarted() && callback == IoCallback.END_EXCHANGE) { if (responseContentLength == -1 && !exchange.getResponseHeaders().contains(Headers.TRANSFER_ENCODING)) { exchange.setResponseContentLength(buffer.remaining()); } } } if (writeBuffer(buffer, callback)) { invokeOnComplete(callback); } } @Override public void send(final ByteBuffer[] buffer, final IoCallback callback) { sendThread = Thread.currentThread(); if (inCall == Thread.currentThread()) { queue(buffer, callback); return; } else { long responseContentLength = exchange.getResponseContentLength(); if(responseContentLength > 0 && Buffers.remaining(buffer) > responseContentLength) { callback.onException(exchange, this, UndertowLogger.ROOT_LOGGER.dataLargerThanContentLength(Buffers.remaining(buffer), responseContentLength)); return; } if (!exchange.isResponseStarted() && callback == IoCallback.END_EXCHANGE) { if (responseContentLength == -1 && !exchange.getResponseHeaders().contains(Headers.TRANSFER_ENCODING)) { exchange.setResponseContentLength(Buffers.remaining(buffer)); } } } if (!writeBuffer(buffer, callback)) { return; } invokeOnComplete(callback); } @Override public void send(final ByteBuffer buffer) { send(buffer, IoCallback.END_EXCHANGE); } @Override public void send(final ByteBuffer[] buffer) { send(buffer, IoCallback.END_EXCHANGE); } @Override public void send(final String data, final IoCallback callback) { byte[] bytes = data.getBytes(StandardCharsets.UTF_8); sendThread = Thread.currentThread(); if (inCall == Thread.currentThread()) { queue(new ByteBuffer[]{ByteBuffer.wrap(bytes)}, callback); return; } else { long responseContentLength = exchange.getResponseContentLength(); if(responseContentLength > 0 && bytes.length > responseContentLength) { callback.onException(exchange, this, UndertowLogger.ROOT_LOGGER.dataLargerThanContentLength(bytes.length, responseContentLength)); return; } if (!exchange.isResponseStarted() && callback == IoCallback.END_EXCHANGE) { if (responseContentLength == -1 && !exchange.getResponseHeaders().contains(Headers.TRANSFER_ENCODING)) { exchange.setResponseContentLength(bytes.length); } } } try { outputStream.write(bytes); invokeOnComplete(callback); } catch (IOException e) { callback.onException(exchange, this, e); } } @Override public void send(final String data, final Charset charset, final IoCallback callback) { byte[] bytes = data.getBytes(charset); sendThread = Thread.currentThread(); if (inCall == Thread.currentThread()) { queue(new ByteBuffer[]{ByteBuffer.wrap(bytes)}, callback); return; }else { long responseContentLength = exchange.getResponseContentLength(); if(responseContentLength > 0 && bytes.length > responseContentLength) { callback.onException(exchange, this, UndertowLogger.ROOT_LOGGER.dataLargerThanContentLength(bytes.length, responseContentLength)); return; } if (!exchange.isResponseStarted() && callback == IoCallback.END_EXCHANGE) { if (responseContentLength == -1 && !exchange.getResponseHeaders().contains(Headers.TRANSFER_ENCODING)) { exchange.setResponseContentLength(bytes.length); } } } try { outputStream.write(bytes); invokeOnComplete(callback); } catch (IOException e) { callback.onException(exchange, this, e); } } @Override public void send(final String data) { send(data, IoCallback.END_EXCHANGE); } @Override public void send(final String data, final Charset charset) { send(data, charset, IoCallback.END_EXCHANGE); } @Override public void transferFrom(FileChannel source, IoCallback callback) { sendThread = Thread.currentThread(); if (inCall == Thread.currentThread()) { queue(source, callback); return; } performTransfer(source, callback); invokeOnComplete(callback); } private void performTransfer(FileChannel source, IoCallback callback) { if (outputStream instanceof BufferWritableOutputStream) { try { ((BufferWritableOutputStream) outputStream).transferFrom(source); } catch (IOException e) { callback.onException(exchange, this, e); } } else { try (PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().getArrayBackedPool().allocate()){ ByteBuffer buffer = pooled.getBuffer(); long pos = source.position(); long size = source.size(); while (size - pos > 0) { int ret = source.read(buffer); if (ret <= 0) { break; } pos += ret; outputStream.write(buffer.array(), buffer.arrayOffset(), ret); buffer.clear(); } if (pos != size) { throw new EOFException("Unexpected EOF reading file"); } } catch (IOException e) { callback.onException(exchange, this, e); } } } @Override public void close(final IoCallback callback) { try { outputStream.close(); invokeOnComplete(callback); } catch (IOException e) { callback.onException(exchange, this, e); } } @Override public void close() { IoUtils.safeClose(outputStream); } private boolean writeBuffer(final ByteBuffer buffer, final IoCallback callback) { return writeBuffer(new ByteBuffer[]{buffer}, callback); } private boolean writeBuffer(final ByteBuffer[] buffers, final IoCallback callback) { if (outputStream instanceof BufferWritableOutputStream) { //fast path, if the stream can take a buffer directly just write to it try { ((BufferWritableOutputStream) outputStream).write(buffers); return true; } catch (IOException e) { callback.onException(exchange, this, e); return false; } } for (ByteBuffer buffer : buffers) { if (buffer.hasArray()) { try { outputStream.write(buffer.array(), buffer.arrayOffset(), buffer.remaining()); } catch (IOException e) { callback.onException(exchange, this, e); return false; } } else { try (PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().getArrayBackedPool().allocate()) { while (buffer.hasRemaining()) { int toRead = Math.min(buffer.remaining(), pooled.getBuffer().remaining()); buffer.get(pooled.getBuffer().array(), pooled.getBuffer().arrayOffset(), toRead); try { outputStream.write(pooled.getBuffer().array(), pooled.getBuffer().arrayOffset(), toRead); } catch (IOException e) { callback.onException(exchange, this, e); return false; } } } } } return true; } private void invokeOnComplete(final IoCallback callback) { sendThread = null; inCall = Thread.currentThread(); try { callback.onComplete(exchange, this); } finally { inCall = null; } if (Thread.currentThread() != sendThread) { return; } while (next != null || pendingFile != null) { ByteBuffer[] next = this.next; IoCallback queuedCallback = this.queuedCallback; FileChannel file = this.pendingFile; this.next = null; this.queuedCallback = null; this.pendingFile = null; if (next != null) { for (ByteBuffer buffer : next) { writeBuffer(buffer, queuedCallback); } } else if (file != null) { performTransfer(file, queuedCallback); } sendThread = null; inCall = Thread.currentThread(); try { queuedCallback.onComplete(exchange, this); } finally { inCall = null; } if (Thread.currentThread() != sendThread) { return; } } } private void queue(final ByteBuffer[] byteBuffers, final IoCallback ioCallback) { //if data is sent from withing the callback we queue it, to prevent the stack growing indefinitely if (next != null) { throw UndertowMessages.MESSAGES.dataAlreadyQueued(); } next = byteBuffers; queuedCallback = ioCallback; } private void queue(final FileChannel source, final IoCallback ioCallback) { //if data is sent from withing the callback we queue it, to prevent the stack growing indefinitely if (pendingFile != null) { throw UndertowMessages.MESSAGES.dataAlreadyQueued(); } pendingFile = source; queuedCallback = ioCallback; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/io/BufferWritableOutputStream.java000066400000000000000000000022371420065311100311120ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.io; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; /** * Represents an output stream that can write byte buffers * directly. * * @author Stuart Douglas */ public interface BufferWritableOutputStream { void write(final ByteBuffer[] buffers) throws IOException; void write(final ByteBuffer byteBuffer) throws IOException; void transferFrom(FileChannel source) throws IOException; } undertow-2.2.16.Final/core/src/main/java/io/undertow/io/DefaultIoCallback.java000066400000000000000000000040231420065311100270760ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.io; import java.io.IOException; import io.undertow.UndertowLogger; import io.undertow.server.HttpServerExchange; import org.xnio.IoUtils; /** * A default callback implementation that simply ends the exchange * * @author Stuart Douglas * @see IoCallback#END_EXCHANGE */ public class DefaultIoCallback implements IoCallback { private static final IoCallback CALLBACK = new IoCallback() { @Override public void onComplete(final HttpServerExchange exchange, final Sender sender) { exchange.endExchange(); } @Override public void onException(final HttpServerExchange exchange, final Sender sender, final IOException exception) { UndertowLogger.REQUEST_IO_LOGGER.ioException(exception); exchange.endExchange(); } }; protected DefaultIoCallback() { } @Override public void onComplete(final HttpServerExchange exchange, final Sender sender) { sender.close(CALLBACK); } @Override public void onException(final HttpServerExchange exchange, final Sender sender, final IOException exception) { UndertowLogger.REQUEST_IO_LOGGER.ioException(exception); try { exchange.endExchange(); } finally { IoUtils.safeClose(exchange.getConnection()); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/io/IoCallback.java000066400000000000000000000022541420065311100255750ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.io; import java.io.IOException; import io.undertow.server.HttpServerExchange; /** * @author Stuart Douglas */ public interface IoCallback { void onComplete(final HttpServerExchange exchange, final Sender sender); void onException(final HttpServerExchange exchange, final Sender sender, final IOException exception); /** * A default callback that simply ends the exchange. */ IoCallback END_EXCHANGE = new DefaultIoCallback(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/io/Receiver.java000066400000000000000000000203221420065311100253510ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.io; import io.undertow.server.HttpServerExchange; import io.undertow.server.RequestTooBigException; import java.io.IOException; import java.nio.charset.Charset; /** * Interface that provides an easy way to read data from the request. It is lambda compatible. * * @author Stuart Douglas */ public interface Receiver { /** * Sets the maximum amount of data that will be buffered in memory. If you call a receiveFull* method * and the request size is larger than this amount then the error callback with be invoked with a * {@link RequestTooBigException}. * * @param maxBufferSize The maximum amount of data to be buffered */ void setMaxBufferSize(int maxBufferSize); /** * * Reads the request and invokes the callback when the request body has been fully read. * * This string will be interpreted according to {@link java.nio.charset.StandardCharsets#ISO_8859_1}. * * If there is an error reading the request the error callback will be invoked. * * @param callback The callback to invoke with the request * @param errorCallback The callback that is invoked on error */ void receiveFullString(FullStringCallback callback, ErrorCallback errorCallback); /** * * Reads the request and invokes the callback when the request body has been fully read. * * This string will be interpreted according to {@link java.nio.charset.StandardCharsets#ISO_8859_1}. * * If there is an error the exchange will be ended. * * @param callback The callback to invoke with the request */ void receiveFullString(FullStringCallback callback); /** * * Reads the request and invokes the callback with request data. The callback may be invoked multiple * times, and on the last time the last parameter will be true. * * This string will be interpreted according to {@link java.nio.charset.StandardCharsets#ISO_8859_1}. * * If there is an error reading the request the error callback will be invoked. * * @param callback The callback to invoke with the request * @param errorCallback The callback that is invoked on error */ void receivePartialString(PartialStringCallback callback, ErrorCallback errorCallback); /** * * Reads the request and invokes the callback with request data. The callback may be invoked multiple * times, and on the last time the last parameter will be true. * * This string will be interpreted according to {@link java.nio.charset.StandardCharsets#ISO_8859_1}. * * If there is an error the exchange will be ended. * * @param callback The callback to invoke with the request */ void receivePartialString(PartialStringCallback callback); /** * * Reads the request and invokes the callback when the request body has been fully read. * * This string will be interpreted according to the specified charset. * * If there is an error reading the request the error callback will be invoked. * * @param callback The callback to invoke with the request * @param errorCallback The callback that is invoked on error * @param charset The charset that is used to interpret the string */ void receiveFullString(FullStringCallback callback, ErrorCallback errorCallback, Charset charset); /** * * Reads the request and invokes the callback when the request body has been fully read. * * This string will be interpreted according to the specified charset. * * If there is an error the exchange will be ended. * * @param callback The callback to invoke with the request * @param charset The charset that is used to interpret the string */ void receiveFullString(FullStringCallback callback, Charset charset); /** * * Reads the request and invokes the callback with request data. The callback may be invoked multiple * times, and on the last time the last parameter will be true. * * This string will be interpreted according to the specified charset. * * If there is an error reading the request the error callback will be invoked. * * @param callback The callback to invoke with the request * @param errorCallback The callback that is invoked on error * @param charset The charset that is used to interpret the string */ void receivePartialString(PartialStringCallback callback, ErrorCallback errorCallback, Charset charset); /** * * Reads the request and invokes the callback with request data. The callback may be invoked multiple * times, and on the last time the last parameter will be true. * * This string will be interpreted according to the specified charset. * * If there is an error the exchange will be ended. * * @param callback The callback to invoke with the request * @param charset The charset that is used to interpret the string */ void receivePartialString(PartialStringCallback callback, Charset charset); /** * * Reads the request and invokes the callback when the request body has been fully read. * * If there is an error reading the request the error callback will be invoked. * * @param callback The callback to invoke with the request * @param errorCallback The callback that is invoked on error */ void receiveFullBytes(FullBytesCallback callback, ErrorCallback errorCallback); /** * * Reads the request and invokes the callback when the request body has been fully read. * * If there is an error the exchange will be ended. * * @param callback The callback to invoke with the request */ void receiveFullBytes(FullBytesCallback callback); /** * * Reads the request and invokes the callback with request data. The callback may be invoked multiple * times, and on the last time the last parameter will be true. * * If there is an error reading the request the error callback will be invoked. * * @param callback The callback to invoke with the request * @param errorCallback The callback that is invoked on error */ void receivePartialBytes(PartialBytesCallback callback, ErrorCallback errorCallback); /** * * Reads the request and invokes the callback with request data. The callback may be invoked multiple * times, and on the last time the last parameter will be true. * * * If there is an error the exchange will be ended. * * @param callback The callback to invoke with the request */ void receivePartialBytes(PartialBytesCallback callback); /** * When receiving partial data calling this method will pause the callbacks. Callbacks will not resume until * {@link #resume()} has been called. */ void pause(); /** * Resumes paused callbacks. */ void resume(); interface ErrorCallback { void error(HttpServerExchange exchange, IOException e); } interface FullStringCallback { void handle(HttpServerExchange exchange, String message); } interface FullBytesCallback { void handle(HttpServerExchange exchange, byte[] message); } interface PartialStringCallback { void handle(HttpServerExchange exchange, String message, boolean last); } interface PartialBytesCallback { void handle(HttpServerExchange exchange, byte[] message, boolean last); } class RequestToLargeException extends IOException {} } undertow-2.2.16.Final/core/src/main/java/io/undertow/io/Sender.java000066400000000000000000000076351420065311100250410ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.io; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.Charset; /** * Sender interface that allows for callback based async IO. * * Note that all methods on this class are asynchronous, and may result in dispatch to an IO thread. After calling * a method on this class you should not perform any more work on the current exchange until the callback is invoked. * * NOTE: implementers of this interface should be careful that they do not recursively call onComplete, which can * lead to stack overflows if send is called many times. * * * @author Stuart Douglas */ public interface Sender { /** * Write the given buffer using async IO, and calls the given callback on completion or error. * * @param buffer The buffer to send. * @param callback The callback */ void send(final ByteBuffer buffer, final IoCallback callback); /** * Write the given buffers using async IO, and calls the given callback on completion or error. * * @param buffer The buffers to send. * @param callback The callback */ void send(final ByteBuffer[] buffer, final IoCallback callback); /** * Write the given buffer using async IO, and ends the exchange when done * * @param buffer The buffer to send. */ void send(final ByteBuffer buffer); /** * Write the given buffers using async IO, and ends the exchange when done * * @param buffer The buffers to send. */ void send(final ByteBuffer[] buffer); /** * Write the given String using async IO, and calls the given callback on completion or error. *

* The CharSequence is encoded to UTF8 * * @param data The data to send * @param callback The callback */ void send(final String data, final IoCallback callback); /** * Write the given String using async IO, and calls the given callback on completion or error. * * @param data The buffer to end. * @param charset The charset to use * @param callback The callback */ void send(final String data, final Charset charset, final IoCallback callback); /** * Write the given String using async IO, and ends the exchange when done *

* The CharSequence is encoded to UTF8 * * @param data The data to send */ void send(final String data); /** * Write the given String using async IO, and ends the exchange when done * * @param data The buffer to end. * @param charset The charset to use */ void send(final String data, final Charset charset); /** * Transfers all content from the specified file * * @param channel the file channel to transfer * @param callback The callback */ void transferFrom(final FileChannel channel, final IoCallback callback); /** * Closes this sender asynchronously. The given callback is notified on completion * * @param callback The callback that is notified when all data has been flushed and the channel is closed */ void close(final IoCallback callback); /** * Closes this sender asynchronously * */ void close(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/io/UndertowInputStream.java000066400000000000000000000122441420065311100276140ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.io; import io.undertow.UndertowMessages; import io.undertow.server.HttpServerExchange; import org.xnio.Buffers; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import org.xnio.channels.Channels; import org.xnio.channels.EmptyStreamSourceChannel; import org.xnio.channels.StreamSourceChannel; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.anyAreSet; /** * Input stream that reads from the underlying channel. This stream delays creation * of the channel till it is actually used. * * @author Stuart Douglas */ public class UndertowInputStream extends InputStream { private final StreamSourceChannel channel; private final ByteBufferPool bufferPool; /** * If this stream is ready for a read */ private static final int FLAG_CLOSED = 1; private static final int FLAG_FINISHED = 1 << 1; private int state; private PooledByteBuffer pooled; public UndertowInputStream(final HttpServerExchange exchange) { if (exchange.isRequestChannelAvailable()) { this.channel = exchange.getRequestChannel(); } else { this.channel = new EmptyStreamSourceChannel(exchange.getIoThread()); } this.bufferPool = exchange.getConnection().getByteBufferPool(); } @Override public int read() throws IOException { byte[] b = new byte[1]; int read = read(b); if (read == -1) { return -1; } return b[0] & 0xff; } @Override public int read(final byte[] b) throws IOException { return read(b, 0, b.length); } @Override public int read(final byte[] b, final int off, final int len) throws IOException { if(Thread.currentThread() == channel.getIoThread()) { throw UndertowMessages.MESSAGES.blockingIoFromIOThread(); } if (anyAreSet(state, FLAG_CLOSED)) { throw UndertowMessages.MESSAGES.streamIsClosed(); } readIntoBuffer(); if (anyAreSet(state, FLAG_FINISHED)) { return -1; } if (len == 0) { return 0; } ByteBuffer buffer = pooled.getBuffer(); int copied = Buffers.copy(ByteBuffer.wrap(b, off, len), buffer); if (!buffer.hasRemaining()) { pooled.close(); pooled = null; } return copied; } private void readIntoBuffer() throws IOException { if (pooled == null && !anyAreSet(state, FLAG_FINISHED)) { pooled = bufferPool.allocate(); int res = Channels.readBlocking(channel, pooled.getBuffer()); pooled.getBuffer().flip(); if (res == -1) { state |= FLAG_FINISHED; pooled.close(); pooled = null; } } } private void readIntoBufferNonBlocking() throws IOException { if (pooled == null && !anyAreSet(state, FLAG_FINISHED)) { pooled = bufferPool.allocate(); int res = channel.read(pooled.getBuffer()); if (res == 0) { pooled.close(); pooled = null; return; } pooled.getBuffer().flip(); if (res == -1) { state |= FLAG_FINISHED; pooled.close(); pooled = null; } } } @Override public int available() throws IOException { if (anyAreSet(state, FLAG_CLOSED)) { throw UndertowMessages.MESSAGES.streamIsClosed(); } readIntoBufferNonBlocking(); if (anyAreSet(state, FLAG_FINISHED)) { return -1; } if (pooled == null) { return 0; } return pooled.getBuffer().remaining(); } @Override public void close() throws IOException { if (anyAreSet(state, FLAG_CLOSED)) { return; } state |= FLAG_CLOSED; try { while (allAreClear(state, FLAG_FINISHED)) { readIntoBuffer(); if (pooled != null) { pooled.close(); pooled = null; } } } finally { if (pooled != null) { pooled.close(); pooled = null; } channel.shutdownReads(); state |= FLAG_FINISHED; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/io/UndertowOutputStream.java000066400000000000000000000323321420065311100300150ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.io; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import io.undertow.UndertowMessages; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; import io.undertow.util.Methods; import org.xnio.Buffers; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import org.xnio.IoUtils; import org.xnio.channels.Channels; import org.xnio.channels.StreamSinkChannel; import static org.xnio.Bits.anyAreClear; import static org.xnio.Bits.anyAreSet; /** * Buffering output stream that wraps a channel. *

* This stream delays channel creation, so if a response will fit in the buffer it is not necessary to * set the content length header. * * @author Stuart Douglas */ public class UndertowOutputStream extends OutputStream implements BufferWritableOutputStream { private final HttpServerExchange exchange; private ByteBuffer buffer; private PooledByteBuffer pooledBuffer; private StreamSinkChannel channel; private int state; private long written; private final long contentLength; private static final int FLAG_CLOSED = 1; private static final int FLAG_WRITE_STARTED = 1 << 1; private static final int MAX_BUFFERS_TO_ALLOCATE = 10; /** * Construct a new instance. No write timeout is configured. * * @param exchange The exchange */ public UndertowOutputStream(HttpServerExchange exchange) { this.exchange = exchange; this.contentLength = exchange.getResponseContentLength(); } /** * If the response has not yet been written to the client this method will clear the streams buffer, * invalidating any content that has already been written. If any content has already been sent to the client then * this method will throw and IllegalStateException * * @throws java.lang.IllegalStateException If the response has been committed */ public void resetBuffer() { if(anyAreSet(state, FLAG_WRITE_STARTED)) { throw UndertowMessages.MESSAGES.cannotResetBuffer(); } buffer = null; IoUtils.safeClose(pooledBuffer); pooledBuffer = null; written = 0; } public long getBytesWritten() { return written; } /** * {@inheritDoc} */ public void write(final int b) throws IOException { write(new byte[]{(byte) b}, 0, 1); } /** * {@inheritDoc} */ public void write(final byte[] b) throws IOException { write(b, 0, b.length); } /** * {@inheritDoc} */ public void write(final byte[] b, final int off, final int len) throws IOException { if (len < 1) { return; } if (exchange.isInIoThread()) { throw UndertowMessages.MESSAGES.blockingIoFromIOThread(); } if (anyAreSet(state, FLAG_CLOSED)) { throw UndertowMessages.MESSAGES.streamIsClosed(); } //if this is the last of the content ByteBuffer buffer = buffer(); if (len == contentLength - written || buffer.remaining() < len) { if (buffer.remaining() < len) { //so what we have will not fit. //We allocate multiple buffers up to MAX_BUFFERS_TO_ALLOCATE //and put it in them //if it still dopes not fit we loop, re-using these buffers StreamSinkChannel channel = this.channel; if (channel == null) { this.channel = channel = exchange.getResponseChannel(); } final ByteBufferPool bufferPool = exchange.getConnection().getByteBufferPool(); ByteBuffer[] buffers = new ByteBuffer[MAX_BUFFERS_TO_ALLOCATE + 1]; PooledByteBuffer[] pooledBuffers = new PooledByteBuffer[MAX_BUFFERS_TO_ALLOCATE]; try { buffers[0] = buffer; int bytesWritten = 0; int rem = buffer.remaining(); buffer.put(b, bytesWritten + off, rem); buffer.flip(); bytesWritten += rem; int bufferCount = 1; for (int i = 0; i < MAX_BUFFERS_TO_ALLOCATE; ++i) { PooledByteBuffer pooled = bufferPool.allocate(); pooledBuffers[bufferCount - 1] = pooled; buffers[bufferCount++] = pooled.getBuffer(); ByteBuffer cb = pooled.getBuffer(); int toWrite = len - bytesWritten; if (toWrite > cb.remaining()) { rem = cb.remaining(); cb.put(b, bytesWritten + off, rem); cb.flip(); bytesWritten += rem; } else { cb.put(b, bytesWritten + off, len - bytesWritten); bytesWritten = len; cb.flip(); break; } } Channels.writeBlocking(channel, buffers, 0, bufferCount); while (bytesWritten < len) { //ok, it did not fit, loop and loop and loop until it is done bufferCount = 0; for (int i = 0; i < MAX_BUFFERS_TO_ALLOCATE + 1; ++i) { ByteBuffer cb = buffers[i]; cb.clear(); bufferCount++; int toWrite = len - bytesWritten; if (toWrite > cb.remaining()) { rem = cb.remaining(); cb.put(b, bytesWritten + off, rem); cb.flip(); bytesWritten += rem; } else { cb.put(b, bytesWritten + off, len - bytesWritten); bytesWritten = len; cb.flip(); break; } } Channels.writeBlocking(channel, buffers, 0, bufferCount); } buffer.clear(); } finally { for (int i = 0; i < pooledBuffers.length; ++i) { PooledByteBuffer p = pooledBuffers[i]; if (p == null) { break; } p.close(); } } } else { buffer.put(b, off, len); if (buffer.remaining() == 0) { writeBufferBlocking(false); } } } else { buffer.put(b, off, len); if (buffer.remaining() == 0) { writeBufferBlocking(false); } } updateWritten(len); } @Override public void write(ByteBuffer[] buffers) throws IOException { if (anyAreSet(state, FLAG_CLOSED)) { throw UndertowMessages.MESSAGES.streamIsClosed(); } int len = 0; for (ByteBuffer buf : buffers) { len += buf.remaining(); } if (len < 1) { return; } //if we have received the exact amount of content write it out in one go //this is a common case when writing directly from a buffer cache. if (this.written == 0 && len == contentLength) { if (channel == null) { channel = exchange.getResponseChannel(); } Channels.writeBlocking(channel, buffers, 0, buffers.length); state |= FLAG_WRITE_STARTED; } else { ByteBuffer buffer = buffer(); if (len < buffer.remaining()) { Buffers.copy(buffer, buffers, 0, buffers.length); } else { if (channel == null) { channel = exchange.getResponseChannel(); } if (buffer.position() == 0) { Channels.writeBlocking(channel, buffers, 0, buffers.length); } else { final ByteBuffer[] newBuffers = new ByteBuffer[buffers.length + 1]; buffer.flip(); newBuffers[0] = buffer; System.arraycopy(buffers, 0, newBuffers, 1, buffers.length); Channels.writeBlocking(channel, newBuffers, 0, newBuffers.length); buffer.clear(); } state |= FLAG_WRITE_STARTED; } } updateWritten(len); } @Override public void write(ByteBuffer byteBuffer) throws IOException { write(new ByteBuffer[]{byteBuffer}); } void updateWritten(final long len) throws IOException { this.written += len; if (contentLength != -1 && this.written >= contentLength) { flush(); close(); } } /** * {@inheritDoc} */ public void flush() throws IOException { if (anyAreSet(state, FLAG_CLOSED)) { throw UndertowMessages.MESSAGES.streamIsClosed(); } if (buffer != null && buffer.position() != 0) { writeBufferBlocking(false); } if (channel == null) { channel = exchange.getResponseChannel(); } Channels.flushBlocking(channel); } private void writeBufferBlocking(final boolean writeFinal) throws IOException { if (channel == null) { channel = exchange.getResponseChannel(); } buffer.flip(); while (buffer.hasRemaining()) { if(writeFinal) { channel.writeFinal(buffer); } else { channel.write(buffer); } if(buffer.hasRemaining()) { channel.awaitWritable(); } } buffer.clear(); state |= FLAG_WRITE_STARTED; } @Override public void transferFrom(FileChannel source) throws IOException { if (anyAreSet(state, FLAG_CLOSED)) { throw UndertowMessages.MESSAGES.streamIsClosed(); } if (buffer != null && buffer.position() != 0) { writeBufferBlocking(false); } if (channel == null) { channel = exchange.getResponseChannel(); } long position = source.position(); long size = source.size(); Channels.transferBlocking(channel, source, position, size); updateWritten(size - position); } /** * {@inheritDoc} */ public void close() throws IOException { if (anyAreSet(state, FLAG_CLOSED)) return; try { state |= FLAG_CLOSED; if (anyAreClear(state, FLAG_WRITE_STARTED) && channel == null && !isHeadRequestWithContentLength(exchange)) { if (buffer == null) { exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, "0"); } else { exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, "" + buffer.position()); } } if (buffer != null) { writeBufferBlocking(true); } if (channel == null) { channel = exchange.getResponseChannel(); } if(channel == null) { return; } StreamSinkChannel channel = this.channel; channel.shutdownWrites(); Channels.flushBlocking(channel); } finally { if (pooledBuffer != null) { pooledBuffer.close(); buffer = null; } else { buffer = null; } } } // Head request handlers may set the content-length response header in lieu of writing bytes private static boolean isHeadRequestWithContentLength(HttpServerExchange exchange) { return Methods.HEAD.equals(exchange.getRequestMethod()) && exchange.getResponseHeaders().contains(Headers.CONTENT_LENGTH); } private ByteBuffer buffer() { ByteBuffer buffer = this.buffer; if (buffer != null) { return buffer; } this.pooledBuffer = exchange.getConnection().getByteBufferPool().allocate(); this.buffer = pooledBuffer.getBuffer(); return this.buffer; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/000077500000000000000000000000001420065311100242745ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/AndPredicate.java000066400000000000000000000031111420065311100274560ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import io.undertow.server.HttpServerExchange; /** * @author Stuart Douglas */ class AndPredicate implements Predicate { private final Predicate[] predicates; AndPredicate(final Predicate ... predicates) { this.predicates = predicates; } @Override public boolean resolve(final HttpServerExchange value) { for(final Predicate predicate : predicates) { if(!predicate.resolve(value)) { return false; } } return true; } @Override public String toString() { StringBuilder result = new StringBuilder(); for(final Predicate predicate : predicates) { if( result.length() > 0 ) { result.append(" and "); } result.append(predicate.toString()); } return result.toString(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/AuthenticationRequiredPredicate.java000066400000000000000000000030161420065311100334400ustar00rootroot00000000000000package io.undertow.predicate; import io.undertow.security.api.SecurityContext; import io.undertow.server.HttpServerExchange; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Predicate that returns true if authentication is required. * * @author Stuart Douglas */ public class AuthenticationRequiredPredicate implements Predicate { public static final AuthenticationRequiredPredicate INSTANCE = new AuthenticationRequiredPredicate(); @Override public boolean resolve(HttpServerExchange value) { SecurityContext sc = value.getSecurityContext(); if(sc == null) { return false; } return sc.isAuthenticationRequired(); } @Override public String toString() { return "auth-required()"; } public static class Builder implements PredicateBuilder { @Override public String name() { return "auth-required"; } @Override public Map> parameters() { final Map> params = new HashMap<>(); return params; } @Override public Set requiredParameters() { final Set params = new HashSet<>(); return params; } @Override public String defaultParameter() { return null; } @Override public Predicate build(final Map config) { return INSTANCE; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/ContainsPredicate.java000066400000000000000000000064111420065311100305400ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import io.undertow.attribute.ExchangeAttribute; import io.undertow.server.HttpServerExchange; /** * Returns true if the request header is present and contains one of the strings to match. * * @author Stuart Douglas */ public class ContainsPredicate implements Predicate { private final ExchangeAttribute attribute; private final String[] values; ContainsPredicate(final ExchangeAttribute attribute, final String[] values) { this.attribute = attribute; this.values = new String[values.length]; System.arraycopy(values, 0, this.values, 0, values.length); } @Override public boolean resolve(final HttpServerExchange value) { String attr = attribute.readAttribute(value); if (attr == null) { return false; } for (int i = 0; i < values.length; ++i) { if (attr.contains(values[i])) { return true; } } return false; } public ExchangeAttribute getAttribute() { return attribute; } public String[] getValues() { String[] ret = new String[values.length]; System.arraycopy(values, 0, ret, 0, values.length); return ret; } @Override public String toString() { return "contains( search={" + String.join(", ", Arrays.asList( values ) ) + "}, value='" + attribute.toString() + "' )"; } public static class Builder implements PredicateBuilder { @Override public String name() { return "contains"; } @Override public Map> parameters() { final Map> params = new HashMap<>(); params.put("value", ExchangeAttribute.class); params.put("search", String[].class); return params; } @Override public Set requiredParameters() { final Set params = new HashSet<>(); params.add("value"); params.add("search"); return params; } @Override public String defaultParameter() { return null; } @Override public Predicate build(final Map config) { String[] search = (String[]) config.get("search"); ExchangeAttribute values = (ExchangeAttribute) config.get("value"); return new ContainsPredicate(values, search); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/EqualsPredicate.java000066400000000000000000000056641420065311100302250ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import io.undertow.attribute.ExchangeAttribute; import io.undertow.server.HttpServerExchange; /** * Returns true if all the provided arguments are equal to each other * * @author Stuart Douglas */ public class EqualsPredicate implements Predicate { private final ExchangeAttribute[] attributes; EqualsPredicate(final ExchangeAttribute[] attributes) { this.attributes = attributes; } @Override public boolean resolve(final HttpServerExchange value) { if(attributes.length < 2) { return true; } String first = attributes[0].readAttribute(value); for(int i = 1; i < attributes.length; ++i) { String current = attributes[i].readAttribute(value); if(first == null) { if(current != null) { return false; } } else if(current == null) { return false; } else if(!first.equals(current)) { return false; } } return true; } @Override public String toString() { return "equals( {" + Arrays.asList( attributes ).stream().map(a->a.toString()).collect(Collectors.joining(", ")) + "} )"; } public static class Builder implements PredicateBuilder { @Override public String name() { return "equals"; } @Override public Map> parameters() { final Map> params = new HashMap<>(); params.put("value", ExchangeAttribute[].class); return params; } @Override public Set requiredParameters() { return Collections.singleton("value"); } @Override public String defaultParameter() { return "value"; } @Override public Predicate build(final Map config) { ExchangeAttribute[] value = (ExchangeAttribute[]) config.get("value"); return new EqualsPredicate(value); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/ExistsPredicate.java000066400000000000000000000046121420065311100302420ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import io.undertow.attribute.ExchangeAttribute; import io.undertow.server.HttpServerExchange; /** * Returns true if the given attribute is not null and not an empty string * * @author Stuart Douglas */ public class ExistsPredicate implements Predicate { private final ExchangeAttribute attribute; ExistsPredicate(final ExchangeAttribute attribute) { this.attribute = attribute; } @Override public boolean resolve(final HttpServerExchange value) { final String att = attribute.readAttribute(value); if(att == null) { return false; } return !att.isEmpty(); } @Override public String toString() { return "exists( '" + attribute.toString() + "' )"; } public static class Builder implements PredicateBuilder { @Override public String name() { return "exists"; } @Override public Map> parameters() { final Map> params = new HashMap<>(); params.put("value", ExchangeAttribute.class); return params; } @Override public Set requiredParameters() { return Collections.singleton("value"); } @Override public String defaultParameter() { return "value"; } @Override public Predicate build(final Map config) { ExchangeAttribute value = (ExchangeAttribute) config.get("value"); return new ExistsPredicate(value); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/FalsePredicate.java000066400000000000000000000022721420065311100300150ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import io.undertow.server.HttpServerExchange; /** * @author Stuart Douglas */ public class FalsePredicate implements Predicate { public static final FalsePredicate INSTANCE = new FalsePredicate(); public static FalsePredicate instance() { return INSTANCE; } @Override public boolean resolve(final HttpServerExchange value) { return false; } @Override public String toString() { return "false"; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/IdempotentPredicate.java000066400000000000000000000045771420065311100311050ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import io.undertow.server.HttpServerExchange; import io.undertow.util.HttpString; import io.undertow.util.Methods; /** * A predicate that returns true if the request is idempotent * according to the HTTP RFC. * * @author Stuart Douglas */ public class IdempotentPredicate implements Predicate { public static final IdempotentPredicate INSTANCE = new IdempotentPredicate(); private static final Set METHODS; static { Set methods = new HashSet<>(); methods.add(Methods.GET); methods.add(Methods.DELETE); methods.add(Methods.PUT); methods.add(Methods.HEAD); methods.add(Methods.OPTIONS); METHODS = Collections.unmodifiableSet(methods); } @Override public boolean resolve(HttpServerExchange value) { return METHODS.contains(value.getRequestMethod()); } @Override public String toString() { return "idempotent()"; } public static class Builder implements PredicateBuilder { @Override public String name() { return "idempotent"; } @Override public Map> parameters() { return Collections.emptyMap(); } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return null; } @Override public Predicate build(Map config) { return INSTANCE; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/MaxContentSizePredicate.java000066400000000000000000000046031420065311100316760ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import java.util.Collections; import java.util.Map; import java.util.Set; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; /** * Predicate that returns true if the Content-Size of a request is above a * given value. * * Use {@link RequestLargerThanPredicate} instead. * * @author Stuart Douglas */ @Deprecated public class MaxContentSizePredicate implements Predicate { private final long maxSize; MaxContentSizePredicate(final long maxSize) { this.maxSize = maxSize; } @Override public boolean resolve(final HttpServerExchange value) { final String length = value.getResponseHeaders().getFirst(Headers.CONTENT_LENGTH); if (length == null) { return false; } return Long.parseLong(length) > maxSize; } @Override public String toString() { return "max-content-size( " + maxSize + " )"; } public static class Builder implements PredicateBuilder { @Override public String name() { return "max-content-size"; } @Override public Map> parameters() { return Collections.>singletonMap("value", Long.class); } @Override public Set requiredParameters() { return Collections.singleton("value"); } @Override public String defaultParameter() { return "value"; } @Override public Predicate build(final Map config) { Long max = (Long) config.get("value"); return new MaxContentSizePredicate(max); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/MethodPredicate.java000066400000000000000000000052001420065311100301750ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import io.undertow.server.HttpServerExchange; import io.undertow.util.HttpString; /** * @author Stuart Douglas */ public class MethodPredicate implements Predicate { private final HttpString[] methods; MethodPredicate(String[] methods) { HttpString[] values = new HttpString[methods.length]; for(int i = 0; i < methods.length; ++i) { values[i] = HttpString.tryFromString(methods[i]); } this.methods = values; } @Override public boolean resolve(final HttpServerExchange value) { for(int i =0; i < methods.length; ++i) { if(value.getRequestMethod().equals(methods[i])) { return true; } } return false; } @Override public String toString() { if( methods.length == 1 ) { return "method( '" + methods[0] + "' )"; } else { return "method( {" + Arrays.asList( methods ).stream().map(s->s.toString()).collect(Collectors.joining(", ")) + "} )"; } } public static class Builder implements PredicateBuilder { @Override public String name() { return "method"; } @Override public Map> parameters() { return Collections.>singletonMap("value", String[].class); } @Override public Set requiredParameters() { return Collections.singleton("value"); } @Override public String defaultParameter() { return "value"; } @Override public Predicate build(final Map config) { String[] methods = (String[]) config.get("value"); return new MethodPredicate(methods); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/MinContentSizePredicate.java000066400000000000000000000046041420065311100316750ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import java.util.Collections; import java.util.Map; import java.util.Set; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; /** * Predicate that returns true if the Content-Size of a request is below a * given value. * * Use {@link RequestSmallerThanPredicate} instead. * * @author Stuart Douglas */ @Deprecated public class MinContentSizePredicate implements Predicate { private final long minSize; MinContentSizePredicate(final long minSize) { this.minSize = minSize; } @Override public boolean resolve(final HttpServerExchange value) { final String length = value.getResponseHeaders().getFirst(Headers.CONTENT_LENGTH); if (length == null) { return false; } return Long.parseLong(length) < minSize; } @Override public String toString() { return "max-content-size( " + minSize + " )"; } public static class Builder implements PredicateBuilder { @Override public String name() { return "min-content-size"; } @Override public Map> parameters() { return Collections.>singletonMap("value", Long.class); } @Override public Set requiredParameters() { return Collections.singleton("value"); } @Override public String defaultParameter() { return "value"; } @Override public Predicate build(final Map config) { Long max = (Long) config.get("value"); return new MinContentSizePredicate(max); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/NotPredicate.java000066400000000000000000000023151420065311100275210ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import io.undertow.server.HttpServerExchange; /** * @author Stuart Douglas */ public class NotPredicate implements Predicate { private final Predicate predicate; NotPredicate(final Predicate predicate) { this.predicate = predicate; } @Override public boolean resolve(final HttpServerExchange value) { return !predicate.resolve(value); } @Override public String toString() { return " not " + predicate.toString(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/OrPredicate.java000066400000000000000000000031061420065311100273400ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import io.undertow.server.HttpServerExchange; /** * @author Stuart Douglas */ class OrPredicate implements Predicate { private final Predicate[] predicates; OrPredicate(final Predicate... predicates) { this.predicates = predicates; } @Override public boolean resolve(final HttpServerExchange value) { for (final Predicate predicate : predicates) { if (predicate.resolve(value)) { return true; } } return false; } @Override public String toString() { StringBuilder result = new StringBuilder(); for(final Predicate predicate : predicates) { if( result.length() > 0 ) { result.append(" or "); } result.append(predicate.toString()); } return result.toString(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/PathMatchPredicate.java000066400000000000000000000063731420065311100306420ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import io.undertow.server.HttpServerExchange; import io.undertow.util.PathMatcher; import io.undertow.UndertowLogger; /** * @author Stuart Douglas */ public class PathMatchPredicate implements Predicate { private final PathMatcher pathMatcher; private static final boolean traceEnabled; static { traceEnabled = UndertowLogger.PREDICATE_LOGGER.isTraceEnabled(); } PathMatchPredicate(final String... paths) { PathMatcher matcher = new PathMatcher<>(); for(String path : paths) { if(!path.startsWith("/")) { matcher.addExactPath("/" + path, Boolean.TRUE); } else { matcher.addExactPath(path, Boolean.TRUE); } } this.pathMatcher = matcher; } public String toString() { Set matches = pathMatcher.getExactPathMatchesSet(); if( matches.size() == 1 ) { return "path( '" + matches.toArray()[0] + "' )"; } else { return "path( { '" + matches.stream().collect(Collectors.joining("', '")) + "' } )"; } } @Override public boolean resolve(final HttpServerExchange value) { final String relativePath = value.getRelativePath(); PathMatcher.PathMatch result = pathMatcher.match(relativePath); boolean matches = Boolean.TRUE.equals(result.getValue()); if (traceEnabled) { UndertowLogger.PREDICATE_LOGGER.tracef( "Path(s) [%s] %s input [%s] for %s.", pathMatcher.getExactPathMatchesSet().stream().collect(Collectors.joining(", ")), ( matches ? "MATCH" : "DO NOT MATCH" ), relativePath, value ); } return matches; } public static class Builder implements PredicateBuilder { @Override public String name() { return "path"; } @Override public Map> parameters() { return Collections.>singletonMap("path", String[].class); } @Override public Set requiredParameters() { return Collections.singleton("path"); } @Override public String defaultParameter() { return "path"; } @Override public Predicate build(final Map config) { String[] path = (String[]) config.get("path"); return new PathMatchPredicate(path); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/PathPrefixPredicate.java000066400000000000000000000074701420065311100310420ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.stream.Collectors; import io.undertow.server.HttpServerExchange; import io.undertow.util.PathMatcher; import io.undertow.UndertowLogger; /** * @author Stuart Douglas */ public class PathPrefixPredicate implements Predicate { private final PathMatcher pathMatcher; private static final boolean traceEnabled; static { traceEnabled = UndertowLogger.PREDICATE_LOGGER.isTraceEnabled(); } PathPrefixPredicate(final String... paths) { PathMatcher matcher = new PathMatcher<>(); for(String path : paths) { if(!path.startsWith("/")) { matcher.addPrefixPath("/" + path, Boolean.TRUE); } else { matcher.addPrefixPath(path, Boolean.TRUE); } } this.pathMatcher = matcher; } @Override public boolean resolve(final HttpServerExchange value) { final String relativePath = value.getRelativePath(); PathMatcher.PathMatch result = pathMatcher.match(relativePath); boolean matches = Boolean.TRUE.equals(result.getValue()); if (traceEnabled) { UndertowLogger.PREDICATE_LOGGER.tracef("Path prefix(s) [%s] %s input [%s] for %s.", pathMatcher.getPathMatchesSet().stream().collect(Collectors.joining(", ")), (matches ? "MATCH" : "DO NOT MATCH" ), relativePath, value); } if(matches) { Map context = value.getAttachment(PREDICATE_CONTEXT); if(context == null) { value.putAttachment(PREDICATE_CONTEXT, context = new TreeMap<>()); } if (traceEnabled && result.getRemaining().length() > 0 ) { UndertowLogger.PREDICATE_LOGGER.tracef("Storing \"remaining\" string of [%s] for %s.", result.getRemaining(), value); } context.put("remaining", result.getRemaining()); } return matches; } public String toString() { Set matches = pathMatcher.getPathMatchesSet(); if( matches.size() == 1 ) { return "path-prefix( '" + matches.toArray()[0] + "' )"; } else { return "path-prefix( { '" + matches.stream().collect(Collectors.joining("', '")) + "' } )"; } } public static class Builder implements PredicateBuilder { @Override public String name() { return "path-prefix"; } @Override public Map> parameters() { return Collections.>singletonMap("path", String[].class); } @Override public Set requiredParameters() { return Collections.singleton("path"); } @Override public String defaultParameter() { return "path"; } @Override public Predicate build(final Map config) { String[] path = (String[]) config.get("path"); return new PathPrefixPredicate(path); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/PathSuffixPredicate.java000066400000000000000000000046771420065311100310570ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import java.util.Collections; import java.util.Map; import java.util.Set; import io.undertow.server.HttpServerExchange; import io.undertow.UndertowLogger; /** * @author Stuart Douglas */ public class PathSuffixPredicate implements Predicate { private final String suffix; private static final boolean traceEnabled; static { traceEnabled = UndertowLogger.PREDICATE_LOGGER.isTraceEnabled(); } PathSuffixPredicate(final String suffix) { this.suffix = suffix; } @Override public boolean resolve(final HttpServerExchange value) { boolean matches = value.getRelativePath().endsWith(suffix); if (traceEnabled) { UndertowLogger.PREDICATE_LOGGER.tracef("Path suffix [%s] %s input [%s] for %s.", suffix, (matches ? "MATCHES" : "DOES NOT MATCH" ), value.getRelativePath(), value); } return matches; } public String toString() { return "path-suffix( '" + suffix + "' )"; } public static class Builder implements PredicateBuilder { @Override public String name() { return "path-suffix"; } @Override public Map> parameters() { return Collections.>singletonMap("path", String[].class); } @Override public Set requiredParameters() { return Collections.singleton("path"); } @Override public String defaultParameter() { return "path"; } @Override public Predicate build(final Map config) { String[] path = (String[]) config.get("path"); return Predicates.suffixes(path); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/PathTemplatePredicate.java000066400000000000000000000100711420065311100313470ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.TreeMap; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributes; import io.undertow.server.HttpServerExchange; import io.undertow.util.PathTemplate; import io.undertow.UndertowLogger; /** * @author Stuart Douglas */ public class PathTemplatePredicate implements Predicate { private final ExchangeAttribute attribute; private final String template; private final PathTemplate value; private static final boolean traceEnabled; static { traceEnabled = UndertowLogger.PREDICATE_LOGGER.isTraceEnabled(); } public PathTemplatePredicate(final String template, final ExchangeAttribute attribute) { this.attribute = attribute; this.template = template; this.value = PathTemplate.create(template); } @Override public boolean resolve(final HttpServerExchange exchange) { final Map params = new HashMap<>(); String path = attribute.readAttribute(exchange); if(path == null) { return false; } boolean result = this.value.matches(path, params); if (traceEnabled) { UndertowLogger.PREDICATE_LOGGER.tracef("Path template [%s] %s input [%s] for %s.", template, (result ? "MATCHES" : "DOES NOT MATCH" ), path, exchange); } if (result) { Map context = exchange.getAttachment(PREDICATE_CONTEXT); if(context == null) { exchange.putAttachment(PREDICATE_CONTEXT, context = new TreeMap<>()); } if (traceEnabled ) { params.entrySet().forEach( param -> UndertowLogger.PREDICATE_LOGGER.tracef("Storing template match [%s=%s] for %s.", param.getKey(), param.getValue(), exchange) ); } context.putAll(params); } return result; } public String toString() { if( attribute == ExchangeAttributes.relativePath() ) { return "path-template( '" + template + "' )"; } else { return "path-template( value='" + template + "', match='" + attribute.toString() + "' )"; } } public static class Builder implements PredicateBuilder { @Override public String name() { return "path-template"; } @Override public Map> parameters() { final Map> params = new HashMap<>(); params.put("value", String.class); params.put("match", ExchangeAttribute.class); return params; } @Override public Set requiredParameters() { final Set params = new HashSet<>(); params.add("value"); return params; } @Override public String defaultParameter() { return "value"; } @Override public Predicate build(final Map config) { ExchangeAttribute match = (ExchangeAttribute) config.get("match"); if (match == null) { match = ExchangeAttributes.relativePath(); } String value = (String) config.get("value"); return new PathTemplatePredicate(value, match); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/Predicate.java000066400000000000000000000033311420065311100270370ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import java.util.Map; import io.undertow.server.HttpServerExchange; import io.undertow.util.AttachmentKey; /** * A predicate. * * This is mainly used by handlers as a way to decide if a request should have certain * processing applied, based on the given conditions. * * @author Stuart Douglas */ public interface Predicate { /** * Attachment key that can be used to store additional predicate context that allows the predicates to store * additional information. For example a predicate that matches on a regular expression can place additional * information about match groups into the predicate context. * * Predicates must not rely on this attachment being present, it will only be present if the predicate is being * used in a situation where this information may be required by later handlers. * */ AttachmentKey> PREDICATE_CONTEXT = AttachmentKey.create(Map.class); boolean resolve(final HttpServerExchange value); } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/PredicateBuilder.java000066400000000000000000000033441420065311100303520ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import java.util.Map; import java.util.Set; /** * An interface that knows how to build a predicate from a textual representation. This is loaded * using a service loader to make it configurable. *

* This makes it easy to configure conditions based on a string representation * * @author Stuart Douglas */ public interface PredicateBuilder { /** * The string representation of the predicate name. * * @return The predicate name */ String name(); /** * Returns a map of parameters and their types. */ Map> parameters(); /** * * @return The required parameters */ Set requiredParameters(); /** * @return The default parameter name, or null if it does not have a default parameter */ String defaultParameter(); /** * Creates a predicate * * @param config The predicate config * @return The new predicate */ Predicate build(final Map config); } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/PredicateParser.java000066400000000000000000000041451420065311100302200ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import io.undertow.server.handlers.builder.PredicatedHandlersParser; /** * Parser that can build a predicate from a string representation. The underlying syntax is quite simple, and example is * shown below: *

* * path["/MyPath"] or (method[value="POST"] and not headersPresent[value={Content-Type, "Content-Encoding"}, ignoreTrailer=true] * *

* The following boolean operators are built in, listed in order or precedence: * - not * - and * - or *

* They work pretty much as you would expect them to. All other tokens are taken * to be predicate names. If the predicate does not require any parameters then the * brackets can be omitted, otherwise they are mandatory. *

* If a predicate is only being passed a single parameter then the parameter name can be omitted. * Strings can be enclosed in optional double or single quotations marks, and quotation marks can be escaped using * \". *

* Array types are represented via a comma separated list of values enclosed in curly braces. *

* TODO: should we use antlr (or whatever) here? I don't really want an extra dependency just for this... * * @author Stuart Douglas */ public class PredicateParser { public static final Predicate parse(String string, final ClassLoader classLoader) { return PredicatedHandlersParser.parsePredicate(string, classLoader); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/Predicates.java000066400000000000000000000205761420065311100272340ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributes; /** * Utility class used for creating predicates * * * @author Stuart Douglas */ public class Predicates { /** * Creates a procedure that returns true if the given ExchangeAttributes are equal. * @param attributes to be compared in the predictor. * @return A new EqualsPredicate. */ public static Predicate equals(final ExchangeAttribute[] attributes){ return new EqualsPredicate(attributes); } /** * Creates a predicate that returns true if an only if the given predicates all * return true. */ public static Predicate and(final Predicate... predicates) { return new AndPredicate(predicates); } /** * Creates a predicate that returns true if any of the given predicates * return true. */ public static Predicate or(final Predicate... predicates) { return new OrPredicate(predicates); } /** * Creates a predicate that returns true if the given predicate returns * false. */ public static Predicate not(final Predicate predicate) { return new NotPredicate(predicate); } /** * Creates a predicate that returns true if the given path matches exactly. */ public static Predicate path(final String path) { return new PathMatchPredicate(path); } /** * Creates a predicate that returns true if any of the given paths match exactly. */ public static Predicate paths(final String... paths) { final PathMatchPredicate[] predicates = new PathMatchPredicate[paths.length]; for (int i = 0; i < paths.length; ++i) { predicates[i] = new PathMatchPredicate(paths[i]); } return or(predicates); } /** * Creates a predicate that returns true if the request path ends with the provided suffix. */ public static Predicate suffix(final String path) { return new PathSuffixPredicate(path); } /** * Creates a predicate that returns true if the request path ends with any of the provided suffixes. */ public static Predicate suffixes(final String... paths) { if(paths.length == 1) { return suffix(paths[0]); } final PathSuffixPredicate[] predicates = new PathSuffixPredicate[paths.length]; for (int i = 0; i < paths.length; ++i) { predicates[i] = new PathSuffixPredicate(paths[i]); } return or(predicates); } /** * Creates a predicate that returns true if the given relative path starts with the provided prefix. */ public static Predicate prefix(final String path) { return new PathPrefixPredicate(path); } /** * Creates a predicate that returns true if the relative request path matches any of the provided prefixes. */ public static Predicate prefixes(final String... paths) { return new PathPrefixPredicate(paths); } /** * Predicate that returns true if the Content-Size of a request is above a * given value. * * Use {@link #requestLargerThan(long)} instead. */ @Deprecated public static Predicate maxContentSize(final long size) { return new MaxContentSizePredicate(size); } /** * Predicate that returns true if the Content-Size of a request is below a * given value. * * Use {@link #requestSmallerThan(long)} instead. */ @Deprecated public static Predicate minContentSize(final long size) { return new MinContentSizePredicate(size); } /** * Predicate that returns true if the Content-Size of a request is smaller than a * given size. */ public static Predicate requestSmallerThan(final long size) { return new RequestSmallerThanPredicate(size); } /** * Predicate that returns true if the Content-Size of a request is larger than a * given size. */ public static Predicate requestLargerThan(final long size) { return new RequestLargerThanPredicate(size); } /** * Prediction which always returns true */ public static Predicate truePredicate() { return TruePredicate.instance(); } /** * Predicate which always returns false. */ public static Predicate falsePredicate() { return FalsePredicate.instance(); } /** * Return a predicate that will return true if the given attribute is not null and not empty. * * @param attribute The attribute to check whether it exists or not. */ public static Predicate exists(final ExchangeAttribute attribute) { return new ExistsPredicate(attribute); } /** * Returns true if the given attribute is present and contains one of the provided value. * @param attribute The exchange attribute. * @param values The values to check for. */ public static Predicate contains(final ExchangeAttribute attribute, final String ... values) { return new ContainsPredicate(attribute, values); } /** * Creates a predicate that matches the given attribute against a regex. A full match is not required * @param attribute The exchange attribute to check against. * @param pattern The pattern to look for. */ public static Predicate regex(final ExchangeAttribute attribute, final String pattern) { return new RegularExpressionPredicate(pattern, attribute); } /** * Creates a predicate that matches the given attribute against a regex. * @param requireFullMatch If a full match is required in order to return true. * @param attribute The attribute to check against. * @param pattern The pattern to look for. */ public static Predicate regex(final ExchangeAttribute attribute, final String pattern, boolean requireFullMatch) { return new RegularExpressionPredicate(pattern, attribute, requireFullMatch); } /** * Creates a predicate that matches the given attribute against a regex. * @param requireFullMatch If a full match is required in order to return true. * @param attribute The attribute to check against. * @param pattern The pattern to look for. */ public static Predicate regex(final String attribute, final String pattern, final ClassLoader classLoader, final boolean requireFullMatch) { return new RegularExpressionPredicate(pattern, ExchangeAttributes.parser(classLoader).parse(attribute), requireFullMatch); } /** * A predicate that returns true if authentication is required * * @return A predicate that returns true if authentication is required */ public static Predicate authRequired() { return AuthenticationRequiredPredicate.INSTANCE; } /** * parses the predicate string, and returns the result, using the TCCL to load predicate definitions * @param predicate The prediate string * @return The predicate */ public static Predicate parse(final String predicate) { return PredicateParser.parse(predicate, Thread.currentThread().getContextClassLoader()); } /** * parses the predicate string, and returns the result * @param predicate The prediate string * @param classLoader The class loader to load the predicates from * @return The predicate */ public static Predicate parse(final String predicate, ClassLoader classLoader) { return PredicateParser.parse(predicate, classLoader); } /** * * @return A predicate that returns true if the request is secure */ public static Predicate secure() { return SecurePredicate.INSTANCE; } private Predicates() { } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/PredicatesHandler.java000066400000000000000000000274751420065311100305370ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import io.undertow.UndertowLogger; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.server.handlers.builder.PredicatedHandler; import io.undertow.util.AttachmentKey; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; /** * Handler that can deal with a large number of predicates. chaining together a large number of {@link io.undertow.predicate.PredicatesHandler.Holder} * instances will make the stack grow to large, so this class is used that can deal with a large number of predicates. * * @author Stuart Douglas */ public class PredicatesHandler implements HttpHandler { /** * static done marker. If this is attached to the exchange it will drop out immediately. */ public static final AttachmentKey DONE = AttachmentKey.create(Boolean.class); public static final AttachmentKey RESTART = AttachmentKey.create(Boolean.class); private static final boolean traceEnabled; static { traceEnabled = UndertowLogger.PREDICATE_LOGGER.isTraceEnabled(); } private volatile Holder[] handlers = new Holder[0]; private volatile HttpHandler next; private final boolean outerHandler; //non-static, so multiple handlers can co-exist private final AttachmentKey CURRENT_POSITION = AttachmentKey.create(Integer.class); public PredicatesHandler(HttpHandler next) { this.next = next; this.outerHandler = true; } public PredicatesHandler(HttpHandler next, boolean outerHandler) { this.next = next; this.outerHandler = outerHandler; } @Override public String toString() { return "PredicatesHandler with " + handlers.length + " predicates"; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { final int length = handlers.length; Integer current = exchange.getAttachment(CURRENT_POSITION); do { int pos; if (current == null) { if (outerHandler) { exchange.removeAttachment(RESTART); exchange.removeAttachment(DONE); if (exchange.getAttachment(Predicate.PREDICATE_CONTEXT) == null) { exchange.putAttachment(Predicate.PREDICATE_CONTEXT, new TreeMap()); } } pos = 0; } else { //if it has been marked as done if (exchange.getAttachment(DONE) != null) { if (traceEnabled && outerHandler) { UndertowLogger.PREDICATE_LOGGER.tracef("Predicate chain marked done. Next handler is [%s] for %s.", next.toString(), exchange); } exchange.removeAttachment(CURRENT_POSITION); next.handleRequest(exchange); return; } pos = current; } for (; pos < length; ++pos) { final Holder handler = handlers[pos]; if (handler.predicate.resolve(exchange)) { if(traceEnabled) { if( handler.predicate.toString().equals("true") ) { UndertowLogger.PREDICATE_LOGGER.tracef("Executing handler [%s] for %s.", handler.handler.toString(), exchange); } else { UndertowLogger.PREDICATE_LOGGER.tracef("Predicate [%s] resolved to true. Next handler is [%s] for %s.", handler.predicate.toString(), handler.handler.toString(), exchange); } } exchange.putAttachment(CURRENT_POSITION, pos + 1); handler.handler.handleRequest(exchange); if(shouldRestart(exchange, current)) { if(traceEnabled) { UndertowLogger.PREDICATE_LOGGER.tracef("Restarting predicate resolution for %s.", exchange); } break; } else { return; } } else if(handler.elseBranch != null) { if(traceEnabled) { UndertowLogger.PREDICATE_LOGGER.tracef("Predicate [%s] resolved to false. Else branch is [%s] for %s.", handler.predicate.toString(), handler.elseBranch.toString(), exchange); } exchange.putAttachment(CURRENT_POSITION, pos + 1); handler.elseBranch.handleRequest(exchange); if(shouldRestart(exchange, current)) { if(traceEnabled) { UndertowLogger.PREDICATE_LOGGER.tracef("Restarting predicate resolution for %s.", exchange); } break; } else { return; } } else if(traceEnabled) { UndertowLogger.PREDICATE_LOGGER.tracef("Predicate [%s] resolved to false for %s.", handler.predicate.toString(), exchange); } } } while (shouldRestart(exchange, current)); next.handleRequest(exchange); } private boolean shouldRestart(HttpServerExchange exchange, Integer current) { return exchange.getAttachment(RESTART) != null && outerHandler && current == null; } /** * Adds a new predicated handler. *

* * @param predicate * @param handlerWrapper */ public PredicatesHandler addPredicatedHandler(final Predicate predicate, final HandlerWrapper handlerWrapper, final HandlerWrapper elseBranch) { Holder[] old = handlers; Holder[] handlers = new Holder[old.length + 1]; System.arraycopy(old, 0, handlers, 0, old.length); HttpHandler elseHandler = elseBranch != null ? elseBranch.wrap(this) : null; handlers[old.length] = new Holder(predicate, handlerWrapper.wrap(this), elseHandler); this.handlers = handlers; return this; } /** * Adds a new predicated handler. *

* * @param predicate * @param handlerWrapper */ public PredicatesHandler addPredicatedHandler(final Predicate predicate, final HandlerWrapper handlerWrapper) { this.addPredicatedHandler(predicate, handlerWrapper, null); return this; } public PredicatesHandler addPredicatedHandler(final PredicatedHandler handler) { return addPredicatedHandler(handler.getPredicate(), handler.getHandler(), handler.getElseHandler()); } public void setNext(HttpHandler next) { this.next = next; } public HttpHandler getNext() { return next; } private static final class Holder { final Predicate predicate; final HttpHandler handler; final HttpHandler elseBranch; private Holder(Predicate predicate, HttpHandler handler, HttpHandler elseBranch) { this.predicate = predicate; this.handler = handler; this.elseBranch = elseBranch; } } public static final class DoneHandlerBuilder implements HandlerBuilder { @Override public String name() { return "done"; } @Override public Map> parameters() { return Collections.emptyMap(); } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { return new HandlerWrapper() { @Override public HttpHandler wrap(final HttpHandler handler) { return new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.putAttachment(DONE, true); handler.handleRequest(exchange); } @Override public String toString() { return "done"; } }; } }; } } public static final class RestartHandlerBuilder implements HandlerBuilder { private static final AttachmentKey RESTART_COUNT = AttachmentKey.create(Integer.class); private static final int MAX_RESTARTS = Integer.getInteger("io.undertow.max_restarts", 1000); @Override public String name() { return "restart"; } @Override public Map> parameters() { return Collections.emptyMap(); } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { return new HandlerWrapper() { @Override public HttpHandler wrap(final HttpHandler handler) { return new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { Integer restarts = exchange.getAttachment(RESTART_COUNT); if(restarts == null) { restarts = 1; } else { restarts++; } exchange.putAttachment(RESTART_COUNT, restarts); if(restarts > MAX_RESTARTS) { throw UndertowLogger.ROOT_LOGGER.maxRestartsExceeded(MAX_RESTARTS); } exchange.putAttachment(RESTART, true); } @Override public String toString() { return "restart"; } }; } }; } } public static class Wrapper implements HandlerWrapper { private final List handlers; private final boolean outerHandler; public Wrapper(List handlers, boolean outerHandler) { this.handlers = handlers; this.outerHandler = outerHandler; } @Override public HttpHandler wrap(HttpHandler handler) { PredicatesHandler h = new PredicatesHandler(handler, outerHandler); for(PredicatedHandler pred : handlers) { h.addPredicatedHandler(pred.getPredicate(), pred.getHandler()); } return h; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/RegularExpressionPredicate.java000066400000000000000000000125671420065311100324540ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributes; import io.undertow.server.HttpServerExchange; import io.undertow.UndertowLogger; /** * A predicate that does a regex match against an exchange. *

*

* By default this match is done against the relative URI, however it is possible to set it to match against other * exchange attributes. * * @author Stuart Douglas */ public class RegularExpressionPredicate implements Predicate { private final Pattern pattern; private final ExchangeAttribute matchAttribute; private final boolean requireFullMatch; private static final boolean traceEnabled; static { traceEnabled = UndertowLogger.PREDICATE_LOGGER.isTraceEnabled(); } public RegularExpressionPredicate(final String regex, final ExchangeAttribute matchAttribute, final boolean requireFullMatch, boolean caseSensitive) { this.requireFullMatch = requireFullMatch; pattern = Pattern.compile(regex, caseSensitive ? 0 : Pattern.CASE_INSENSITIVE); this.matchAttribute = matchAttribute; } public RegularExpressionPredicate(final String regex, final ExchangeAttribute matchAttribute, final boolean requireFullMatch) { this(regex, matchAttribute, requireFullMatch, true); } public RegularExpressionPredicate(final String regex, final ExchangeAttribute matchAttribute) { this(regex, matchAttribute, false); } @Override public boolean resolve(final HttpServerExchange value) { String input = matchAttribute.readAttribute(value); if(input == null) { return false; } Matcher matcher = pattern.matcher(input); final boolean matches; if (requireFullMatch) { matches = matcher.matches(); } else { matches = matcher.find(); } if (traceEnabled) { UndertowLogger.PREDICATE_LOGGER.tracef("Regex pattern [%s] %s input [%s] for %s.", pattern.toString(), (matches ? "MATCHES" : "DOES NOT MATCH" ), input, value); } if (matches) { Map context = value.getAttachment(PREDICATE_CONTEXT); if(context == null) { value.putAttachment(PREDICATE_CONTEXT, context = new TreeMap<>()); } int count = matcher.groupCount(); for (int i = 0; i <= count; ++i) { if (traceEnabled) { UndertowLogger.PREDICATE_LOGGER.tracef("Storing regex match group [%s] as [%s] for %s.", i, matcher.group(i), value); } context.put(Integer.toString(i), matcher.group(i)); } } return matches; } @Override public String toString() { return "regex( pattern='" + pattern.toString() + "', value='" + matchAttribute.toString() + "', full-match='" + Boolean.valueOf( this.requireFullMatch ) + "', case-sensitive='" + Boolean.valueOf( ( pattern.flags() & Pattern.CASE_INSENSITIVE ) == Pattern.CASE_INSENSITIVE ) + "' )"; } public static class Builder implements PredicateBuilder { @Override public String name() { return "regex"; } @Override public Map> parameters() { final Map> params = new HashMap<>(); params.put("pattern", String.class); params.put("value", ExchangeAttribute.class); params.put("full-match", Boolean.class); params.put("case-sensitive", Boolean.class); return params; } @Override public Set requiredParameters() { final Set params = new HashSet<>(); params.add("pattern"); return params; } @Override public String defaultParameter() { return "pattern"; } @Override public Predicate build(final Map config) { ExchangeAttribute value = (ExchangeAttribute) config.get("value"); if(value == null) { value = ExchangeAttributes.relativePath(); } Boolean fullMatch = (Boolean) config.get("full-match"); Boolean caseSensitive = (Boolean) config.get("case-sensitive"); String pattern = (String) config.get("pattern"); return new RegularExpressionPredicate(pattern, value, fullMatch == null ? false : fullMatch, caseSensitive == null ? true : caseSensitive); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/RequestLargerThanPredicate.java000066400000000000000000000044661420065311100323720ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import java.util.Collections; import java.util.Map; import java.util.Set; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; /** * Predicate that returns true if the Content-Size of a request is larger than a * given size. * * @author Brad Wood */ public class RequestLargerThanPredicate implements Predicate { private final long size; RequestLargerThanPredicate(final long size) { this.size = size; } @Override public boolean resolve(final HttpServerExchange exchange) { final String length = exchange.getResponseHeaders().getFirst(Headers.CONTENT_LENGTH); if (length == null) { return false; } return Long.parseLong(length) > size; } public String toString() { return "request-larger-than( '" + size + "' )"; } public static class Builder implements PredicateBuilder { @Override public String name() { return "request-larger-than"; } @Override public Map> parameters() { return Collections.>singletonMap("size", Long.class); } @Override public Set requiredParameters() { return Collections.singleton("size"); } @Override public String defaultParameter() { return "size"; } @Override public Predicate build(final Map config) { Long size = (Long) config.get("size"); return new RequestLargerThanPredicate(size); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/RequestSmallerThanPredicate.java000066400000000000000000000044741420065311100325540ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import java.util.Collections; import java.util.Map; import java.util.Set; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; /** * Predicate that returns true if the Content-Size of a request is smaller than a * given size. * * @author Brad Wood */ public class RequestSmallerThanPredicate implements Predicate { private final long size; RequestSmallerThanPredicate(final long size) { this.size = size; } @Override public boolean resolve(final HttpServerExchange exchange) { final String length = exchange.getResponseHeaders().getFirst(Headers.CONTENT_LENGTH); if (length == null) { return false; } return Long.parseLong(length) < size; } public String toString() { return "request-smaller-than( '" + size + "' )"; } public static class Builder implements PredicateBuilder { @Override public String name() { return "request-smaller-than"; } @Override public Map> parameters() { return Collections.>singletonMap("size", Long.class); } @Override public Set requiredParameters() { return Collections.singleton("size"); } @Override public String defaultParameter() { return "size"; } @Override public Predicate build(final Map config) { Long size = (Long) config.get("size"); return new RequestSmallerThanPredicate(size); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/SecurePredicate.java000066400000000000000000000034711420065311100302130ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import io.undertow.server.HttpServerExchange; import java.util.Collections; import java.util.Map; import java.util.Set; /** * @author Stuart Douglas */ public class SecurePredicate implements Predicate { public static final SecurePredicate INSTANCE = new SecurePredicate(); @Override public boolean resolve(HttpServerExchange value) { return value.getRequestScheme().equals("https"); } public String toString() { return "secure()"; } public static class Builder implements PredicateBuilder { @Override public String name() { return "secure"; } @Override public Map> parameters() { return Collections.emptyMap(); } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return null; } @Override public Predicate build(Map config) { return INSTANCE; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/predicate/TruePredicate.java000066400000000000000000000022641420065311100277030ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import io.undertow.server.HttpServerExchange; /** * @author Stuart Douglas */ public class TruePredicate implements Predicate { public static final TruePredicate INSTANCE = new TruePredicate(); public static TruePredicate instance() { return INSTANCE; } @Override public boolean resolve(final HttpServerExchange value) { return true; } @Override public String toString() { return "true"; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/000077500000000000000000000000001420065311100243605ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ajp/000077500000000000000000000000001420065311100251325ustar00rootroot00000000000000AbstractAjpClientStreamSinkChannel.java000066400000000000000000000023161420065311100345470ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ajp/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.ajp; import io.undertow.server.protocol.framed.AbstractFramedStreamSinkChannel; /** * @author Stuart Douglas */ public class AbstractAjpClientStreamSinkChannel extends AbstractFramedStreamSinkChannel { protected AbstractAjpClientStreamSinkChannel(AjpClientChannel channel) { super(channel); } @Override protected boolean isLastFrame() { return false; } } AbstractAjpClientStreamSourceChannel.java000066400000000000000000000024111420065311100350770ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ajp/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.ajp; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.protocol.framed.AbstractFramedStreamSourceChannel; /** * @author Stuart Douglas */ public class AbstractAjpClientStreamSourceChannel extends AbstractFramedStreamSourceChannel { public AbstractAjpClientStreamSourceChannel(AjpClientChannel framedChannel, PooledByteBuffer data, long frameDataRemaining) { super(framedChannel, data, frameDataRemaining); } } AjpClientCPingStreamSinkChannel.java000066400000000000000000000012311420065311100337770ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ajppackage io.undertow.protocols.ajp; import io.undertow.server.protocol.framed.SendFrameHeader; import io.undertow.util.ImmediatePooledByteBuffer; import java.nio.ByteBuffer; /** * @author Stuart Douglas */ public class AjpClientCPingStreamSinkChannel extends AbstractAjpClientStreamSinkChannel { private static final byte[] CPING = {0x12, 0x34, 0, 1, 10}; //CPONG response data protected AjpClientCPingStreamSinkChannel(AjpClientChannel channel) { super(channel); } @Override protected final SendFrameHeader createFrameHeader() { return new SendFrameHeader(new ImmediatePooledByteBuffer(ByteBuffer.wrap(CPING))); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ajp/AjpClientChannel.java000066400000000000000000000304231420065311100311410ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.ajp; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.client.ClientConnection; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.protocol.framed.AbstractFramedChannel; import io.undertow.server.protocol.framed.AbstractFramedStreamSourceChannel; import io.undertow.server.protocol.framed.FrameHeaderData; import io.undertow.util.Attachable; import io.undertow.util.HeaderMap; import io.undertow.util.HttpString; import org.xnio.ChannelExceptionHandler; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.OptionMap; import org.xnio.StreamConnection; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import static io.undertow.protocols.ajp.AjpConstants.FRAME_TYPE_CPONG; import static io.undertow.protocols.ajp.AjpConstants.FRAME_TYPE_END_RESPONSE; import static io.undertow.protocols.ajp.AjpConstants.FRAME_TYPE_REQUEST_BODY_CHUNK; import static io.undertow.protocols.ajp.AjpConstants.FRAME_TYPE_SEND_BODY_CHUNK; import static io.undertow.protocols.ajp.AjpConstants.FRAME_TYPE_SEND_HEADERS; /** * AJP client side channel. * * @author Stuart Douglas */ public class AjpClientChannel extends AbstractFramedChannel { private final AjpResponseParser ajpParser; private AjpClientResponseStreamSourceChannel source; private AjpClientRequestClientStreamSinkChannel sink; private final List pingListeners = new ArrayList<>(); boolean sinkDone = true; boolean sourceDone = true; private boolean lastFrameSent; private boolean lastFrameReceived; /** * Create a new {@link io.undertow.server.protocol.framed.AbstractFramedChannel} * 8 * * @param connectedStreamChannel The {@link org.xnio.channels.ConnectedStreamChannel} over which the WebSocket Frames should get send and received. * Be aware that it already must be "upgraded". * @param bufferPool The {@link org.xnio.Pool} which will be used to acquire {@link java.nio.ByteBuffer}'s from. */ public AjpClientChannel(StreamConnection connectedStreamChannel, ByteBufferPool bufferPool, OptionMap settings) { super(connectedStreamChannel, bufferPool, AjpClientFramePriority.INSTANCE, null, settings); ajpParser = new AjpResponseParser(); } @Override protected AbstractAjpClientStreamSourceChannel createChannel(FrameHeaderData frameHeaderData, PooledByteBuffer frameData) throws IOException { if (frameHeaderData instanceof SendHeadersResponse) { SendHeadersResponse h = (SendHeadersResponse) frameHeaderData; AjpClientResponseStreamSourceChannel sourceChannel = new AjpClientResponseStreamSourceChannel(this, h.headers, h.statusCode, h.reasonPhrase, frameData, (int) frameHeaderData.getFrameLength()); this.source = sourceChannel; return sourceChannel; } else if (frameHeaderData instanceof RequestBodyChunk) { RequestBodyChunk r = (RequestBodyChunk) frameHeaderData; this.sink.chunkRequested(r.getLength()); frameData.close(); return null; } else if (frameHeaderData instanceof CpongResponse) { synchronized (pingListeners) { for(ClientConnection.PingListener i : pingListeners) { try { i.acknowledged(); } catch (Throwable t) { UndertowLogger.ROOT_LOGGER.debugf("Exception notifying ping listener", t); } } pingListeners.clear(); } return null; } else { frameData.close(); throw new RuntimeException("TODO: unknown frame"); } } @Override protected FrameHeaderData parseFrame(ByteBuffer data) throws IOException { ajpParser.parse(data); if (ajpParser.isComplete()) { try { AjpResponseParser parser = ajpParser; if (parser.prefix == FRAME_TYPE_SEND_HEADERS) { return new SendHeadersResponse(parser.statusCode, parser.reasonPhrase, parser.headers); } else if (parser.prefix == FRAME_TYPE_REQUEST_BODY_CHUNK) { return new RequestBodyChunk(parser.readBodyChunkSize); } else if (parser.prefix == FRAME_TYPE_SEND_BODY_CHUNK) { return new SendBodyChunk(parser.currentIntegerPart + 1); //+1 for the null terminator } else if (parser.prefix == FRAME_TYPE_END_RESPONSE) { boolean persistent = parser.currentIntegerPart != 0; if (!persistent) { lastFrameReceived = true; lastFrameSent = true; } return new EndResponse(); } else if (parser.prefix == FRAME_TYPE_CPONG) { return new CpongResponse(); } else { UndertowLogger.ROOT_LOGGER.debug("UNKOWN FRAME"); } } finally { ajpParser.reset(); } } return null; } public AjpClientRequestClientStreamSinkChannel sendRequest(final HttpString method, final String path, final HttpString protocol, final HeaderMap headers, final Attachable attachable, ChannelListener finishListener) { if (!sinkDone || !sourceDone) { throw UndertowMessages.MESSAGES.ajpRequestAlreadyInProgress(); } sinkDone = false; sourceDone = false; AjpClientRequestClientStreamSinkChannel ajpClientRequestStreamSinkChannel = new AjpClientRequestClientStreamSinkChannel(this, finishListener, headers, path, method, protocol, attachable); sink = ajpClientRequestStreamSinkChannel; source = null; return ajpClientRequestStreamSinkChannel; } @Override protected boolean isLastFrameReceived() { return lastFrameReceived; } @Override protected boolean isLastFrameSent() { return lastFrameSent; } protected void lastDataRead() { if(!lastFrameSent) { markReadsBroken(new ClosedChannelException()); markWritesBroken(new ClosedChannelException()); } lastFrameReceived = true; lastFrameSent = true; IoUtils.safeClose(this); } @Override protected void handleBrokenSourceChannel(Throwable e) { } @Override protected void handleBrokenSinkChannel(Throwable e) { } @Override protected void closeSubChannels() { IoUtils.safeClose(source, sink); } @Override protected Collection> getReceivers() { if(source == null) { return Collections.emptyList(); } return Collections.>singleton(source); } protected OptionMap getSettings() { return super.getSettings(); } void sinkDone() { sinkDone = true; if (sourceDone) { sink = null; source = null; } } void sourceDone() { sourceDone = true; if (sinkDone) { sink = null; source = null; } else { sink.startDiscard(); } } @Override public boolean isOpen() { return super.isOpen() && !lastFrameSent && !lastFrameReceived; } @Override protected synchronized void recalculateHeldFrames() throws IOException { super.recalculateHeldFrames(); } public void sendPing(ClientConnection.PingListener pingListener, long timeout, TimeUnit timeUnit) { AjpClientCPingStreamSinkChannel pingChannel = new AjpClientCPingStreamSinkChannel(this); try { pingChannel.shutdownWrites(); if (!pingChannel.flush()) { pingChannel.getWriteSetter().set(ChannelListeners.flushingChannelListener(null, new ChannelExceptionHandler() { @Override public void handleException(AbstractAjpClientStreamSinkChannel channel, IOException exception) { pingListener.failed(exception); synchronized (pingListeners) { pingListeners.remove(pingListener); } } })); pingChannel.resumeWrites(); } } catch (IOException e) { pingListener.failed(e); return; } synchronized (pingListeners) { pingListeners.add(pingListener); } getIoThread().executeAfter(() -> { synchronized (pingListeners) { if(pingListeners.contains(pingListener)) { pingListeners.remove(pingListener); pingListener.failed(UndertowMessages.MESSAGES.pingTimeout()); } } }, timeout, timeUnit); } class SendHeadersResponse implements FrameHeaderData { private final int statusCode; private final String reasonPhrase; private final HeaderMap headers; SendHeadersResponse(int statusCode, String reasonPhrase, HeaderMap headers) { this.statusCode = statusCode; this.reasonPhrase = reasonPhrase; this.headers = headers; } @Override public long getFrameLength() { //zero return 0; } @Override public AbstractFramedStreamSourceChannel getExistingChannel() { return null; } } class RequestBodyChunk implements FrameHeaderData { private final int length; RequestBodyChunk(int length) { this.length = length; } public int getLength() { return length; } @Override public long getFrameLength() { return 0; } @Override public AbstractFramedStreamSourceChannel getExistingChannel() { return null; } } class SendBodyChunk implements FrameHeaderData { private final int length; SendBodyChunk(int length) { this.length = length; } @Override public long getFrameLength() { return length; } @Override public AbstractFramedStreamSourceChannel getExistingChannel() { return source; } } class EndResponse implements FrameHeaderData { @Override public long getFrameLength() { return 0; } @Override public AbstractFramedStreamSourceChannel getExistingChannel() { return source; } } class CpongResponse implements FrameHeaderData { @Override public long getFrameLength() { return 0; } @Override public AbstractFramedStreamSourceChannel getExistingChannel() { return null; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ajp/AjpClientFramePriority.java000066400000000000000000000055671420065311100324000ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.ajp; import java.util.Deque; import java.util.Iterator; import java.util.List; import io.undertow.server.protocol.framed.FramePriority; import io.undertow.server.protocol.framed.SendFrameHeader; /** * AJP frame priority * @author Stuart Douglas */ class AjpClientFramePriority implements FramePriority{ public static AjpClientFramePriority INSTANCE = new AjpClientFramePriority(); @Override public boolean insertFrame(AbstractAjpClientStreamSinkChannel newFrame, List pendingFrames) { if(newFrame instanceof AjpClientRequestClientStreamSinkChannel) { SendFrameHeader header = ((AjpClientRequestClientStreamSinkChannel) newFrame).generateSendFrameHeader(); if(header.getByteBuffer() == null) { //we clear the header, as we want to generate a new real header when the flow control window is updated ((AjpClientRequestClientStreamSinkChannel) newFrame).clearHeader(); return false; } } pendingFrames.add(newFrame); return true; } @Override public void frameAdded(AbstractAjpClientStreamSinkChannel addedFrame, List pendingFrames, Deque holdFrames) { Iterator it = holdFrames.iterator(); while (it.hasNext()){ AbstractAjpClientStreamSinkChannel pending = it.next(); if(pending instanceof AjpClientRequestClientStreamSinkChannel) { SendFrameHeader header = ((AjpClientRequestClientStreamSinkChannel) pending).generateSendFrameHeader(); if(header.getByteBuffer() != null) { pendingFrames.add(pending); it.remove(); } else { //we clear the header, as we want to generate a new real header when the flow control window is updated ((AjpClientRequestClientStreamSinkChannel) pending).clearHeader(); } } } } } AjpClientRequestClientStreamSinkChannel.java000066400000000000000000000336461420065311100356050ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ajp/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.ajp; import static io.undertow.protocols.ajp.AjpConstants.ATTR_AUTH_TYPE; import static io.undertow.protocols.ajp.AjpConstants.ATTR_QUERY_STRING; import static io.undertow.protocols.ajp.AjpConstants.ATTR_REMOTE_USER; import static io.undertow.protocols.ajp.AjpConstants.ATTR_ROUTE; import static io.undertow.protocols.ajp.AjpConstants.ATTR_SECRET; import static io.undertow.protocols.ajp.AjpConstants.ATTR_SSL_CERT; import static io.undertow.protocols.ajp.AjpConstants.ATTR_SSL_CIPHER; import static io.undertow.protocols.ajp.AjpConstants.ATTR_SSL_KEY_SIZE; import static io.undertow.protocols.ajp.AjpConstants.ATTR_SSL_SESSION; import static io.undertow.protocols.ajp.AjpConstants.ATTR_STORED_METHOD; import static io.undertow.protocols.ajp.AjpUtils.notNull; import static io.undertow.protocols.ajp.AjpUtils.putString; import java.io.IOException; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import org.xnio.ChannelListener; import io.undertow.UndertowMessages; import io.undertow.UndertowOptions; import io.undertow.client.ProxiedRequestAttachments; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.protocol.framed.SendFrameHeader; import io.undertow.util.Attachable; import io.undertow.util.HeaderMap; import io.undertow.util.Headers; import io.undertow.util.HexConverter; import io.undertow.util.HttpString; import io.undertow.util.ImmediatePooledByteBuffer; /** * AJP stream sink channel that corresponds to a request send from the load balancer to the backend * * @author Stuart Douglas */ public class AjpClientRequestClientStreamSinkChannel extends AbstractAjpClientStreamSinkChannel { private final ChannelListener finishListener; public static final int DEFAULT_MAX_DATA_SIZE = 8192; private final HeaderMap headers; private final String path; private final HttpString method; private final HttpString protocol; private final Attachable attachable; private boolean firstFrameWritten = false; private long dataSize; private int requestedChunkSize = -1; private SendFrameHeader header; /** * When we are the client and the */ private boolean discardMode = false; AjpClientRequestClientStreamSinkChannel(AjpClientChannel channel, ChannelListener finishListener, HeaderMap headers, String path, HttpString method, HttpString protocol, Attachable attachable) { super(channel); this.finishListener = finishListener; this.headers = headers; this.path = path; this.method = method; this.protocol = protocol; this.attachable = attachable; } private SendFrameHeader createFrameHeaderImpl() { if (discardMode) { getBuffer().clear(); getBuffer().flip(); return new SendFrameHeader(new ImmediatePooledByteBuffer(ByteBuffer.wrap(new byte[0]))); } PooledByteBuffer pooledHeaderBuffer = getChannel().getBufferPool().allocate(); try { final ByteBuffer buffer = pooledHeaderBuffer.getBuffer(); ByteBuffer dataBuffer = getBuffer(); int dataInBuffer = dataBuffer.remaining(); if (!firstFrameWritten && requestedChunkSize == 0) { //we are waiting on a read body chunk return new SendFrameHeader(dataInBuffer, null); } int maxData = getChannel().getSettings().get(UndertowOptions.MAX_AJP_PACKET_SIZE, DEFAULT_MAX_DATA_SIZE) - 6; if (!firstFrameWritten) { String contentLength = headers.getFirst(Headers.CONTENT_LENGTH); if (contentLength != null) { dataSize = Long.parseLong(contentLength); requestedChunkSize = maxData; if (dataInBuffer > dataSize) { throw UndertowMessages.MESSAGES.fixedLengthOverflow(); } } else if (isWritesShutdown() && !headers.contains(Headers.TRANSFER_ENCODING)) { //writes are shut down, go to fixed length headers.put(Headers.CONTENT_LENGTH, dataInBuffer); dataSize = dataInBuffer; requestedChunkSize = maxData; } else { headers.put(Headers.TRANSFER_ENCODING, Headers.CHUNKED.toString()); dataSize = -1; requestedChunkSize = 0; } firstFrameWritten = true; final String path; final String queryString; int qsIndex = this.path.indexOf('?'); if (qsIndex == -1) { path = this.path; queryString = null; } else { path = this.path.substring(0, qsIndex); queryString = this.path.substring(qsIndex + 1); } buffer.put((byte) 0x12); buffer.put((byte) 0x34); buffer.put((byte) 0); //we fill the size in later buffer.put((byte) 0); buffer.put((byte) 2); boolean storeMethod = false; Integer methodNp = AjpConstants.HTTP_METHODS_MAP.get(method); if (methodNp == null) { methodNp = 0xFF; storeMethod = true; } buffer.put((byte) (int) methodNp); AjpUtils.putHttpString(buffer, protocol); putString(buffer, path); putString(buffer, notNull(attachable.getAttachment(ProxiedRequestAttachments.REMOTE_ADDRESS))); putString(buffer, notNull(attachable.getAttachment(ProxiedRequestAttachments.REMOTE_HOST))); putString(buffer, notNull(attachable.getAttachment(ProxiedRequestAttachments.SERVER_NAME))); AjpUtils.putInt(buffer, notNull(attachable.getAttachment(ProxiedRequestAttachments.SERVER_PORT))); buffer.put((byte) (notNull(attachable.getAttachment(ProxiedRequestAttachments.IS_SSL)) ? 1 : 0)); int headers = 0; //we need to count the headers final HeaderMap responseHeaders = this.headers; for (HttpString name : responseHeaders.getHeaderNames()) { headers += responseHeaders.get(name).size(); } AjpUtils.putInt(buffer, headers); for (final HttpString header : responseHeaders.getHeaderNames()) { for (String headerValue : responseHeaders.get(header)) { Integer headerCode = AjpConstants.HEADER_MAP.get(header); if (headerCode != null) { AjpUtils.putInt(buffer, headerCode); } else { AjpUtils.putHttpString(buffer, header); } putString(buffer, headerValue); } } if (queryString != null) { buffer.put((byte) ATTR_QUERY_STRING); //query_string putString(buffer, queryString); } String remoteUser = attachable.getAttachment(ProxiedRequestAttachments.REMOTE_USER); if (remoteUser != null) { buffer.put((byte) ATTR_REMOTE_USER); putString(buffer, remoteUser); } String authType = attachable.getAttachment(ProxiedRequestAttachments.AUTH_TYPE); if (authType != null) { buffer.put((byte) ATTR_AUTH_TYPE); putString(buffer, authType); } String route = attachable.getAttachment(ProxiedRequestAttachments.ROUTE); if (route != null) { buffer.put((byte) ATTR_ROUTE); putString(buffer, route); } String sslCert = attachable.getAttachment(ProxiedRequestAttachments.SSL_CERT); if (sslCert != null) { buffer.put((byte) ATTR_SSL_CERT); putString(buffer, sslCert); } String sslCypher = attachable.getAttachment(ProxiedRequestAttachments.SSL_CYPHER); if (sslCypher != null) { buffer.put((byte) ATTR_SSL_CIPHER); putString(buffer, sslCypher); } byte[] sslSession = attachable.getAttachment(ProxiedRequestAttachments.SSL_SESSION_ID); if (sslSession != null) { buffer.put((byte) ATTR_SSL_SESSION); putString(buffer, HexConverter.convertToHexString(sslSession)); } Integer sslKeySize = attachable.getAttachment(ProxiedRequestAttachments.SSL_KEY_SIZE); if (sslKeySize != null) { buffer.put((byte) ATTR_SSL_KEY_SIZE); putString(buffer, sslKeySize.toString()); } String secret = attachable.getAttachment(ProxiedRequestAttachments.SECRET); if (secret != null) { buffer.put((byte) ATTR_SECRET); putString(buffer, secret); } if (storeMethod) { buffer.put((byte) ATTR_STORED_METHOD); putString(buffer, method.toString()); } buffer.put((byte) 0xFF); int dataLength = buffer.position() - 4; buffer.put(2, (byte) ((dataLength >> 8) & 0xFF)); buffer.put(3, (byte) (dataLength & 0xFF)); } if (dataSize == 0) { //no data, just write out this frame and we are done buffer.flip(); return new SendFrameHeader(pooledHeaderBuffer); } else if (requestedChunkSize > 0) { if (isWritesShutdown() && dataInBuffer == 0) { buffer.put((byte) 0x12); buffer.put((byte) 0x34); buffer.put((byte) 0x00); buffer.put((byte) 0x02); buffer.put((byte) 0x00); buffer.put((byte) 0x00); buffer.flip(); return new SendFrameHeader(pooledHeaderBuffer); } int remaining = dataInBuffer; remaining = Math.min(remaining, maxData); remaining = Math.min(remaining, requestedChunkSize); int bodySize = remaining + 2; buffer.put((byte) 0x12); buffer.put((byte) 0x34); buffer.put((byte) ((bodySize >> 8) & 0xFF)); buffer.put((byte) (bodySize & 0xFF)); buffer.put((byte) ((remaining >> 8) & 0xFF)); buffer.put((byte) (remaining & 0xFF)); requestedChunkSize = 0; if (remaining < dataInBuffer) { dataBuffer.limit(getBuffer().position() + remaining); buffer.flip(); return new SendFrameHeader(dataInBuffer - remaining, pooledHeaderBuffer, dataSize < 0); } else { buffer.flip(); return new SendFrameHeader(0, pooledHeaderBuffer, dataSize < 0); } } else { //chunked. We just write the headers, and leave all the data in the buffer //they need to send us a read body chunk in order to get any data buffer.flip(); if (buffer.remaining() == 0) { pooledHeaderBuffer.close(); return new SendFrameHeader(dataInBuffer, null, true); } dataBuffer.limit(dataBuffer.position()); return new SendFrameHeader(dataInBuffer, pooledHeaderBuffer, true); } } catch (BufferOverflowException e) { //TODO: UNDERTOW-901 pooledHeaderBuffer.close(); markBroken(); throw e; } } SendFrameHeader generateSendFrameHeader() { header = createFrameHeaderImpl(); return header; } void chunkRequested(int size) throws IOException { requestedChunkSize = size; getChannel().recalculateHeldFrames(); } public void startDiscard() { discardMode = true; try { getChannel().recalculateHeldFrames(); } catch (IOException e) { markBroken(); } } @Override protected final SendFrameHeader createFrameHeader() { SendFrameHeader header = this.header; this.header = null; return header; } @Override protected void handleFlushComplete(boolean finalFrame) { super.handleFlushComplete(finalFrame); if (finalFrame) { getChannel().sinkDone(); } if (finalFrame && finishListener != null) { finishListener.handleEvent(this); } } @Override protected void channelForciblyClosed() throws IOException { super.channelForciblyClosed(); getChannel().sinkDone(); finishListener.handleEvent(this); } public void clearHeader() { header = null; } } AjpClientResponseStreamSourceChannel.java000066400000000000000000000062141420065311100351370ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ajp/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.ajp; import java.io.IOException; import org.xnio.ChannelListener; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.protocol.framed.FrameHeaderData; import io.undertow.util.HeaderMap; /** * @author Stuart Douglas */ public class AjpClientResponseStreamSourceChannel extends AbstractAjpClientStreamSourceChannel { private ChannelListener finishListener; private final HeaderMap headers; private final int statusCode; private final String reasonPhrase; public AjpClientResponseStreamSourceChannel(AjpClientChannel framedChannel, HeaderMap headers, int statusCode, String reasonPhrase, PooledByteBuffer frameData, int remaining) { super(framedChannel, frameData, remaining); this.headers = headers; this.statusCode = statusCode; this.reasonPhrase = reasonPhrase; } public HeaderMap getHeaders() { return headers; } public int getStatusCode() { return statusCode; } public String getReasonPhrase() { return reasonPhrase; } public void setFinishListener(ChannelListener finishListener) { this.finishListener = finishListener; } @Override protected void handleHeaderData(FrameHeaderData headerData) { if(headerData instanceof AjpClientChannel.EndResponse) { lastFrame(); } } @Override protected long updateFrameDataRemaining(PooledByteBuffer frameData, long frameDataRemaining) { if(frameDataRemaining > 0 && frameData.getBuffer().remaining() == frameDataRemaining) { //there is a null terminator on the end frameData.getBuffer().limit(frameData.getBuffer().limit() - 1); return frameDataRemaining - 1; } return frameDataRemaining; } @Override protected void complete() throws IOException { if(finishListener != null) { getFramedChannel().sourceDone(); finishListener.handleEvent(this); } } @Override public void wakeupReads() { super.wakeupReads(); getFramedChannel().resumeReceives(); } @Override public void resumeReads() { super.resumeReads(); getFramedChannel().resumeReceives(); } @Override public void suspendReads() { getFramedChannel().suspendReceives(); super.suspendReads(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ajp/AjpConstants.java000066400000000000000000000116761420065311100304170ustar00rootroot00000000000000package io.undertow.protocols.ajp; import static io.undertow.util.Methods.ACL; import static io.undertow.util.Methods.BASELINE_CONTROL; import static io.undertow.util.Methods.CHECKIN; import static io.undertow.util.Methods.CHECKOUT; import static io.undertow.util.Methods.COPY; import static io.undertow.util.Methods.DELETE; import static io.undertow.util.Methods.GET; import static io.undertow.util.Methods.HEAD; import static io.undertow.util.Methods.LABEL; import static io.undertow.util.Methods.LOCK; import static io.undertow.util.Methods.MERGE; import static io.undertow.util.Methods.MKACTIVITY; import static io.undertow.util.Methods.MKCOL; import static io.undertow.util.Methods.MKWORKSPACE; import static io.undertow.util.Methods.MOVE; import static io.undertow.util.Methods.OPTIONS; import static io.undertow.util.Methods.POST; import static io.undertow.util.Methods.PROPFIND; import static io.undertow.util.Methods.PROPPATCH; import static io.undertow.util.Methods.PUT; import static io.undertow.util.Methods.REPORT; import static io.undertow.util.Methods.SEARCH; import static io.undertow.util.Methods.TRACE; import static io.undertow.util.Methods.UNCHECKOUT; import static io.undertow.util.Methods.UNLOCK; import static io.undertow.util.Methods.UPDATE; import static io.undertow.util.Methods.VERSION_CONTROL; import java.util.Collections; import java.util.HashMap; import java.util.Map; import io.undertow.util.Headers; import io.undertow.util.HttpString; /** * @author Stuart Douglas */ class AjpConstants { public static final int FRAME_TYPE_SEND_HEADERS = 4; public static final int FRAME_TYPE_REQUEST_BODY_CHUNK = 6; public static final int FRAME_TYPE_SEND_BODY_CHUNK = 3; public static final int FRAME_TYPE_END_RESPONSE = 5; public static final int FRAME_TYPE_CPONG = 9; public static final int FRAME_TYPE_CPING = 10; public static final int FRAME_TYPE_SHUTDOWN = 7; static final Map HEADER_MAP; static final Map HTTP_METHODS_MAP; static final HttpString[] HTTP_HEADERS_ARRAY; static final int ATTR_CONTEXT = 0x01; static final int ATTR_SERVLET_PATH = 0x02; static final int ATTR_REMOTE_USER = 0x03; static final int ATTR_AUTH_TYPE = 0x04; static final int ATTR_QUERY_STRING = 0x05; static final int ATTR_ROUTE = 0x06; static final int ATTR_SSL_CERT = 0x07; static final int ATTR_SSL_CIPHER = 0x08; static final int ATTR_SSL_SESSION = 0x09; static final int ATTR_REQ_ATTRIBUTE = 0x0A; static final int ATTR_SSL_KEY_SIZE = 0x0B; static final int ATTR_SECRET = 0x0C; static final int ATTR_STORED_METHOD = 0x0D; static final int ATTR_ARE_DONE = 0xFF; static { final Map headers = new HashMap<>(); headers.put(Headers.ACCEPT, 0xA001); headers.put(Headers.ACCEPT_CHARSET, 0xA002); headers.put(Headers.ACCEPT_ENCODING, 0xA003); headers.put(Headers.ACCEPT_LANGUAGE, 0xA004); headers.put(Headers.AUTHORIZATION, 0xA005); headers.put(Headers.CONNECTION, 0xA006); headers.put(Headers.CONTENT_TYPE, 0xA007); headers.put(Headers.CONTENT_LENGTH, 0xA008); headers.put(Headers.COOKIE, 0xA009); headers.put(Headers.COOKIE2, 0xA00A); headers.put(Headers.HOST, 0xA00B); headers.put(Headers.PRAGMA, 0xA00C); headers.put(Headers.REFERER, 0xA00D); headers.put(Headers.USER_AGENT, 0xA00E); HEADER_MAP = Collections.unmodifiableMap(headers); final Map methods = new HashMap<>(); methods.put(OPTIONS, 1); methods.put(GET, 2); methods.put(HEAD, 3); methods.put(POST, 4); methods.put(PUT, 5); methods.put(DELETE, 6); methods.put(TRACE, 7); methods.put(PROPFIND, 8); methods.put(PROPPATCH, 9); methods.put(MKCOL, 10); methods.put(COPY, 11); methods.put(MOVE, 12); methods.put(LOCK, 13); methods.put(UNLOCK, 14); methods.put(ACL, 15); methods.put(REPORT, 16); methods.put(VERSION_CONTROL, 17); methods.put(CHECKIN, 18); methods.put(CHECKOUT, 19); methods.put(UNCHECKOUT, 20); methods.put(SEARCH, 21); methods.put(MKWORKSPACE, 22); methods.put(UPDATE, 23); methods.put(LABEL, 24); methods.put(MERGE, 25); methods.put(BASELINE_CONTROL, 26); methods.put(MKACTIVITY, 27); HTTP_METHODS_MAP = Collections.unmodifiableMap(methods); HTTP_HEADERS_ARRAY = new HttpString[]{null, Headers.CONTENT_TYPE, Headers.CONTENT_LANGUAGE, Headers.CONTENT_LENGTH, Headers.DATE, Headers.LAST_MODIFIED, Headers.LOCATION, Headers.SET_COOKIE, Headers.SET_COOKIE2, Headers.SERVLET_ENGINE, Headers.STATUS, Headers.WWW_AUTHENTICATE }; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ajp/AjpResponseParser.java000066400000000000000000000305111420065311100314030ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.ajp; import io.undertow.util.HeaderMap; import io.undertow.util.HttpString; import java.io.IOException; import java.nio.ByteBuffer; import static io.undertow.protocols.ajp.AjpConstants.FRAME_TYPE_END_RESPONSE; import static io.undertow.protocols.ajp.AjpConstants.FRAME_TYPE_REQUEST_BODY_CHUNK; import static io.undertow.protocols.ajp.AjpConstants.FRAME_TYPE_SEND_BODY_CHUNK; import static io.undertow.protocols.ajp.AjpConstants.FRAME_TYPE_SEND_HEADERS; /** * Parser used for the client (i.e. load balancer) side of the AJP connection. * * @author Stuart Douglas */ class AjpResponseParser { public static final AjpResponseParser INSTANCE = new AjpResponseParser(); private static final int AB = ('A' << 8) + 'B'; //states public static final int BEGIN = 0; public static final int READING_MAGIC_NUMBER = 1; public static final int READING_DATA_SIZE = 2; public static final int READING_PREFIX_CODE = 3; public static final int READING_STATUS_CODE = 4; public static final int READING_REASON_PHRASE = 5; public static final int READING_NUM_HEADERS = 6; public static final int READING_HEADERS = 7; public static final int READING_PERSISTENT_BOOLEAN = 8; public static final int READING_BODY_CHUNK_LENGTH = 9; public static final int DONE = 10; //parser states int state; byte prefix; int numHeaders = 0; HttpString currentHeader; //final states int statusCode; String reasonPhrase; HeaderMap headers = new HeaderMap(); int readBodyChunkSize; public boolean isComplete() { return state == DONE; } public void parse(final ByteBuffer buf) throws IOException { if (!buf.hasRemaining()) { return; } switch (this.state) { case BEGIN: { IntegerHolder result = parse16BitInteger(buf); if (!result.readComplete) { return; } else { if (result.value != AB) { throw new IOException("Wrong magic number"); } } } case READING_DATA_SIZE: { IntegerHolder result = parse16BitInteger(buf); if (!result.readComplete) { this.state = READING_DATA_SIZE; return; } } case READING_PREFIX_CODE: { if (!buf.hasRemaining()) { this.state = READING_PREFIX_CODE; return; } else { final byte prefix = buf.get(); this.prefix = prefix; if (prefix == FRAME_TYPE_END_RESPONSE) { this.state = READING_PERSISTENT_BOOLEAN; break; } else if (prefix == FRAME_TYPE_SEND_BODY_CHUNK) { this.state = READING_BODY_CHUNK_LENGTH; break; } else if (prefix != FRAME_TYPE_SEND_HEADERS && prefix != FRAME_TYPE_REQUEST_BODY_CHUNK) { this.state = DONE; return; } } } case READING_STATUS_CODE: { //this state is overloaded for the request size //when reading state=6 (read_body_chunk requests) IntegerHolder result = parse16BitInteger(buf); if (result.readComplete) { if (this.prefix == FRAME_TYPE_SEND_HEADERS) { statusCode = result.value; } else { //read body chunk or end result //a bit hacky this.state = DONE; this.readBodyChunkSize = result.value; return; } } else { this.state = READING_STATUS_CODE; return; } } case READING_REASON_PHRASE: { StringHolder result = parseString(buf, false); if (result.readComplete) { reasonPhrase = result.value; } else { this.state = READING_REASON_PHRASE; return; } } case READING_NUM_HEADERS: { IntegerHolder result = parse16BitInteger(buf); if (!result.readComplete) { this.state = READING_NUM_HEADERS; return; } else { this.numHeaders = result.value; } } case READING_HEADERS: { int readHeaders = this.readHeaders; while (readHeaders < this.numHeaders) { if (this.currentHeader == null) { StringHolder result = parseString(buf, true); if (!result.readComplete) { this.state = READING_HEADERS; this.readHeaders = readHeaders; return; } if (result.header != null) { this.currentHeader = result.header; } else { this.currentHeader = HttpString.tryFromString(result.value); } } StringHolder result = parseString(buf, false); if (!result.readComplete) { this.state = READING_HEADERS; this.readHeaders = readHeaders; return; } headers.add(this.currentHeader, result.value); this.currentHeader = null; ++readHeaders; } break; } } if (state == READING_PERSISTENT_BOOLEAN) { if (!buf.hasRemaining()) { return; } currentIntegerPart = buf.get(); this.state = DONE; return; } else if (state == READING_BODY_CHUNK_LENGTH) { IntegerHolder result = parse16BitInteger(buf); if (result.readComplete) { this.currentIntegerPart = result.value; this.state = DONE; } return; } else { this.state = DONE; } } protected HttpString headers(int offset) { return AjpConstants.HTTP_HEADERS_ARRAY[offset]; } public HeaderMap getHeaders() { return headers; } public int getStatusCode() { return statusCode; } public String getReasonPhrase() { return reasonPhrase; } public int getReadBodyChunkSize() { return readBodyChunkSize; } public static final int STRING_LENGTH_MASK = 1 << 31; /** * The length of the string being read */ public int stringLength = -1; /** * The current string being read */ public StringBuilder currentString; /** * when reading the first byte of an integer this stores the first value. It is set to -1 to signify that * the first byte has not been read yet. */ public int currentIntegerPart = -1; boolean containsUrlCharacters = false; public int readHeaders = 0; public void reset() { state = 0; prefix = 0; numHeaders = 0; currentHeader = null; statusCode = 0; reasonPhrase = null; headers = new HeaderMap(); stringLength = -1; currentString = null; currentIntegerPart = -1; readHeaders = 0; } protected IntegerHolder parse16BitInteger(ByteBuffer buf) { if (!buf.hasRemaining()) { return new IntegerHolder(-1, false); } int number = this.currentIntegerPart; if (number == -1) { number = (buf.get() & 0xFF); } if (buf.hasRemaining()) { final byte b = buf.get(); int result = ((0xFF & number) << 8) + (b & 0xFF); this.currentIntegerPart = -1; return new IntegerHolder(result, true); } else { this.currentIntegerPart = number; return new IntegerHolder(-1, false); } } protected StringHolder parseString(ByteBuffer buf, boolean header) { boolean containsUrlCharacters = this.containsUrlCharacters; if (!buf.hasRemaining()) { return new StringHolder(null, false, false); } int stringLength = this.stringLength; if (stringLength == -1) { int number = buf.get() & 0xFF; if (buf.hasRemaining()) { final byte b = buf.get(); stringLength = ((0xFF & number) << 8) + (b & 0xFF); } else { this.stringLength = number | STRING_LENGTH_MASK; return new StringHolder(null, false, false); } } else if ((stringLength & STRING_LENGTH_MASK) != 0) { int number = stringLength & ~STRING_LENGTH_MASK; stringLength = ((0xFF & number) << 8) + (buf.get() & 0xFF); } if (header && (stringLength & 0xFF00) != 0) { this.stringLength = -1; return new StringHolder(headers(stringLength & 0xFF)); } if (stringLength == 0xFFFF) { //OxFFFF means null this.stringLength = -1; return new StringHolder(null, true, false); } StringBuilder builder = this.currentString; if (builder == null) { builder = new StringBuilder(); this.currentString = builder; } int length = builder.length(); while (length < stringLength) { if (!buf.hasRemaining()) { this.stringLength = stringLength; this.containsUrlCharacters = containsUrlCharacters; return new StringHolder(null, false, false); } char c = (char) buf.get(); if(c == '+' || c == '%') { containsUrlCharacters = true; } builder.append(c); ++length; } if (buf.hasRemaining()) { buf.get(); //null terminator this.currentString = null; this.stringLength = -1; this.containsUrlCharacters = false; return new StringHolder(builder.toString(), true, containsUrlCharacters); } else { this.stringLength = stringLength; this.containsUrlCharacters = containsUrlCharacters; return new StringHolder(null, false, false); } } protected static class IntegerHolder { public final int value; public final boolean readComplete; private IntegerHolder(int value, boolean readComplete) { this.value = value; this.readComplete = readComplete; } } protected static class StringHolder { public final String value; public final HttpString header; public final boolean readComplete; public final boolean containsUrlCharacters; private StringHolder(String value, boolean readComplete, boolean containsUrlCharacters) { this.value = value; this.readComplete = readComplete; this.containsUrlCharacters = containsUrlCharacters; this.header = null; } private StringHolder(HttpString value) { this.value = null; this.readComplete = true; this.header = value; this.containsUrlCharacters = false; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ajp/AjpUtils.java000066400000000000000000000034341420065311100275340ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.ajp; import java.nio.ByteBuffer; import io.undertow.util.HttpString; /** * @author Stuart Douglas */ class AjpUtils { static boolean notNull(Boolean attachment) { return attachment == null ? false : attachment; } static int notNull(Integer attachment) { return attachment == null ? 0 : attachment; } static String notNull(String attachment) { return attachment == null ? "" : attachment; } static void putInt(final ByteBuffer buf, int value) { buf.put((byte) ((value >> 8) & 0xFF)); buf.put((byte) (value & 0xFF)); } static void putString(final ByteBuffer buf, String value) { final int length = value.length(); putInt(buf, length); for (int i = 0; i < length; ++i) { buf.put((byte) value.charAt(i)); } buf.put((byte) 0); } static void putHttpString(final ByteBuffer buf, HttpString value) { final int length = value.length(); putInt(buf, length); value.appendTo(buf); buf.put((byte) 0); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/alpn/000077500000000000000000000000001420065311100253125ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/alpn/ALPNEngineManager.java000066400000000000000000000026311420065311100313320ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2018 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * 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. */ package io.undertow.protocols.alpn; import java.util.function.Function; import javax.net.ssl.SSLEngine; public interface ALPNEngineManager { /** * @return The priority of this provider, higher priority managers will be tried first */ int getPriority(); /** * @param engine The original SSL Engine * @param selectedFunction A function that must be called when the Underlying SSL engine has been selected. The return value of this callback may be a wrapped engine, which must replace the selected engine * @return true if the engine was registered, false otherwise */ boolean registerEngine(SSLEngine engine, Function selectedFunction); } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/alpn/ALPNManager.java000066400000000000000000000057671420065311100302210ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.alpn; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.ServiceLoader; import java.util.function.Function; import javax.net.ssl.SSLEngine; /** * @author Stuart Douglas */ public class ALPNManager { private final ALPNProvider[] alpnProviders; private final ALPNEngineManager[] alpnEngineManagers; public static final ALPNManager INSTANCE = new ALPNManager(ALPNManager.class.getClassLoader()); public ALPNManager(ClassLoader classLoader) { ServiceLoader loader = ServiceLoader.load(ALPNProvider.class, classLoader); List provider = new ArrayList<>(); for (ALPNProvider prov : loader) { provider.add(prov); } Collections.sort(provider, new Comparator() { @Override public int compare(ALPNProvider o1, ALPNProvider o2) { return Integer.compare(o2.getPriority(), o1.getPriority()); //highest first } }); this.alpnProviders = provider.toArray(new ALPNProvider[0]); ServiceLoader managerLoader = ServiceLoader.load(ALPNEngineManager.class, classLoader); List managers = new ArrayList<>(); for (ALPNEngineManager manager : managerLoader) { managers.add(manager); } Collections.sort(managers, new Comparator() { @Override public int compare(ALPNEngineManager o1, ALPNEngineManager o2) { return Integer.compare(o2.getPriority(), o1.getPriority()); //highest first } }); this.alpnEngineManagers = managers.toArray(new ALPNEngineManager[0]); } public ALPNProvider getProvider(SSLEngine engine) { for (ALPNProvider provider : alpnProviders) { if (provider.isEnabled(engine)) { return provider; } } return null; } public void registerEngineCallback(SSLEngine original, Function selectionFunction) { for(ALPNEngineManager manager : alpnEngineManagers) { if(manager.registerEngine(original, selectionFunction)) { return; } } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/alpn/ALPNProvider.java000066400000000000000000000033001420065311100304160ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.alpn; import javax.net.ssl.SSLEngine; /** * Interface that allows for ALPN providers to be dynamically selected. * * THIS API IS TECH PREVIEW * * It will not be finalised until JDK9 has been released and the JDK9 ALPN API is 100% confirmed * * @author Stuart Douglas */ public interface ALPNProvider { boolean isEnabled(SSLEngine sslEngine); /** * Sets the SSL protocols, and potentially wraps the SSLEngine * @param engine The original engine * @param protocols The protocols * @return The new SSLEngine */ SSLEngine setProtocols(SSLEngine engine, String[] protocols); /** * Gets the selected ALPN protocol, of null if none was selected. * * * @param engine The SSL Engine * @return The selected protocol */ String getSelectedProtocol(SSLEngine engine); /** * * @return The priority of this provider, higher priority providers will be tried first */ int getPriority(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/alpn/DefaultAlpnEngineManager.java000066400000000000000000000021541420065311100327770ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2018 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * 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. */ package io.undertow.protocols.alpn; import java.util.function.Function; import javax.net.ssl.SSLEngine; public class DefaultAlpnEngineManager implements ALPNEngineManager { @Override public int getPriority() { return 0; } @Override public boolean registerEngine(SSLEngine engine, Function selectedFunction) { selectedFunction.apply(engine); return true; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/alpn/JDK8HackAlpnProvider.java000066400000000000000000000035151420065311100317760ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.alpn; import java.util.Arrays; import javax.net.ssl.SSLEngine; import io.undertow.protocols.ssl.ALPNHackSSLEngine; /** * Open listener adaptor for ALPN connections that uses the SSLExplorer based approach and hack into the JDK8 * SSLEngine via reflection. * * @author Stuart Douglas */ public class JDK8HackAlpnProvider implements ALPNProvider { @Override public boolean isEnabled(SSLEngine sslEngine) { return ALPNHackSSLEngine.isEnabled(sslEngine); } @Override public SSLEngine setProtocols(SSLEngine engine, String[] protocols) { ALPNHackSSLEngine newEngine = engine instanceof ALPNHackSSLEngine ? (ALPNHackSSLEngine) engine : new ALPNHackSSLEngine(engine); newEngine.setApplicationProtocols(Arrays.asList(protocols)); return newEngine; } @Override public String getSelectedProtocol(SSLEngine engine) { return ((ALPNHackSSLEngine) engine).getSelectedApplicationProtocol(); } @Override public int getPriority() { return 300; } @Override public String toString() { return "JDK8AlpnProvider"; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/alpn/JDK9AlpnProvider.java000066400000000000000000000130561420065311100312110ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.alpn; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLParameters; import io.undertow.UndertowLogger; /** * Open listener adaptor for ALPN connections that use the JDK9 API *

* Not a proper open listener as such, but more a mechanism for selecting between them * * @author Stuart Douglas */ public class JDK9AlpnProvider implements ALPNProvider { public static final JDK9ALPNMethods JDK_9_ALPN_METHODS; private static final String JDK8_SUPPORT_PROPERTY = "io.undertow.protocols.alpn.jdk8"; static { JDK_9_ALPN_METHODS = AccessController.doPrivileged(new PrivilegedAction() { @Override public JDK9ALPNMethods run() { try { final String javaVersion = System.getProperty("java.specification.version"); int vmVersion = 8; try { final Matcher matcher = Pattern.compile("^(?:1\\.)?(\\d+)$").matcher(javaVersion); if (matcher.find()) { vmVersion = Integer.parseInt(matcher.group(1)); } } catch (Exception ignore) { } // There was a backport of the ALPN support to Java 8 in rev 251. If a non-JDK implementation of the // SSLEngine is used these methods throw an UnsupportedOperationException by default. However the // methods would exist and could result in issues. By default it seems most JDK's have a working // implementation. However since this was introduced in a micro release we should have a way to // disable this feature. Setting the io.undertow.protocols.alpn.jdk8 to false will workaround the // possible issue where the SSLEngine does not have an implementation of these methods. final String value = System.getProperty(JDK8_SUPPORT_PROPERTY); final boolean addSupportIfExists = value == null || value.trim().isEmpty() || Boolean.parseBoolean(value); if (vmVersion > 8 || addSupportIfExists) { Method setApplicationProtocols = SSLParameters.class.getMethod("setApplicationProtocols", String[].class); Method getApplicationProtocol = SSLEngine.class.getMethod("getApplicationProtocol"); UndertowLogger.ROOT_LOGGER.debug("Using JDK9 ALPN"); return new JDK9ALPNMethods(setApplicationProtocols, getApplicationProtocol); } UndertowLogger.ROOT_LOGGER.debugf("It's not certain ALPN support was found. " + "Provider %s will be disabled.", JDK9AlpnProvider.class.getName()); return null; } catch (Exception e) { UndertowLogger.ROOT_LOGGER.debug("JDK9 ALPN not supported"); return null; } } }); } public static class JDK9ALPNMethods { private final Method setApplicationProtocols; private final Method getApplicationProtocol; JDK9ALPNMethods(Method setApplicationProtocols, Method getApplicationProtocol) { this.setApplicationProtocols = setApplicationProtocols; this.getApplicationProtocol = getApplicationProtocol; } public Method getApplicationProtocol() { return getApplicationProtocol; } public Method setApplicationProtocols() { return setApplicationProtocols; } } @Override public boolean isEnabled(SSLEngine sslEngine) { return JDK_9_ALPN_METHODS != null; } @Override public SSLEngine setProtocols(SSLEngine engine, String[] protocols) { SSLParameters sslParameters = engine.getSSLParameters(); try { JDK_9_ALPN_METHODS.setApplicationProtocols().invoke(sslParameters, (Object) protocols); } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } engine.setSSLParameters(sslParameters); return engine; } @Override public String getSelectedProtocol(SSLEngine engine) { try { return (String) JDK_9_ALPN_METHODS.getApplicationProtocol().invoke(engine); } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } } @Override public int getPriority() { return 200; } @Override public String toString() { return "JDK9AlpnProvider"; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/alpn/JettyAlpnProvider.java000066400000000000000000000106461420065311100316110ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.alpn; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Arrays; import java.util.List; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLSession; import org.eclipse.jetty.alpn.ALPN; /** * Jetty ALPN implementation. This is the lowest priority * * @author Stuart Douglas */ public class JettyAlpnProvider implements ALPNProvider { private static final String PROTOCOL_KEY = JettyAlpnProvider.class.getName() + ".protocol"; private static final boolean ENABLED; static { ENABLED = AccessController.doPrivileged(new PrivilegedAction() { @Override public Boolean run() { try { Class.forName("org.eclipse.jetty.alpn.ALPN", true, JettyAlpnProvider.class.getClassLoader()); return true; } catch (ClassNotFoundException e) { return false; } } }); } @Override public boolean isEnabled(SSLEngine sslEngine) { return ENABLED; } @Override public SSLEngine setProtocols(SSLEngine engine, String[] protocols) { return Impl.setProtocols(engine, protocols); } @Override public String getSelectedProtocol(SSLEngine engine) { SSLSession handshake = engine.getHandshakeSession(); if (handshake != null) { return (String) handshake.getValue(PROTOCOL_KEY); } handshake = engine.getSession(); if (handshake != null) { return (String) handshake.getValue(PROTOCOL_KEY); } return null; } @Override public int getPriority() { return 100; } private static class Impl { static SSLEngine setProtocols(final SSLEngine engine, final String[] protocols) { if (engine.getUseClientMode()) { ALPN.put(engine, new ALPNClientSelectionProvider(Arrays.asList(protocols), engine)); } else { ALPN.put(engine, new ALPN.ServerProvider() { @Override public void unsupported() { ALPN.remove(engine); } @Override public String select(List strings) { ALPN.remove(engine); for (String p : protocols) { if (strings.contains(p)) { engine.getHandshakeSession().putValue(PROTOCOL_KEY, p); return p; } } return null; } }); } return engine; } } private static class ALPNClientSelectionProvider implements ALPN.ClientProvider { final List protocols; private String selected; private final SSLEngine sslEngine; private ALPNClientSelectionProvider(List protocols, SSLEngine sslEngine) { this.protocols = protocols; this.sslEngine = sslEngine; } public boolean supports() { return true; } @Override public List protocols() { return protocols; } @Override public void unsupported() { ALPN.remove(sslEngine); selected = ""; } @Override public void selected(String s) { ALPN.remove(sslEngine); selected = s; sslEngine.getHandshakeSession().putValue(PROTOCOL_KEY, selected); } } @Override public String toString() { return "JettyAlpnProvider"; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/alpn/OpenSSLAlpnProvider.java000066400000000000000000000104701420065311100317700ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.alpn; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.security.AccessController; import java.security.PrivilegedAction; import javax.net.ssl.SSLEngine; import io.undertow.UndertowLogger; /** * Open listener adaptor for ALPN connections that use the Wildfly OpenSSL implementation *

* * @author Stuart Douglas */ public class OpenSSLAlpnProvider implements ALPNProvider { private static volatile OpenSSLALPNMethods openSSLALPNMethods; private static volatile boolean initialized; public static final String OPENSSL_ENGINE = "org.wildfly.openssl.OpenSSLEngine"; public static class OpenSSLALPNMethods { private final Method setApplicationProtocols; private final Method getApplicationProtocol; OpenSSLALPNMethods(Method setApplicationProtocols, Method getApplicationProtocol) { this.setApplicationProtocols = setApplicationProtocols; this.getApplicationProtocol = getApplicationProtocol; } public Method getApplicationProtocol() { return getApplicationProtocol; } public Method setApplicationProtocols() { return setApplicationProtocols; } } @Override public boolean isEnabled(SSLEngine sslEngine) { return sslEngine.getClass().getName().equals(OPENSSL_ENGINE) && getOpenSSLAlpnMethods() != null; } @Override public SSLEngine setProtocols(SSLEngine engine, String[] protocols) { try { getOpenSSLAlpnMethods().setApplicationProtocols().invoke(engine, (Object) protocols); } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } return engine; } @Override public String getSelectedProtocol(SSLEngine engine) { try { return (String) getOpenSSLAlpnMethods().getApplicationProtocol().invoke(engine); } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } } private static OpenSSLALPNMethods getOpenSSLAlpnMethods() { if(!initialized) { synchronized (OpenSSLAlpnProvider.class) { if(!initialized) { openSSLALPNMethods = AccessController.doPrivileged(new PrivilegedAction() { @Override public OpenSSLALPNMethods run() { try { Class openSSLEngine = Class.forName(OPENSSL_ENGINE, true, OpenSSLAlpnProvider.class.getClassLoader()); Method setApplicationProtocols = openSSLEngine.getMethod("setApplicationProtocols", String[].class); Method getApplicationProtocol = openSSLEngine.getMethod("getSelectedApplicationProtocol"); UndertowLogger.ROOT_LOGGER.debug("OpenSSL ALPN Enabled"); return new OpenSSLALPNMethods(setApplicationProtocols, getApplicationProtocol); } catch (Throwable e) { UndertowLogger.ROOT_LOGGER.debug("OpenSSL ALPN disabled", e); return null; } } }); initialized = true; } } } return openSSLALPNMethods; } @Override public int getPriority() { return 400; } @Override public String toString() { return "OpenSSLAlpnProvider"; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/000077500000000000000000000000001420065311100254215ustar00rootroot00000000000000AbstractHttp2StreamSinkChannel.java000066400000000000000000000022601420065311100341640ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import io.undertow.server.protocol.framed.AbstractFramedStreamSinkChannel; /** * @author Stuart Douglas */ public class AbstractHttp2StreamSinkChannel extends AbstractFramedStreamSinkChannel { AbstractHttp2StreamSinkChannel(Http2Channel channel) { super(channel); } @Override protected boolean isLastFrame() { return false; } } AbstractHttp2StreamSourceChannel.java000066400000000000000000000040371420065311100345240ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.protocol.framed.AbstractFramedStreamSourceChannel; import io.undertow.server.protocol.framed.FrameHeaderData; /** * HTTP2 stream source channel * * @author Stuart Douglas */ public class AbstractHttp2StreamSourceChannel extends AbstractFramedStreamSourceChannel { AbstractHttp2StreamSourceChannel(Http2Channel framedChannel) { super(framedChannel); } AbstractHttp2StreamSourceChannel(Http2Channel framedChannel, PooledByteBuffer data, long frameDataRemaining) { super(framedChannel, data, frameDataRemaining); } @Override protected void handleHeaderData(FrameHeaderData headerData) { //by default we do nothing } @Override protected Http2Channel getFramedChannel() { return super.getFramedChannel(); } public Http2Channel getHttp2Channel() { return getFramedChannel(); } @Override protected void lastFrame() { super.lastFrame(); } void rstStream() { rstStream(Http2Channel.ERROR_CANCEL); } void rstStream(int error) { //noop by default } protected void markStreamBroken() { super.markStreamBroken(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/ConnectionErrorException.java000066400000000000000000000024651420065311100332630ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.io.IOException; /** * Exception that represents a connection error * * @author Stuart Douglas */ public class ConnectionErrorException extends IOException { private final int code; public ConnectionErrorException(int code) { this.code = code; } public ConnectionErrorException(int code, String message) { super(message); this.code = code; } public ConnectionErrorException(int code, Throwable cause) { super(cause); this.code = code; } public int getCode() { return code; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/HPackHuffman.java000066400000000000000000000562711420065311100305720ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import io.undertow.UndertowMessages; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashSet; import java.util.Set; /** * @author Stuart Douglas */ public class HPackHuffman { private static final HuffmanCode[] HUFFMAN_CODES; /** * array based tree representation of a huffman code. *

* the high two bytes corresponds to the tree node if the bit is set, and the low two bytes for if it is clear * if the high bit is set it is a terminal node, otherwise it contains the next node position. */ private static final int[] DECODING_TABLE; private static final int LOW_TERMINAL_BIT = (0b10000000) << 8; private static final int HIGH_TERMINAL_BIT = (0b10000000) << 24; private static final int LOW_MASK = 0b0111111111111111; static { HuffmanCode[] codes = new HuffmanCode[257]; codes[0] = new HuffmanCode(0x1ff8, 13); codes[1] = new HuffmanCode(0x7fffd8, 23); codes[2] = new HuffmanCode(0xfffffe2, 28); codes[3] = new HuffmanCode(0xfffffe3, 28); codes[4] = new HuffmanCode(0xfffffe4, 28); codes[5] = new HuffmanCode(0xfffffe5, 28); codes[6] = new HuffmanCode(0xfffffe6, 28); codes[7] = new HuffmanCode(0xfffffe7, 28); codes[8] = new HuffmanCode(0xfffffe8, 28); codes[9] = new HuffmanCode(0xffffea, 24); codes[10] = new HuffmanCode(0x3ffffffc, 30); codes[11] = new HuffmanCode(0xfffffe9, 28); codes[12] = new HuffmanCode(0xfffffea, 28); codes[13] = new HuffmanCode(0x3ffffffd, 30); codes[14] = new HuffmanCode(0xfffffeb, 28); codes[15] = new HuffmanCode(0xfffffec, 28); codes[16] = new HuffmanCode(0xfffffed, 28); codes[17] = new HuffmanCode(0xfffffee, 28); codes[18] = new HuffmanCode(0xfffffef, 28); codes[19] = new HuffmanCode(0xffffff0, 28); codes[20] = new HuffmanCode(0xffffff1, 28); codes[21] = new HuffmanCode(0xffffff2, 28); codes[22] = new HuffmanCode(0x3ffffffe, 30); codes[23] = new HuffmanCode(0xffffff3, 28); codes[24] = new HuffmanCode(0xffffff4, 28); codes[25] = new HuffmanCode(0xffffff5, 28); codes[26] = new HuffmanCode(0xffffff6, 28); codes[27] = new HuffmanCode(0xffffff7, 28); codes[28] = new HuffmanCode(0xffffff8, 28); codes[29] = new HuffmanCode(0xffffff9, 28); codes[30] = new HuffmanCode(0xffffffa, 28); codes[31] = new HuffmanCode(0xffffffb, 28); codes[32] = new HuffmanCode(0x14, 6); codes[33] = new HuffmanCode(0x3f8, 10); codes[34] = new HuffmanCode(0x3f9, 10); codes[35] = new HuffmanCode(0xffa, 12); codes[36] = new HuffmanCode(0x1ff9, 13); codes[37] = new HuffmanCode(0x15, 6); codes[38] = new HuffmanCode(0xf8, 8); codes[39] = new HuffmanCode(0x7fa, 11); codes[40] = new HuffmanCode(0x3fa, 10); codes[41] = new HuffmanCode(0x3fb, 10); codes[42] = new HuffmanCode(0xf9, 8); codes[43] = new HuffmanCode(0x7fb, 11); codes[44] = new HuffmanCode(0xfa, 8); codes[45] = new HuffmanCode(0x16, 6); codes[46] = new HuffmanCode(0x17, 6); codes[47] = new HuffmanCode(0x18, 6); codes[48] = new HuffmanCode(0x0, 5); codes[49] = new HuffmanCode(0x1, 5); codes[50] = new HuffmanCode(0x2, 5); codes[51] = new HuffmanCode(0x19, 6); codes[52] = new HuffmanCode(0x1a, 6); codes[53] = new HuffmanCode(0x1b, 6); codes[54] = new HuffmanCode(0x1c, 6); codes[55] = new HuffmanCode(0x1d, 6); codes[56] = new HuffmanCode(0x1e, 6); codes[57] = new HuffmanCode(0x1f, 6); codes[58] = new HuffmanCode(0x5c, 7); codes[59] = new HuffmanCode(0xfb, 8); codes[60] = new HuffmanCode(0x7ffc, 15); codes[61] = new HuffmanCode(0x20, 6); codes[62] = new HuffmanCode(0xffb, 12); codes[63] = new HuffmanCode(0x3fc, 10); codes[64] = new HuffmanCode(0x1ffa, 13); codes[65] = new HuffmanCode(0x21, 6); codes[66] = new HuffmanCode(0x5d, 7); codes[67] = new HuffmanCode(0x5e, 7); codes[68] = new HuffmanCode(0x5f, 7); codes[69] = new HuffmanCode(0x60, 7); codes[70] = new HuffmanCode(0x61, 7); codes[71] = new HuffmanCode(0x62, 7); codes[72] = new HuffmanCode(0x63, 7); codes[73] = new HuffmanCode(0x64, 7); codes[74] = new HuffmanCode(0x65, 7); codes[75] = new HuffmanCode(0x66, 7); codes[76] = new HuffmanCode(0x67, 7); codes[77] = new HuffmanCode(0x68, 7); codes[78] = new HuffmanCode(0x69, 7); codes[79] = new HuffmanCode(0x6a, 7); codes[80] = new HuffmanCode(0x6b, 7); codes[81] = new HuffmanCode(0x6c, 7); codes[82] = new HuffmanCode(0x6d, 7); codes[83] = new HuffmanCode(0x6e, 7); codes[84] = new HuffmanCode(0x6f, 7); codes[85] = new HuffmanCode(0x70, 7); codes[86] = new HuffmanCode(0x71, 7); codes[87] = new HuffmanCode(0x72, 7); codes[88] = new HuffmanCode(0xfc, 8); codes[89] = new HuffmanCode(0x73, 7); codes[90] = new HuffmanCode(0xfd, 8); codes[91] = new HuffmanCode(0x1ffb, 13); codes[92] = new HuffmanCode(0x7fff0, 19); codes[93] = new HuffmanCode(0x1ffc, 13); codes[94] = new HuffmanCode(0x3ffc, 14); codes[95] = new HuffmanCode(0x22, 6); codes[96] = new HuffmanCode(0x7ffd, 15); codes[97] = new HuffmanCode(0x3, 5); codes[98] = new HuffmanCode(0x23, 6); codes[99] = new HuffmanCode(0x4, 5); codes[100] = new HuffmanCode(0x24, 6); codes[101] = new HuffmanCode(0x5, 5); codes[102] = new HuffmanCode(0x25, 6); codes[103] = new HuffmanCode(0x26, 6); codes[104] = new HuffmanCode(0x27, 6); codes[105] = new HuffmanCode(0x6, 5); codes[106] = new HuffmanCode(0x74, 7); codes[107] = new HuffmanCode(0x75, 7); codes[108] = new HuffmanCode(0x28, 6); codes[109] = new HuffmanCode(0x29, 6); codes[110] = new HuffmanCode(0x2a, 6); codes[111] = new HuffmanCode(0x7, 5); codes[112] = new HuffmanCode(0x2b, 6); codes[113] = new HuffmanCode(0x76, 7); codes[114] = new HuffmanCode(0x2c, 6); codes[115] = new HuffmanCode(0x8, 5); codes[116] = new HuffmanCode(0x9, 5); codes[117] = new HuffmanCode(0x2d, 6); codes[118] = new HuffmanCode(0x77, 7); codes[119] = new HuffmanCode(0x78, 7); codes[120] = new HuffmanCode(0x79, 7); codes[121] = new HuffmanCode(0x7a, 7); codes[122] = new HuffmanCode(0x7b, 7); codes[123] = new HuffmanCode(0x7ffe, 15); codes[124] = new HuffmanCode(0x7fc, 11); codes[125] = new HuffmanCode(0x3ffd, 14); codes[126] = new HuffmanCode(0x1ffd, 13); codes[127] = new HuffmanCode(0xffffffc, 28); codes[128] = new HuffmanCode(0xfffe6, 20); codes[129] = new HuffmanCode(0x3fffd2, 22); codes[130] = new HuffmanCode(0xfffe7, 20); codes[131] = new HuffmanCode(0xfffe8, 20); codes[132] = new HuffmanCode(0x3fffd3, 22); codes[133] = new HuffmanCode(0x3fffd4, 22); codes[134] = new HuffmanCode(0x3fffd5, 22); codes[135] = new HuffmanCode(0x7fffd9, 23); codes[136] = new HuffmanCode(0x3fffd6, 22); codes[137] = new HuffmanCode(0x7fffda, 23); codes[138] = new HuffmanCode(0x7fffdb, 23); codes[139] = new HuffmanCode(0x7fffdc, 23); codes[140] = new HuffmanCode(0x7fffdd, 23); codes[141] = new HuffmanCode(0x7fffde, 23); codes[142] = new HuffmanCode(0xffffeb, 24); codes[143] = new HuffmanCode(0x7fffdf, 23); codes[144] = new HuffmanCode(0xffffec, 24); codes[145] = new HuffmanCode(0xffffed, 24); codes[146] = new HuffmanCode(0x3fffd7, 22); codes[147] = new HuffmanCode(0x7fffe0, 23); codes[148] = new HuffmanCode(0xffffee, 24); codes[149] = new HuffmanCode(0x7fffe1, 23); codes[150] = new HuffmanCode(0x7fffe2, 23); codes[151] = new HuffmanCode(0x7fffe3, 23); codes[152] = new HuffmanCode(0x7fffe4, 23); codes[153] = new HuffmanCode(0x1fffdc, 21); codes[154] = new HuffmanCode(0x3fffd8, 22); codes[155] = new HuffmanCode(0x7fffe5, 23); codes[156] = new HuffmanCode(0x3fffd9, 22); codes[157] = new HuffmanCode(0x7fffe6, 23); codes[158] = new HuffmanCode(0x7fffe7, 23); codes[159] = new HuffmanCode(0xffffef, 24); codes[160] = new HuffmanCode(0x3fffda, 22); codes[161] = new HuffmanCode(0x1fffdd, 21); codes[162] = new HuffmanCode(0xfffe9, 20); codes[163] = new HuffmanCode(0x3fffdb, 22); codes[164] = new HuffmanCode(0x3fffdc, 22); codes[165] = new HuffmanCode(0x7fffe8, 23); codes[166] = new HuffmanCode(0x7fffe9, 23); codes[167] = new HuffmanCode(0x1fffde, 21); codes[168] = new HuffmanCode(0x7fffea, 23); codes[169] = new HuffmanCode(0x3fffdd, 22); codes[170] = new HuffmanCode(0x3fffde, 22); codes[171] = new HuffmanCode(0xfffff0, 24); codes[172] = new HuffmanCode(0x1fffdf, 21); codes[173] = new HuffmanCode(0x3fffdf, 22); codes[174] = new HuffmanCode(0x7fffeb, 23); codes[175] = new HuffmanCode(0x7fffec, 23); codes[176] = new HuffmanCode(0x1fffe0, 21); codes[177] = new HuffmanCode(0x1fffe1, 21); codes[178] = new HuffmanCode(0x3fffe0, 22); codes[179] = new HuffmanCode(0x1fffe2, 21); codes[180] = new HuffmanCode(0x7fffed, 23); codes[181] = new HuffmanCode(0x3fffe1, 22); codes[182] = new HuffmanCode(0x7fffee, 23); codes[183] = new HuffmanCode(0x7fffef, 23); codes[184] = new HuffmanCode(0xfffea, 20); codes[185] = new HuffmanCode(0x3fffe2, 22); codes[186] = new HuffmanCode(0x3fffe3, 22); codes[187] = new HuffmanCode(0x3fffe4, 22); codes[188] = new HuffmanCode(0x7ffff0, 23); codes[189] = new HuffmanCode(0x3fffe5, 22); codes[190] = new HuffmanCode(0x3fffe6, 22); codes[191] = new HuffmanCode(0x7ffff1, 23); codes[192] = new HuffmanCode(0x3ffffe0, 26); codes[193] = new HuffmanCode(0x3ffffe1, 26); codes[194] = new HuffmanCode(0xfffeb, 20); codes[195] = new HuffmanCode(0x7fff1, 19); codes[196] = new HuffmanCode(0x3fffe7, 22); codes[197] = new HuffmanCode(0x7ffff2, 23); codes[198] = new HuffmanCode(0x3fffe8, 22); codes[199] = new HuffmanCode(0x1ffffec, 25); codes[200] = new HuffmanCode(0x3ffffe2, 26); codes[201] = new HuffmanCode(0x3ffffe3, 26); codes[202] = new HuffmanCode(0x3ffffe4, 26); codes[203] = new HuffmanCode(0x7ffffde, 27); codes[204] = new HuffmanCode(0x7ffffdf, 27); codes[205] = new HuffmanCode(0x3ffffe5, 26); codes[206] = new HuffmanCode(0xfffff1, 24); codes[207] = new HuffmanCode(0x1ffffed, 25); codes[208] = new HuffmanCode(0x7fff2, 19); codes[209] = new HuffmanCode(0x1fffe3, 21); codes[210] = new HuffmanCode(0x3ffffe6, 26); codes[211] = new HuffmanCode(0x7ffffe0, 27); codes[212] = new HuffmanCode(0x7ffffe1, 27); codes[213] = new HuffmanCode(0x3ffffe7, 26); codes[214] = new HuffmanCode(0x7ffffe2, 27); codes[215] = new HuffmanCode(0xfffff2, 24); codes[216] = new HuffmanCode(0x1fffe4, 21); codes[217] = new HuffmanCode(0x1fffe5, 21); codes[218] = new HuffmanCode(0x3ffffe8, 26); codes[219] = new HuffmanCode(0x3ffffe9, 26); codes[220] = new HuffmanCode(0xffffffd, 28); codes[221] = new HuffmanCode(0x7ffffe3, 27); codes[222] = new HuffmanCode(0x7ffffe4, 27); codes[223] = new HuffmanCode(0x7ffffe5, 27); codes[224] = new HuffmanCode(0xfffec, 20); codes[225] = new HuffmanCode(0xfffff3, 24); codes[226] = new HuffmanCode(0xfffed, 20); codes[227] = new HuffmanCode(0x1fffe6, 21); codes[228] = new HuffmanCode(0x3fffe9, 22); codes[229] = new HuffmanCode(0x1fffe7, 21); codes[230] = new HuffmanCode(0x1fffe8, 21); codes[231] = new HuffmanCode(0x7ffff3, 23); codes[232] = new HuffmanCode(0x3fffea, 22); codes[233] = new HuffmanCode(0x3fffeb, 22); codes[234] = new HuffmanCode(0x1ffffee, 25); codes[235] = new HuffmanCode(0x1ffffef, 25); codes[236] = new HuffmanCode(0xfffff4, 24); codes[237] = new HuffmanCode(0xfffff5, 24); codes[238] = new HuffmanCode(0x3ffffea, 26); codes[239] = new HuffmanCode(0x7ffff4, 23); codes[240] = new HuffmanCode(0x3ffffeb, 26); codes[241] = new HuffmanCode(0x7ffffe6, 27); codes[242] = new HuffmanCode(0x3ffffec, 26); codes[243] = new HuffmanCode(0x3ffffed, 26); codes[244] = new HuffmanCode(0x7ffffe7, 27); codes[245] = new HuffmanCode(0x7ffffe8, 27); codes[246] = new HuffmanCode(0x7ffffe9, 27); codes[247] = new HuffmanCode(0x7ffffea, 27); codes[248] = new HuffmanCode(0x7ffffeb, 27); codes[249] = new HuffmanCode(0xffffffe, 28); codes[250] = new HuffmanCode(0x7ffffec, 27); codes[251] = new HuffmanCode(0x7ffffed, 27); codes[252] = new HuffmanCode(0x7ffffee, 27); codes[253] = new HuffmanCode(0x7ffffef, 27); codes[254] = new HuffmanCode(0x7fffff0, 27); codes[255] = new HuffmanCode(0x3ffffee, 26); codes[256] = new HuffmanCode(0x3fffffff, 30); HUFFMAN_CODES = codes; //lengths determined by experimentation, just set it to something large then see how large it actually ends up int[] codingTree = new int[256]; //the current position in the tree int pos = 0; int allocated = 1; //the next position to allocate to //map of the current state at a given position //only used while building the tree HuffmanCode[] currentCode = new HuffmanCode[256]; currentCode[0] = new HuffmanCode(0, 0); final Set allCodes = new HashSet<>(); allCodes.addAll(Arrays.asList(HUFFMAN_CODES)); while (!allCodes.isEmpty()) { int length = currentCode[pos].length; int code = currentCode[pos].value; int newLength = length + 1; HuffmanCode high = new HuffmanCode(code << 1 | 1, newLength); HuffmanCode low = new HuffmanCode(code << 1, newLength); int newVal = 0; boolean highTerminal = allCodes.remove(high); if (highTerminal) { //bah, linear search int i = 0; for (i = 0; i < codes.length; ++i) { if (codes[i].equals(high)) { break; } } newVal = LOW_TERMINAL_BIT | i; } else { int highPos = allocated++; currentCode[highPos] = high; newVal = highPos; } newVal <<= 16; boolean lowTerminal = allCodes.remove(low); if (lowTerminal) { //bah, linear search int i = 0; for (i = 0; i < codes.length; ++i) { if (codes[i].equals(low)) { break; } } newVal |= LOW_TERMINAL_BIT | i; } else { int lowPos = allocated++; currentCode[lowPos] = low; newVal |= lowPos; } codingTree[pos] = newVal; pos++; } DECODING_TABLE = codingTree; } /** * Decodes a huffman encoded string into the target StringBuilder. There must be enough space left in the buffer * for this method to succeed. * * @param data The byte buffer * @param length The data length * @param target The target for the decompressed data */ public static void decode(ByteBuffer data, int length, StringBuilder target) throws HpackException { assert data.remaining() >= length; int treePos = 0; boolean eosBits = true; int eosCount = 0; for (int i = 0; i < length; ++i) { byte b = data.get(); int bitPos = 7; while (bitPos >= 0) { int val = DECODING_TABLE[treePos]; if (((1 << bitPos) & b) == 0) { //bit not set, we want the lower part of the tree if ((val & LOW_TERMINAL_BIT) == 0) { treePos = val & LOW_MASK; eosBits = false; eosCount = 0; } else { target.append((char) (val & LOW_MASK)); treePos = 0; eosBits = true; eosCount = 0; } } else { //bit not set, we want the lower part of the tree if ((val & HIGH_TERMINAL_BIT) == 0) { treePos = (val >> 16) & LOW_MASK; if(eosBits) { eosCount++; } } else { target.append((char) ((val >> 16) & LOW_MASK)); treePos = 0; eosCount = 0; eosBits = true; } } bitPos--; } } if (!eosBits || eosCount > 7) { throw UndertowMessages.MESSAGES.huffmanEncodedHpackValueDidNotEndWithEOS(); } } /** * Encodes the given string into the buffer. If there is not enough space in the buffer, or the encoded * version is bigger than the original it will return false and not modify the buffers position * * @param buffer The buffer to encode into * @param toEncode The string to encode * @param forceLowercase If the string should be encoded in lower case * @return true if encoding succeeded */ public static boolean encode(ByteBuffer buffer, String toEncode, boolean forceLowercase) { if (buffer.remaining() <= toEncode.length()) { return false; } int start = buffer.position(); //this sucks, but we need to put the length first //and we don't really have any option but to calculate it in advance to make sure we have left enough room //so we end up iterating twice int length = 0; for (int i = 0; i < toEncode.length(); ++i) { byte c = (byte) toEncode.charAt(i); if(forceLowercase) { c = Hpack.toLower(c); } HuffmanCode code = HUFFMAN_CODES[c]; length += code.length; } int byteLength = length / 8 + (length % 8 == 0 ? 0 : 1); buffer.put((byte) (1 << 7)); Hpack.encodeInteger(buffer, byteLength, 7); int bytePos = 0; byte currentBufferByte = 0; for (int i = 0; i < toEncode.length(); ++i) { byte c = (byte) toEncode.charAt(i); if(forceLowercase) { c = Hpack.toLower(c); } HuffmanCode code = HUFFMAN_CODES[c]; if (code.length + bytePos <= 8) { //it fits in the current byte currentBufferByte |= ((code.value & 0xFF) << 8 - (code.length + bytePos)); bytePos += code.length; } else { //it does not fit, it may need up to 4 bytes int val = code.value; int rem = code.length; while (rem > 0) { if (!buffer.hasRemaining()) { buffer.position(start); return false; } int remainingInByte = 8 - bytePos; if (rem > remainingInByte) { currentBufferByte |= (val >> (rem - remainingInByte)); } else { currentBufferByte |= (val << (remainingInByte - rem)); } if (rem > remainingInByte) { buffer.put(currentBufferByte); currentBufferByte = 0; bytePos = 0; } else { bytePos = rem; } rem -= remainingInByte; } } if (bytePos == 8) { if (!buffer.hasRemaining()) { buffer.position(start); return false; } buffer.put(currentBufferByte); currentBufferByte = 0; bytePos = 0; } if (buffer.position() - start > toEncode.length()) { //the encoded version is longer than the original //just return false buffer.position(start); return false; } } if (bytePos > 0) { //add the EOS bytes if we have not finished on a single byte if (!buffer.hasRemaining()) { buffer.position(start); return false; } buffer.put((byte) (currentBufferByte | ((0xFF) >> bytePos))); } return true; } protected static class HuffmanCode { /** * The value of the least significan't bits of the code */ int value; /** * length of the code, in bits */ int length; public HuffmanCode(int value, int length) { this.value = value; this.length = length; } public int getValue() { return value; } public int getLength() { return length; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; HuffmanCode that = (HuffmanCode) o; if (length != that.length) return false; if (value != that.value) return false; return true; } @Override public int hashCode() { int result = value; result = 31 * result + length; return result; } @Override public String toString() { return "HuffmanCode{" + "value=" + value + ", length=" + length + '}'; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Hpack.java000066400000000000000000000231101420065311100273070ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.nio.ByteBuffer; import io.undertow.UndertowMessages; import io.undertow.util.HttpString; /** * @author Stuart Douglas */ final class Hpack { private static final byte LOWER_DIFF = 'a' - 'A'; static final int DEFAULT_TABLE_SIZE = 4096; private static final int MAX_INTEGER_OCTETS = 8; //not sure what a good value for this is, but the spec says we need to provide an upper bound /** * table that contains powers of two, * used as both bitmask and to quickly calculate 2^n */ private static final int[] PREFIX_TABLE; static final HeaderField[] STATIC_TABLE; static final int STATIC_TABLE_LENGTH; static { PREFIX_TABLE = new int[32]; for (int i = 0; i < 32; ++i) { int n = 0; for (int j = 0; j < i; ++j) { n = n << 1; n |= 1; } PREFIX_TABLE[i] = n; } HeaderField[] fields = new HeaderField[62]; //note that zero is not used fields[1] = new HeaderField(new HttpString(":authority"), null); fields[2] = new HeaderField(new HttpString(":method"), "GET"); fields[3] = new HeaderField(new HttpString(":method"), "POST"); fields[4] = new HeaderField(new HttpString(":path"), "/"); fields[5] = new HeaderField(new HttpString(":path"), "/index.html"); fields[6] = new HeaderField(new HttpString(":scheme"), "http"); fields[7] = new HeaderField(new HttpString(":scheme"), "https"); fields[8] = new HeaderField(new HttpString(":status"), "200"); fields[9] = new HeaderField(new HttpString(":status"), "204"); fields[10] = new HeaderField(new HttpString(":status"), "206"); fields[11] = new HeaderField(new HttpString(":status"), "304"); fields[12] = new HeaderField(new HttpString(":status"), "400"); fields[13] = new HeaderField(new HttpString(":status"), "404"); fields[14] = new HeaderField(new HttpString(":status"), "500"); fields[15] = new HeaderField(new HttpString("accept-charset"), null); fields[16] = new HeaderField(new HttpString("accept-encoding"), "gzip, deflate"); fields[17] = new HeaderField(new HttpString("accept-language"), null); fields[18] = new HeaderField(new HttpString("accept-ranges"), null); fields[19] = new HeaderField(new HttpString("accept"), null); fields[20] = new HeaderField(new HttpString("access-control-allow-origin"), null); fields[21] = new HeaderField(new HttpString("age"), null); fields[22] = new HeaderField(new HttpString("allow"), null); fields[23] = new HeaderField(new HttpString("authorization"), null); fields[24] = new HeaderField(new HttpString("cache-control"), null); fields[25] = new HeaderField(new HttpString("content-disposition"), null); fields[26] = new HeaderField(new HttpString("content-encoding"), null); fields[27] = new HeaderField(new HttpString("content-language"), null); fields[28] = new HeaderField(new HttpString("content-length"), null); fields[29] = new HeaderField(new HttpString("content-location"), null); fields[30] = new HeaderField(new HttpString("content-range"), null); fields[31] = new HeaderField(new HttpString("content-type"), null); fields[32] = new HeaderField(new HttpString("cookie"), null); fields[33] = new HeaderField(new HttpString("date"), null); fields[34] = new HeaderField(new HttpString("etag"), null); fields[35] = new HeaderField(new HttpString("expect"), null); fields[36] = new HeaderField(new HttpString("expires"), null); fields[37] = new HeaderField(new HttpString("from"), null); fields[38] = new HeaderField(new HttpString("host"), null); fields[39] = new HeaderField(new HttpString("if-match"), null); fields[40] = new HeaderField(new HttpString("if-modified-since"), null); fields[41] = new HeaderField(new HttpString("if-none-match"), null); fields[42] = new HeaderField(new HttpString("if-range"), null); fields[43] = new HeaderField(new HttpString("if-unmodified-since"), null); fields[44] = new HeaderField(new HttpString("last-modified"), null); fields[45] = new HeaderField(new HttpString("link"), null); fields[46] = new HeaderField(new HttpString("location"), null); fields[47] = new HeaderField(new HttpString("max-forwards"), null); fields[48] = new HeaderField(new HttpString("proxy-authenticate"), null); fields[49] = new HeaderField(new HttpString("proxy-authorization"), null); fields[50] = new HeaderField(new HttpString("range"), null); fields[51] = new HeaderField(new HttpString("referer"), null); fields[52] = new HeaderField(new HttpString("refresh"), null); fields[53] = new HeaderField(new HttpString("retry-after"), null); fields[54] = new HeaderField(new HttpString("server"), null); fields[55] = new HeaderField(new HttpString("set-cookie"), null); fields[56] = new HeaderField(new HttpString("strict-transport-security"), null); fields[57] = new HeaderField(new HttpString("transfer-encoding"), null); fields[58] = new HeaderField(new HttpString("user-agent"), null); fields[59] = new HeaderField(new HttpString("vary"), null); fields[60] = new HeaderField(new HttpString("via"), null); fields[61] = new HeaderField(new HttpString("www-authenticate"), null); STATIC_TABLE = fields; STATIC_TABLE_LENGTH = STATIC_TABLE.length - 1; } static class HeaderField { final HttpString name; final String value; final int size; HeaderField(HttpString name, String value) { this.name = name; this.value = value; if (value != null) { this.size = 32 + name.length() + value.length(); } else { this.size = -1; } } } /** * Decodes an integer in the HPACK prefex format. If the return value is -1 * it means that there was not enough data in the buffer to complete the decoding * sequence. *

* If this method returns -1 then the source buffer will not have been modified. * * @param source The buffer that contains the integer * @param n The encoding prefix length * @return The encoded integer, or -1 if there was not enough data */ static int decodeInteger(ByteBuffer source, int n) throws HpackException { if (source.remaining() == 0) { return -1; } if(n >= PREFIX_TABLE.length) { throw UndertowMessages.MESSAGES.integerEncodedOverTooManyOctets(MAX_INTEGER_OCTETS); } int count = 1; int sp = source.position(); int mask = PREFIX_TABLE[n]; int i = mask & source.get(); int b; if (i < PREFIX_TABLE[n]) { return i; } else { int m = 0; do { if(count++ > MAX_INTEGER_OCTETS) { throw UndertowMessages.MESSAGES.integerEncodedOverTooManyOctets(MAX_INTEGER_OCTETS); } if (source.remaining() == 0) { //we have run out of data //reset source.position(sp); return -1; } if(m >= PREFIX_TABLE.length) { throw UndertowMessages.MESSAGES.integerEncodedOverTooManyOctets(MAX_INTEGER_OCTETS); } b = source.get(); i = i + (b & 127) * (PREFIX_TABLE[m] + 1); m += 7; } while ((b & 128) == 128); } return i; } /** * Encodes an integer in the HPACK prefix format. *

* This method assumes that the buffer has already had the first 8-n bits filled. * As such it will modify the last byte that is already present in the buffer, and * potentially add more if required * * @param source The buffer that contains the integer * @param value The integer to encode * @param n The encoding prefix length */ static void encodeInteger(ByteBuffer source, int value, int n) { int twoNminus1 = PREFIX_TABLE[n]; int pos = source.position() - 1; if (value < twoNminus1) { source.put(pos, (byte) (source.get(pos) | value)); } else { source.put(pos, (byte) (source.get(pos) | twoNminus1)); value = value - twoNminus1; while (value >= 128) { source.put((byte) (value % 128 + 128)); value = value / 128; } source.put((byte) value); } } static byte toLower(byte b) { if (b >= 'A' && b <= 'Z') { return (byte) (b + LOWER_DIFF); } return b; } private Hpack() {} } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/HpackDecoder.java000066400000000000000000000347321420065311100306110ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import static io.undertow.protocols.http2.Hpack.HeaderField; import java.nio.ByteBuffer; import io.undertow.UndertowMessages; import io.undertow.util.HttpString; /** * A decoder for HPACK. * * @author Stuart Douglas */ public class HpackDecoder { private static final int DEFAULT_RING_BUFFER_SIZE = 10; /** * The object that receives the headers that are emitted from this decoder */ private HeaderEmitter headerEmitter; /** * The header table */ private HeaderField[] headerTable; /** * The current HEAD position of the header table. We use a ring buffer type * construct as it would be silly to actually shuffle the items around in the * array. */ private int firstSlotPosition = 0; /** * The current table size by index (aka the number of index positions that are filled up) */ private int filledTableSlots = 0; /** * the current calculates memory size, as per the HPACK algorithm */ private int currentMemorySize = 0; /** * The current memory size, as specified by the client */ private int specifiedMemorySize; /** * The maximum allowed memory size, as specified by us. If the client tries to increase beyond this amount it is an error */ private final int maxAllowedMemorySize; private boolean first = true; private final StringBuilder stringBuilder = new StringBuilder(); public HpackDecoder(int maxAllowedMemorySize) { this.specifiedMemorySize = Math.min(Hpack.DEFAULT_TABLE_SIZE, maxAllowedMemorySize); this.maxAllowedMemorySize = maxAllowedMemorySize; headerTable = new HeaderField[DEFAULT_RING_BUFFER_SIZE]; } public HpackDecoder() { this(Hpack.DEFAULT_TABLE_SIZE); } /** * Decodes the provided frame data. If this method leaves data in the buffer then * this buffer should be compacted so this data is preserved, unless there is no * more data in which case this should be considered a protocol error. * * @param buffer The buffer */ public void decode(ByteBuffer buffer, boolean moreData) throws HpackException { while (buffer.hasRemaining()) { int originalPos = buffer.position(); byte b = buffer.get(); if ((b & 0b10000000) != 0) { first = false; //if the first bit is set it is an indexed header field buffer.position(buffer.position() - 1); //unget the byte int index = Hpack.decodeInteger(buffer, 7); //prefix is 7 if (index == -1) { if(!moreData) { throw UndertowMessages.MESSAGES.hpackFailed(); } buffer.position(originalPos); return; } else if(index == 0) { throw UndertowMessages.MESSAGES.zeroNotValidHeaderTableIndex(); } handleIndex(index); } else if ((b & 0b01000000) != 0) { first = false; //Literal Header Field with Incremental Indexing HttpString headerName = readHeaderName(buffer, 6); if (headerName == null) { if(!moreData) { throw UndertowMessages.MESSAGES.hpackFailed(); } buffer.position(originalPos); return; } String headerValue = readHpackString(buffer); if (headerValue == null) { if(!moreData) { throw UndertowMessages.MESSAGES.hpackFailed(); } buffer.position(originalPos); return; } headerEmitter.emitHeader(headerName, headerValue, false); addEntryToHeaderTable(new HeaderField(headerName, headerValue)); } else if ((b & 0b11110000) == 0) { first = false; //Literal Header Field without Indexing HttpString headerName = readHeaderName(buffer, 4); if (headerName == null) { if(!moreData) { throw UndertowMessages.MESSAGES.hpackFailed(); } buffer.position(originalPos); return; } String headerValue = readHpackString(buffer); if (headerValue == null) { if(!moreData) { throw UndertowMessages.MESSAGES.hpackFailed(); } buffer.position(originalPos); return; } headerEmitter.emitHeader(headerName, headerValue, false); } else if ((b & 0b11110000) == 0b00010000) { first = false; //Literal Header Field never indexed HttpString headerName = readHeaderName(buffer, 4); if (headerName == null) { buffer.position(originalPos); return; } String headerValue = readHpackString(buffer); if (headerValue == null) { if(!moreData) { throw UndertowMessages.MESSAGES.hpackFailed(); } buffer.position(originalPos); return; } headerEmitter.emitHeader(headerName, headerValue, true); } else if ((b & 0b11100000) == 0b00100000) { if(!first) { throw new HpackException(); } //context update max table size change if (!handleMaxMemorySizeChange(buffer, originalPos)) { return; } } else { throw UndertowMessages.MESSAGES.invalidHpackEncoding(b); } } if(!moreData) { first = true; } } private boolean handleMaxMemorySizeChange(ByteBuffer buffer, int originalPos) throws HpackException { buffer.position(buffer.position() - 1); //unget the byte int size = Hpack.decodeInteger(buffer, 5); if (size == -1) { buffer.position(originalPos); return false; } if(size > maxAllowedMemorySize) { throw new HpackException(Http2Channel.ERROR_PROTOCOL_ERROR); } specifiedMemorySize = size; if (currentMemorySize > specifiedMemorySize) { int newTableSlots = filledTableSlots; int tableLength = headerTable.length; int newSize = currentMemorySize; while (newSize > specifiedMemorySize) { int clearIndex = firstSlotPosition; firstSlotPosition++; if (firstSlotPosition == tableLength) { firstSlotPosition = 0; } HeaderField oldData = headerTable[clearIndex]; headerTable[clearIndex] = null; newSize -= oldData.size; newTableSlots--; } this.filledTableSlots = newTableSlots; currentMemorySize = newSize; } return true; } private HttpString readHeaderName(ByteBuffer buffer, int prefixLength) throws HpackException { buffer.position(buffer.position() - 1); //unget the byte int index = Hpack.decodeInteger(buffer, prefixLength); if (index == -1) { return null; } else if (index != 0) { return handleIndexedHeaderName(index); } else { String string = readHpackString(buffer); if (string == null) { return null; } else if (string.isEmpty()) { //don't allow empty header names throw new HpackException(); } return new HttpString(string); } } private String readHpackString(ByteBuffer buffer) throws HpackException { if (!buffer.hasRemaining()) { return null; } byte data = buffer.get(buffer.position()); int length = Hpack.decodeInteger(buffer, 7); if (buffer.remaining() < length || length == -1) { return null; } boolean huffman = (data & 0b10000000) != 0; if (huffman) { return readHuffmanString(length, buffer); } for (int i = 0; i < length; ++i) { stringBuilder.append((char) buffer.get()); } String ret = stringBuilder.toString(); stringBuilder.setLength(0); if (ret.isEmpty()) { //return the interned empty string, rather than allocating a new one each time return ""; } return ret; } private String readHuffmanString(int length, ByteBuffer buffer) throws HpackException { HPackHuffman.decode(buffer, length, stringBuilder); String ret = stringBuilder.toString(); if (ret.isEmpty()) { return ""; } stringBuilder.setLength(0); return ret; } private HttpString handleIndexedHeaderName(int index) throws HpackException { if (index <= Hpack.STATIC_TABLE_LENGTH) { return Hpack.STATIC_TABLE[index].name; } else { if (index > Hpack.STATIC_TABLE_LENGTH + filledTableSlots) { throw new HpackException(); } int adjustedIndex = getRealIndex(index - Hpack.STATIC_TABLE_LENGTH); HeaderField res = headerTable[adjustedIndex]; if (res == null) { throw new HpackException(); } return res.name; } } /** * Handle an indexed header representation * * @param index The index * @throws HpackException */ private void handleIndex(int index) throws HpackException { if (index <= Hpack.STATIC_TABLE_LENGTH) { addStaticTableEntry(index); } else { int adjustedIndex = getRealIndex(index - Hpack.STATIC_TABLE_LENGTH); HeaderField headerField = headerTable[adjustedIndex]; headerEmitter.emitHeader(headerField.name, headerField.value, false); } } /** * because we use a ring buffer type construct, and don't actually shuffle * items in the array, we need to figure out he real index to use. *

* package private for unit tests * * @param index The index from the hpack * @return the real index into the array */ int getRealIndex(int index) throws HpackException { //the index is one based, but our table is zero based, hence -1 //also because of our ring buffer setup the indexes are reversed //index = 1 is at position firstSlotPosition + filledSlots int newIndex = (firstSlotPosition + (filledTableSlots - index)) % headerTable.length; if(newIndex < 0) { throw UndertowMessages.MESSAGES.invalidHpackIndex(index); } return newIndex; } private void addStaticTableEntry(int index) throws HpackException { //adds an entry from the static table. HeaderField entry = Hpack.STATIC_TABLE[index]; headerEmitter.emitHeader(entry.name, entry.value == null ? "" : entry.value, false); } private void addEntryToHeaderTable(HeaderField entry) { if (entry.size > specifiedMemorySize) { //it is to big to fit, so we just completely clear the table. while (filledTableSlots > 0) { headerTable[firstSlotPosition] = null; firstSlotPosition++; if (firstSlotPosition == headerTable.length) { firstSlotPosition = 0; } filledTableSlots--; } currentMemorySize = 0; return; } resizeIfRequired(); int newTableSlots = filledTableSlots + 1; int tableLength = headerTable.length; int index = (firstSlotPosition + filledTableSlots) % tableLength; headerTable[index] = entry; int newSize = currentMemorySize + entry.size; while (newSize > specifiedMemorySize) { int clearIndex = firstSlotPosition; firstSlotPosition++; if (firstSlotPosition == tableLength) { firstSlotPosition = 0; } HeaderField oldData = headerTable[clearIndex]; headerTable[clearIndex] = null; newSize -= oldData.size; newTableSlots--; } this.filledTableSlots = newTableSlots; currentMemorySize = newSize; } private void resizeIfRequired() { if(filledTableSlots == headerTable.length) { HeaderField[] newArray = new HeaderField[headerTable.length + 10]; //we only grow slowly for(int i = 0; i < headerTable.length; ++i) { newArray[i] = headerTable[(firstSlotPosition + i) % headerTable.length]; } firstSlotPosition = 0; headerTable = newArray; } } public interface HeaderEmitter { void emitHeader(HttpString name, String value, boolean neverIndex) throws HpackException; } public HeaderEmitter getHeaderEmitter() { return headerEmitter; } public void setHeaderEmitter(HeaderEmitter headerEmitter) { this.headerEmitter = headerEmitter; } //package private fields for unit tests int getFirstSlotPosition() { return firstSlotPosition; } HeaderField[] getHeaderTable() { return headerTable; } int getFilledTableSlots() { return filledTableSlots; } int getCurrentMemorySize() { return currentMemorySize; } int getSpecifiedMemorySize() { return specifiedMemorySize; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/HpackEncoder.java000066400000000000000000000370361420065311100306230ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import io.undertow.util.HeaderMap; import io.undertow.util.HeaderValues; import io.undertow.util.Headers; import io.undertow.util.HttpString; import java.nio.ByteBuffer; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import static io.undertow.protocols.http2.Hpack.HeaderField; import static io.undertow.protocols.http2.Hpack.STATIC_TABLE; import static io.undertow.protocols.http2.Hpack.STATIC_TABLE_LENGTH; import static io.undertow.protocols.http2.Hpack.encodeInteger; /** * Encoder for HPACK frames. * * @author Stuart Douglas */ public class HpackEncoder { private static final Set SKIP; static { Set set = new HashSet<>(); set.add(Headers.CONNECTION); set.add(Headers.TRANSFER_ENCODING); set.add(Headers.KEEP_ALIVE); set.add(Headers.UPGRADE); SKIP = Collections.unmodifiableSet(set); } public static final HpackHeaderFunction DEFAULT_HEADER_FUNCTION = new HpackHeaderFunction() { @Override public boolean shouldUseIndexing(HttpString headerName, String value) { //content length and date change all the time //no need to index them, or they will churn the table return !headerName.equals(Headers.CONTENT_LENGTH) && !headerName.equals(Headers.DATE); } @Override public boolean shouldUseHuffman(HttpString header, String value) { return value.length() > 10; //TODO: figure out a good value for this } @Override public boolean shouldUseHuffman(HttpString header) { return header.length() > 10; //TODO: figure out a good value for this } }; private long headersIterator = -1; private boolean firstPass = true; private HeaderMap currentHeaders; private int entryPositionCounter; private int newMaxHeaderSize = -1; //if the max header size has been changed private int minNewMaxHeaderSize = -1; //records the smallest value of newMaxHeaderSize, as per section 4.1 private static final Map ENCODING_STATIC_TABLE; private final Deque evictionQueue = new ArrayDeque<>(); private final Map> dynamicTable = new HashMap<>(); private byte[] overflowData; private int overflowPos; private int overflowLength; static { Map map = new HashMap<>(); for (int i = 1; i < STATIC_TABLE.length; ++i) { HeaderField m = STATIC_TABLE[i]; TableEntry[] existing = map.get(m.name); if (existing == null) { map.put(m.name, new TableEntry[]{new TableEntry(m.name, m.value, i)}); } else { TableEntry[] newEntry = new TableEntry[existing.length + 1]; System.arraycopy(existing, 0, newEntry, 0, existing.length); newEntry[existing.length] = new TableEntry(m.name, m.value, i); map.put(m.name, newEntry); } } ENCODING_STATIC_TABLE = Collections.unmodifiableMap(map); } /** * The maximum table size */ private int maxTableSize; /** * The current table size */ private int currentTableSize; private final HpackHeaderFunction hpackHeaderFunction; public HpackEncoder(int maxTableSize, HpackHeaderFunction headerFunction) { this.maxTableSize = maxTableSize; this.hpackHeaderFunction = headerFunction; } public HpackEncoder(int maxTableSize) { this(maxTableSize, DEFAULT_HEADER_FUNCTION); } /** * Encodes the headers into a buffer. * * @param headers * @param target */ public State encode(HeaderMap headers, ByteBuffer target) { if(overflowData != null) { for(int i = overflowPos; i < overflowLength; ++i) { if(!target.hasRemaining()) { overflowPos = i; return State.OVERFLOW; } target.put(overflowData[i]); } overflowData = null; } long it = headersIterator; if (headersIterator == -1) { handleTableSizeChange(target); //new headers map it = headers.fastIterate(); currentHeaders = headers; } else { if (headers != currentHeaders) { throw new IllegalStateException(); } it = headers.fiNext(it); } while (it != -1) { HeaderValues values = headers.fiCurrent(it); boolean skip = false; if (firstPass) { if (values.getHeaderName().byteAt(0) != ':') { skip = true; } } else { if (values.getHeaderName().byteAt(0) == ':') { skip = true; } } if(SKIP.contains(values.getHeaderName())) { //ignore connection specific headers skip = true; } if (!skip) { for (int i = 0; i < values.size(); ++i) { HttpString headerName = values.getHeaderName(); int required = 11 + headerName.length(); //we use 11 to make sure we have enough room for the variable length itegers String val = values.get(i); for(int v = 0; v < val.length(); ++v) { char c = val.charAt(v); if(c == '\r' || c == '\n') { val = val.replace('\r', ' ').replace('\n', ' '); break; } } TableEntry tableEntry = findInTable(headerName, val); required += (1 + val.length()); boolean overflowing = false; ByteBuffer current = target; if (current.remaining() < required) { overflowing = true; current = ByteBuffer.wrap(overflowData = new byte[required]); overflowPos = 0; } boolean canIndex = hpackHeaderFunction.shouldUseIndexing(headerName, val) && (headerName.length() + val.length() + 32) < maxTableSize; //only index if it will fit if (tableEntry == null && canIndex) { //add the entry to the dynamic table current.put((byte) (1 << 6)); writeHuffmanEncodableName(current, headerName); writeHuffmanEncodableValue(current, headerName, val); addToDynamicTable(headerName, val); } else if (tableEntry == null) { //literal never indexed current.put((byte) (1 << 4)); writeHuffmanEncodableName(current, headerName); writeHuffmanEncodableValue(current, headerName, val); } else { //so we know something is already in the table if (val.equals(tableEntry.value)) { //the whole thing is in the table current.put((byte) (1 << 7)); encodeInteger(current, tableEntry.getPosition(), 7); } else { if (canIndex) { //add the entry to the dynamic table current.put((byte) (1 << 6)); encodeInteger(current, tableEntry.getPosition(), 6); writeHuffmanEncodableValue(current, headerName, val); addToDynamicTable(headerName, val); } else { current.put((byte) (1 << 4)); encodeInteger(current, tableEntry.getPosition(), 4); writeHuffmanEncodableValue(current, headerName, val); } } } if(overflowing) { this.headersIterator = it; this.overflowLength = current.position(); return State.OVERFLOW; } } } it = headers.fiNext(it); if (it == -1 && firstPass) { firstPass = false; it = headers.fastIterate(); } } headersIterator = -1; firstPass = true; return State.COMPLETE; } private void writeHuffmanEncodableName(ByteBuffer target, HttpString headerName) { if (hpackHeaderFunction.shouldUseHuffman(headerName)) { if(HPackHuffman.encode(target, headerName.toString(), true)) { return; } } target.put((byte) 0); //to use encodeInteger we need to place the first byte in the buffer. encodeInteger(target, headerName.length(), 7); for (int j = 0; j < headerName.length(); ++j) { target.put(Hpack.toLower(headerName.byteAt(j))); } } private void writeHuffmanEncodableValue(ByteBuffer target, HttpString headerName, String val) { if (hpackHeaderFunction.shouldUseHuffman(headerName, val)) { if (!HPackHuffman.encode(target, val, false)) { writeValueString(target, val); } } else { writeValueString(target, val); } } private void writeValueString(ByteBuffer target, String val) { target.put((byte) 0); //to use encodeInteger we need to place the first byte in the buffer. encodeInteger(target, val.length(), 7); for (int j = 0; j < val.length(); ++j) { target.put((byte) val.charAt(j)); } } private void addToDynamicTable(HttpString headerName, String val) { int pos = entryPositionCounter++; DynamicTableEntry d = new DynamicTableEntry(headerName, val, -pos); List existing = dynamicTable.get(headerName); if (existing == null) { dynamicTable.put(headerName, existing = new ArrayList<>(1)); } existing.add(d); evictionQueue.add(d); currentTableSize += d.size; runEvictionIfRequired(); if (entryPositionCounter == Integer.MAX_VALUE) { //prevent rollover preventPositionRollover(); } } private void preventPositionRollover() { //if the position counter is about to roll over we iterate all the table entries //and set their position to their actual position for (Map.Entry> entry : dynamicTable.entrySet()) { for (TableEntry t : entry.getValue()) { t.position = t.getPosition(); } } entryPositionCounter = 0; } private void runEvictionIfRequired() { while (currentTableSize > maxTableSize) { TableEntry next = evictionQueue.poll(); if (next == null) { return; } currentTableSize -= next.size; List list = dynamicTable.get(next.name); list.remove(next); if (list.isEmpty()) { dynamicTable.remove(next.name); } } } private TableEntry findInTable(HttpString headerName, String value) { TableEntry[] staticTable = ENCODING_STATIC_TABLE.get(headerName); if (staticTable != null) { for (TableEntry st : staticTable) { if (st.value != null && st.value.equals(value)) { //todo: some form of lookup? return st; } } } List dynamic = dynamicTable.get(headerName); if (dynamic != null) { for (int i = 0; i < dynamic.size(); ++i) { TableEntry st = dynamic.get(i); if (st.value.equals(value)) { //todo: some form of lookup? return st; } } } if (staticTable != null) { return staticTable[0]; } return null; } public void setMaxTableSize(int newSize) { this.newMaxHeaderSize = newSize; if (minNewMaxHeaderSize == -1) { minNewMaxHeaderSize = newSize; } else { minNewMaxHeaderSize = Math.min(newSize, minNewMaxHeaderSize); } } private void handleTableSizeChange(ByteBuffer target) { if (newMaxHeaderSize == -1) { return; } if (minNewMaxHeaderSize != newMaxHeaderSize) { target.put((byte) (1 << 5)); encodeInteger(target, minNewMaxHeaderSize, 5); } target.put((byte) (1 << 5)); encodeInteger(target, newMaxHeaderSize, 5); maxTableSize = newMaxHeaderSize; runEvictionIfRequired(); newMaxHeaderSize = -1; minNewMaxHeaderSize = -1; } public enum State { COMPLETE, OVERFLOW, } static class TableEntry { final HttpString name; final String value; final int size; int position; TableEntry(HttpString name, String value, int position) { this.name = name; this.value = value; this.position = position; if (value != null) { this.size = 32 + name.length() + value.length(); } else { this.size = -1; } } public int getPosition() { return position; } } class DynamicTableEntry extends TableEntry { DynamicTableEntry(HttpString name, String value, int position) { super(name, value, position); } @Override public int getPosition() { return super.getPosition() + entryPositionCounter + STATIC_TABLE_LENGTH; } } public interface HpackHeaderFunction { boolean shouldUseIndexing(HttpString header, String value); /** * Returns true if huffman encoding should be used on the header value * * @param header The header name * @param value The header value to be encoded * @return true if the value should be encoded */ boolean shouldUseHuffman(HttpString header, String value); /** * Returns true if huffman encoding should be used on the header name * * @param header The header name to be encoded * @return true if the value should be encoded */ boolean shouldUseHuffman(HttpString header); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/HpackException.java000066400000000000000000000024741420065311100312000ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; /** * Exception that is thrown when the HPACK compress context is broken. *

* In this case the connection must be closed. */ public class HpackException extends Exception { private final int closeCode; public HpackException() { this(null, Http2Channel.ERROR_COMPRESSION_ERROR); } public HpackException(String message, int closeCode) { super(message); this.closeCode = closeCode; } public HpackException(int closeCode) { this.closeCode = closeCode; } public int getCloseCode() { return closeCode; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2Channel.java000066400000000000000000001473571420065311100305770ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.UndertowOptions; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.protocol.ParseTimeoutUpdater; import io.undertow.server.protocol.framed.AbstractFramedChannel; import io.undertow.server.protocol.framed.AbstractFramedStreamSourceChannel; import io.undertow.server.protocol.framed.FrameHeaderData; import io.undertow.server.protocol.http2.Http2OpenListener; import io.undertow.util.Attachable; import io.undertow.util.AttachmentKey; import io.undertow.util.AttachmentList; import io.undertow.util.HeaderMap; import io.undertow.util.HttpString; import org.xnio.Bits; import org.xnio.ChannelExceptionHandler; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.OptionMap; import org.xnio.StreamConnection; import org.xnio.channels.StreamSinkChannel; import org.xnio.ssl.SslConnection; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Channel; import java.nio.channels.ClosedChannelException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import javax.net.ssl.SSLSession; /** * HTTP2 channel. * * @author Stuart Douglas */ public class Http2Channel extends AbstractFramedChannel implements Attachable { public static final String CLEARTEXT_UPGRADE_STRING = "h2c"; public static final HttpString METHOD = new HttpString(":method"); public static final HttpString PATH = new HttpString(":path"); public static final HttpString SCHEME = new HttpString(":scheme"); public static final HttpString AUTHORITY = new HttpString(":authority"); public static final HttpString STATUS = new HttpString(":status"); static final int FRAME_TYPE_DATA = 0x00; static final int FRAME_TYPE_HEADERS = 0x01; static final int FRAME_TYPE_PRIORITY = 0x02; static final int FRAME_TYPE_RST_STREAM = 0x03; static final int FRAME_TYPE_SETTINGS = 0x04; static final int FRAME_TYPE_PUSH_PROMISE = 0x05; static final int FRAME_TYPE_PING = 0x06; static final int FRAME_TYPE_GOAWAY = 0x07; static final int FRAME_TYPE_WINDOW_UPDATE = 0x08; static final int FRAME_TYPE_CONTINUATION = 0x09; public static final int ERROR_NO_ERROR = 0x00; public static final int ERROR_PROTOCOL_ERROR = 0x01; public static final int ERROR_INTERNAL_ERROR = 0x02; public static final int ERROR_FLOW_CONTROL_ERROR = 0x03; public static final int ERROR_SETTINGS_TIMEOUT = 0x04; public static final int ERROR_STREAM_CLOSED = 0x05; public static final int ERROR_FRAME_SIZE_ERROR = 0x06; public static final int ERROR_REFUSED_STREAM = 0x07; public static final int ERROR_CANCEL = 0x08; public static final int ERROR_COMPRESSION_ERROR = 0x09; public static final int ERROR_CONNECT_ERROR = 0x0a; public static final int ERROR_ENHANCE_YOUR_CALM = 0x0b; public static final int ERROR_INADEQUATE_SECURITY = 0x0c; static final int DATA_FLAG_END_STREAM = 0x1; static final int DATA_FLAG_END_SEGMENT = 0x2; static final int DATA_FLAG_PADDED = 0x8; static final int PING_FRAME_LENGTH = 8; static final int PING_FLAG_ACK = 0x1; static final int HEADERS_FLAG_END_STREAM = 0x1; static final int HEADERS_FLAG_END_SEGMENT = 0x2; static final int HEADERS_FLAG_END_HEADERS = 0x4; static final int HEADERS_FLAG_PADDED = 0x8; static final int HEADERS_FLAG_PRIORITY = 0x20; static final int SETTINGS_FLAG_ACK = 0x1; static final int CONTINUATION_FLAG_END_HEADERS = 0x4; public static final int DEFAULT_INITIAL_WINDOW_SIZE = 65535; static final byte[] PREFACE_BYTES = { 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a}; public static final int DEFAULT_MAX_FRAME_SIZE = 16384; public static final int MAX_FRAME_SIZE = 16777215; public static final int FLOW_CONTROL_MIN_WINDOW = 2; private Http2FrameHeaderParser frameParser; private final Map currentStreams = new ConcurrentHashMap<>(); private final String protocol; //local private final int encoderHeaderTableSize; private volatile boolean pushEnabled; private volatile int sendMaxConcurrentStreams = -1; private final int receiveMaxConcurrentStreams; private volatile int sendConcurrentStreams = 0; private volatile int receiveConcurrentStreams = 0; private final int initialReceiveWindowSize; private volatile int sendMaxFrameSize = DEFAULT_MAX_FRAME_SIZE; private final int receiveMaxFrameSize; private int unackedReceiveMaxFrameSize = DEFAULT_MAX_FRAME_SIZE; //the old max frame size, this gets updated when our setting frame is acked private final int maxHeaders; private final int maxHeaderListSize; private static final AtomicIntegerFieldUpdater sendConcurrentStreamsAtomicUpdater = AtomicIntegerFieldUpdater.newUpdater( Http2Channel.class, "sendConcurrentStreams"); private static final AtomicIntegerFieldUpdater receiveConcurrentStreamsAtomicUpdater = AtomicIntegerFieldUpdater.newUpdater( Http2Channel.class, "receiveConcurrentStreams"); private boolean thisGoneAway = false; private boolean peerGoneAway = false; private boolean lastDataRead = false; private int streamIdCounter; private int lastGoodStreamId; private int lastAssignedStreamOtherSide; private final HpackDecoder decoder; private final HpackEncoder encoder; private final int maxPadding; private final Random paddingRandom; private int prefaceCount; private boolean initialSettingsReceived; //settings frame must be the first frame we relieve private Http2HeadersParser continuationParser = null; //state for continuation frames /** * We send the settings frame lazily, which is basically a big hack to work around broken IE support for push (it * dies if you send a settings frame with push enabled). * * Once IE is no longer broken this should be removed. */ private boolean initialSettingsSent = false; private final Map, Object> attachments = Collections.synchronizedMap(new HashMap, Object>()); private final ParseTimeoutUpdater parseTimeoutUpdater; private final Object flowControlLock = new Object(); /** * The initial window size for newly created channels, guarded by {@link #flowControlLock} */ private volatile int initialSendWindowSize = DEFAULT_INITIAL_WINDOW_SIZE; /** * How much data we can send to the remote endpoint, at the connection level, guarded by {@link #flowControlLock} */ private volatile long sendWindowSize = initialSendWindowSize; /** * How much data we have told the remote endpoint we are prepared to accept, guarded by {@link #flowControlLock} */ private volatile int receiveWindowSize; public Http2Channel(StreamConnection connectedStreamChannel, String protocol, ByteBufferPool bufferPool, PooledByteBuffer data, boolean clientSide, boolean fromUpgrade, OptionMap settings) { this(connectedStreamChannel, protocol, bufferPool, data, clientSide, fromUpgrade, true, null, settings); } public Http2Channel(StreamConnection connectedStreamChannel, String protocol, ByteBufferPool bufferPool, PooledByteBuffer data, boolean clientSide, boolean fromUpgrade, boolean prefaceRequired, OptionMap settings) { this(connectedStreamChannel, protocol, bufferPool, data, clientSide, fromUpgrade, prefaceRequired, null, settings); } public Http2Channel(StreamConnection connectedStreamChannel, String protocol, ByteBufferPool bufferPool, PooledByteBuffer data, boolean clientSide, boolean fromUpgrade, boolean prefaceRequired, ByteBuffer initialOtherSideSettings, OptionMap settings) { super(connectedStreamChannel, bufferPool, new Http2FramePriority(clientSide ? (fromUpgrade ? 3 : 1) : 2), data, settings); streamIdCounter = clientSide ? (fromUpgrade ? 3 : 1) : 2; pushEnabled = settings.get(UndertowOptions.HTTP2_SETTINGS_ENABLE_PUSH, true); this.initialReceiveWindowSize = settings.get(UndertowOptions.HTTP2_SETTINGS_INITIAL_WINDOW_SIZE, DEFAULT_INITIAL_WINDOW_SIZE); this.receiveWindowSize = initialReceiveWindowSize; this.receiveMaxConcurrentStreams = settings.get(UndertowOptions.HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, -1); this.protocol = protocol == null ? Http2OpenListener.HTTP2 : protocol; this.maxHeaders = settings.get(UndertowOptions.MAX_HEADERS, clientSide ? -1 : UndertowOptions.DEFAULT_MAX_HEADERS); encoderHeaderTableSize = settings.get(UndertowOptions.HTTP2_SETTINGS_HEADER_TABLE_SIZE, Hpack.DEFAULT_TABLE_SIZE); receiveMaxFrameSize = settings.get(UndertowOptions.HTTP2_SETTINGS_MAX_FRAME_SIZE, DEFAULT_MAX_FRAME_SIZE); maxPadding = settings.get(UndertowOptions.HTTP2_PADDING_SIZE, 0); maxHeaderListSize = settings.get(UndertowOptions.HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE, settings.get(UndertowOptions.MAX_HEADER_SIZE, -1)); if(maxPadding > 0) { paddingRandom = new SecureRandom(); } else { paddingRandom = null; } this.decoder = new HpackDecoder(encoderHeaderTableSize); this.encoder = new HpackEncoder(encoderHeaderTableSize); if(!prefaceRequired) { prefaceCount = PREFACE_BYTES.length; } if (clientSide) { sendPreface(); prefaceCount = PREFACE_BYTES.length; sendSettings(); initialSettingsSent = true; if(fromUpgrade) { StreamHolder streamHolder = new StreamHolder((Http2StreamSinkChannel) null); streamHolder.sinkClosed = true; sendConcurrentStreamsAtomicUpdater.getAndIncrement(this); currentStreams.put(1, streamHolder); } } else if(fromUpgrade) { sendSettings(); initialSettingsSent = true; } if (initialOtherSideSettings != null) { Http2SettingsParser parser = new Http2SettingsParser(initialOtherSideSettings.remaining()); try { final Http2FrameHeaderParser headerParser = new Http2FrameHeaderParser(this, null); headerParser.length = initialOtherSideSettings.remaining(); parser.parse(initialOtherSideSettings, headerParser); updateSettings(parser.getSettings()); } catch (Throwable e) { IoUtils.safeClose(connectedStreamChannel); //should never happen throw new RuntimeException(e); } } int requestParseTimeout = settings.get(UndertowOptions.REQUEST_PARSE_TIMEOUT, -1); int requestIdleTimeout = settings.get(UndertowOptions.NO_REQUEST_TIMEOUT, -1); if(requestIdleTimeout < 0 && requestParseTimeout < 0) { this.parseTimeoutUpdater = null; } else { this.parseTimeoutUpdater = new ParseTimeoutUpdater(this, requestParseTimeout, requestIdleTimeout, new Runnable() { @Override public void run() { sendGoAway(ERROR_NO_ERROR); //just to make sure the connection is actually closed we give it 2 seconds //then we forcibly kill the connection getIoThread().executeAfter(new Runnable() { @Override public void run() { IoUtils.safeClose(Http2Channel.this); } }, 2, TimeUnit.SECONDS); } }); this.addCloseTask(new ChannelListener() { @Override public void handleEvent(Http2Channel channel) { parseTimeoutUpdater.close(); } }); } } private void sendSettings() { List settings = new ArrayList<>(); settings.add(new Http2Setting(Http2Setting.SETTINGS_HEADER_TABLE_SIZE, encoderHeaderTableSize)); if(isClient()) { settings.add(new Http2Setting(Http2Setting.SETTINGS_ENABLE_PUSH, pushEnabled ? 1 : 0)); } settings.add(new Http2Setting(Http2Setting.SETTINGS_MAX_FRAME_SIZE, receiveMaxFrameSize)); settings.add(new Http2Setting(Http2Setting.SETTINGS_INITIAL_WINDOW_SIZE, initialReceiveWindowSize)); if(maxHeaderListSize > 0) { settings.add(new Http2Setting(Http2Setting.SETTINGS_MAX_HEADER_LIST_SIZE, maxHeaderListSize)); } if(receiveMaxConcurrentStreams > 0) { settings.add(new Http2Setting(Http2Setting.SETTINGS_MAX_CONCURRENT_STREAMS, receiveMaxConcurrentStreams)); } Http2SettingsStreamSinkChannel stream = new Http2SettingsStreamSinkChannel(this, settings); flushChannelIgnoreFailure(stream); } private void sendSettingsAck() { if(!initialSettingsSent) { sendSettings(); initialSettingsSent = true; } Http2SettingsStreamSinkChannel stream = new Http2SettingsStreamSinkChannel(this); flushChannelIgnoreFailure(stream); } private void flushChannelIgnoreFailure(StreamSinkChannel stream) { try { flushChannel(stream); } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); } catch (Throwable t) { UndertowLogger.REQUEST_IO_LOGGER.handleUnexpectedFailure(t); } } private void flushChannel(StreamSinkChannel stream) throws IOException { stream.shutdownWrites(); if (!stream.flush()) { stream.getWriteSetter().set(ChannelListeners.flushingChannelListener(null, writeExceptionHandler())); stream.resumeWrites(); } } private void sendPreface() { Http2PrefaceStreamSinkChannel preface = new Http2PrefaceStreamSinkChannel(this); flushChannelIgnoreFailure(preface); } @Override protected AbstractHttp2StreamSourceChannel createChannel(FrameHeaderData frameHeaderData, PooledByteBuffer frameData) throws IOException { AbstractHttp2StreamSourceChannel channel = createChannelImpl(frameHeaderData, frameData); if(channel instanceof Http2StreamSourceChannel) { if (parseTimeoutUpdater != null) { if (channel != null) { parseTimeoutUpdater.requestStarted(); } else if (currentStreams.isEmpty()) { parseTimeoutUpdater.failedParse(); } } } return channel; } protected AbstractHttp2StreamSourceChannel createChannelImpl(FrameHeaderData frameHeaderData, PooledByteBuffer frameData) throws IOException { Http2FrameHeaderParser frameParser = (Http2FrameHeaderParser) frameHeaderData; AbstractHttp2StreamSourceChannel channel; if (frameParser.type == FRAME_TYPE_DATA) { //DATA frames must be already associated with a connection. If it gets here then something is wrong //spec explicitly calls this out as a connection error sendGoAway(ERROR_PROTOCOL_ERROR); UndertowLogger.REQUEST_LOGGER.tracef("Dropping Frame of length %s for stream %s", frameParser.getFrameLength(), frameParser.streamId); return null; } //note that not all frame types are covered here, as some are only relevant to already active streams //if which case they are handled by the existing channel support switch (frameParser.type) { case FRAME_TYPE_CONTINUATION: case FRAME_TYPE_PUSH_PROMISE: { //this is some 'clever' code to deal with both types continuation (push_promise and headers) //if the continuation is not a push promise it falls through to the headers code if(frameParser.parser instanceof Http2PushPromiseParser) { if(!isClient()) { sendGoAway(ERROR_PROTOCOL_ERROR); throw UndertowMessages.MESSAGES.serverReceivedPushPromise(); } Http2PushPromiseParser pushPromiseParser = (Http2PushPromiseParser) frameParser.parser; channel = new Http2PushPromiseStreamSourceChannel(this, frameData, frameParser.getFrameLength(), pushPromiseParser.getHeaderMap(), pushPromiseParser.getPromisedStreamId(), frameParser.streamId); break; } //fall through } case FRAME_TYPE_HEADERS: { if(!isIdle(frameParser.streamId)) { //this is an existing stream //make sure it exists StreamHolder existing = currentStreams.get(frameParser.streamId); if(existing == null || existing.sourceClosed) { sendGoAway(ERROR_PROTOCOL_ERROR); frameData.close(); return null; } else if (existing.sourceChannel != null ){ //if exists //make sure it has END_STREAM set if(!Bits.allAreSet(frameParser.flags, HEADERS_FLAG_END_STREAM)) { sendGoAway(ERROR_PROTOCOL_ERROR); frameData.close(); return null; } } } else { if(frameParser.streamId < getLastAssignedStreamOtherSide()) { sendGoAway(ERROR_PROTOCOL_ERROR); frameData.close(); return null; } if(frameParser.streamId % 2 == (isClient() ? 1 : 0)) { sendGoAway(ERROR_PROTOCOL_ERROR); frameData.close(); return null; } } Http2HeadersParser parser = (Http2HeadersParser) frameParser.parser; channel = new Http2StreamSourceChannel(this, frameData, frameHeaderData.getFrameLength(), parser.getHeaderMap(), frameParser.streamId); updateStreamIdsCountersInHeaders(frameParser.streamId); StreamHolder holder = currentStreams.get(frameParser.streamId); if(holder == null) { receiveConcurrentStreamsAtomicUpdater.getAndIncrement(this); currentStreams.put(frameParser.streamId, holder = new StreamHolder((Http2StreamSourceChannel) channel)); } else { holder.sourceChannel = (Http2StreamSourceChannel) channel; } if (parser.isHeadersEndStream() && Bits.allAreSet(frameParser.flags, HEADERS_FLAG_END_HEADERS)) { channel.lastFrame(); holder.sourceChannel = null; //this is yuck if(!isClient() || !"100".equals(parser.getHeaderMap().getFirst(STATUS))) { holder.sourceClosed = true; if(holder.sinkClosed) { receiveConcurrentStreamsAtomicUpdater.getAndDecrement(this); currentStreams.remove(frameParser.streamId); } } } if(parser.isInvalid()) { channel.rstStream(ERROR_PROTOCOL_ERROR); sendRstStream(frameParser.streamId, Http2Channel.ERROR_PROTOCOL_ERROR); channel = null; } if(parser.getDependentStreamId() == frameParser.streamId) { sendRstStream(frameParser.streamId, ERROR_PROTOCOL_ERROR); frameData.close(); return null; } break; } case FRAME_TYPE_RST_STREAM: { Http2RstStreamParser parser = (Http2RstStreamParser) frameParser.parser; if (frameParser.streamId == 0) { if(frameData != null) { frameData.close(); } throw new ConnectionErrorException(Http2Channel.ERROR_PROTOCOL_ERROR, UndertowMessages.MESSAGES.streamIdMustNotBeZeroForFrameType(FRAME_TYPE_RST_STREAM)); } channel = new Http2RstStreamStreamSourceChannel(this, frameData, parser.getErrorCode(), frameParser.streamId); handleRstStream(frameParser.streamId); if(isIdle(frameParser.streamId)) { sendGoAway(ERROR_PROTOCOL_ERROR); } break; } case FRAME_TYPE_SETTINGS: { if (!Bits.anyAreSet(frameParser.flags, SETTINGS_FLAG_ACK)) { if(updateSettings(((Http2SettingsParser) frameParser.parser).getSettings())) { sendSettingsAck(); } } else if (frameHeaderData.getFrameLength() != 0) { sendGoAway(ERROR_FRAME_SIZE_ERROR); frameData.close(); return null; } channel = new Http2SettingsStreamSourceChannel(this, frameData, frameParser.getFrameLength(), ((Http2SettingsParser) frameParser.parser).getSettings()); unackedReceiveMaxFrameSize = receiveMaxFrameSize; break; } case FRAME_TYPE_PING: { Http2PingParser pingParser = (Http2PingParser) frameParser.parser; frameData.close(); boolean ack = Bits.anyAreSet(frameParser.flags, PING_FLAG_ACK); channel = new Http2PingStreamSourceChannel(this, pingParser.getData(), ack); if(!ack) { //not an ack from one of our pings, so send it back sendPing(pingParser.getData(), new Http2ControlMessageExceptionHandler(), true); } break; } case FRAME_TYPE_GOAWAY: { Http2GoAwayParser http2GoAwayParser = (Http2GoAwayParser) frameParser.parser; channel = new Http2GoAwayStreamSourceChannel(this, frameData, frameParser.getFrameLength(), http2GoAwayParser.getStatusCode(), http2GoAwayParser.getLastGoodStreamId()); peerGoneAway = true; //the peer is going away //everything is broken for(StreamHolder holder : currentStreams.values()) { if(holder.sourceChannel != null) { holder.sourceChannel.rstStream(); } if(holder.sinkChannel != null) { holder.sinkChannel.rstStream(); } } frameData.close(); sendGoAway(ERROR_NO_ERROR); break; } case FRAME_TYPE_WINDOW_UPDATE: { Http2WindowUpdateParser parser = (Http2WindowUpdateParser) frameParser.parser; handleWindowUpdate(frameParser.streamId, parser.getDeltaWindowSize()); frameData.close(); //we don't return window update notifications, they are handled internally return null; } case FRAME_TYPE_PRIORITY: { Http2PriorityParser parser = (Http2PriorityParser) frameParser.parser; if(parser.getStreamDependency() == frameParser.streamId) { //according to the spec this is a stream error sendRstStream(frameParser.streamId, ERROR_PROTOCOL_ERROR); return null; } frameData.close(); //we don't return priority notifications, they are handled internally return null; } default: { UndertowLogger.REQUEST_LOGGER.tracef("Dropping frame of length %s and type %s for stream %s as we do not understand this type of frame", frameParser.getFrameLength(), frameParser.type, frameParser.streamId); frameData.close(); return null; } } return channel; } @Override protected FrameHeaderData parseFrame(ByteBuffer data) throws IOException { Http2FrameHeaderParser frameParser; do { frameParser = parseFrameNoContinuation(data); // if the frame requires continuation and there is remaining data in the buffer // it should be consumed cos spec ensures the next frame is the continuation } while(frameParser != null && frameParser.getContinuationParser() != null && data.hasRemaining()); return frameParser; } private Http2FrameHeaderParser parseFrameNoContinuation(ByteBuffer data) throws IOException { if (prefaceCount < PREFACE_BYTES.length) { while (data.hasRemaining() && prefaceCount < PREFACE_BYTES.length) { if (data.get() != PREFACE_BYTES[prefaceCount]) { IoUtils.safeClose(getUnderlyingConnection()); throw UndertowMessages.MESSAGES.incorrectHttp2Preface(); } prefaceCount++; } } Http2FrameHeaderParser frameParser = this.frameParser; if (frameParser == null) { this.frameParser = frameParser = new Http2FrameHeaderParser(this, continuationParser); this.continuationParser = null; } if (!frameParser.handle(data)) { return null; } if (!initialSettingsReceived) { if (frameParser.type != FRAME_TYPE_SETTINGS) { UndertowLogger.REQUEST_IO_LOGGER.remoteEndpointFailedToSendInitialSettings(frameParser.type); //StringBuilder sb = new StringBuilder(); //while (data.hasRemaining()) { // sb.append((char)data.get()); // sb.append(" "); //} markReadsBroken(new IOException()); } else { initialSettingsReceived = true; } } this.frameParser = null; if (frameParser.getActualLength() > receiveMaxFrameSize && frameParser.getActualLength() > unackedReceiveMaxFrameSize) { sendGoAway(ERROR_FRAME_SIZE_ERROR); throw UndertowMessages.MESSAGES.http2FrameTooLarge(); } if (frameParser.getContinuationParser() != null) { this.continuationParser = frameParser.getContinuationParser(); } return frameParser; } protected void lastDataRead() { lastDataRead = true; if(!peerGoneAway) { //we just close the connection, as the peer has performed an unclean close IoUtils.safeClose(this); } else { peerGoneAway = true; if(!thisGoneAway) { //we send a goaway message, and then close sendGoAway(ERROR_CONNECT_ERROR); } } } @Override protected boolean isLastFrameReceived() { return lastDataRead; } @Override protected boolean isLastFrameSent() { return thisGoneAway; } @Override protected void handleBrokenSourceChannel(Throwable e) { UndertowLogger.REQUEST_LOGGER.debugf(e, "Closing HTTP2 channel to %s due to broken read side", getPeerAddress()); if (e instanceof ConnectionErrorException) { sendGoAway(((ConnectionErrorException) e).getCode(), new Http2ControlMessageExceptionHandler()); } else { sendGoAway(e instanceof ClosedChannelException ? Http2Channel.ERROR_CONNECT_ERROR : Http2Channel.ERROR_PROTOCOL_ERROR, new Http2ControlMessageExceptionHandler()); } } @Override protected void handleBrokenSinkChannel(Throwable e) { UndertowLogger.REQUEST_LOGGER.debugf(e, "Closing HTTP2 channel to %s due to broken write side", getPeerAddress()); //the write side is broken, so we can't even send GO_AWAY //just tear down the TCP connection IoUtils.safeClose(this); } @Override protected void closeSubChannels() { for (Map.Entry e : currentStreams.entrySet()) { StreamHolder holder = e.getValue(); AbstractHttp2StreamSourceChannel receiver = holder.sourceChannel; if(receiver != null) { receiver.markStreamBroken(); } Http2StreamSinkChannel sink = holder.sinkChannel; if(sink != null) { if (sink.isWritesShutdown()) { ChannelListeners.invokeChannelListener(sink.getIoThread(), sink, ((ChannelListener.SimpleSetter) sink.getWriteSetter()).get()); } IoUtils.safeClose(sink); } } } @Override protected Collection> getReceivers() { List> channels = new ArrayList<>(currentStreams.size()); for(Map.Entry entry : currentStreams.entrySet()) { if(!entry.getValue().sourceClosed) { channels.add(entry.getValue().sourceChannel); } } return channels; } /** * Setting have been received from the client * * @param settings */ boolean updateSettings(List settings) { for (Http2Setting setting : settings) { if (setting.getId() == Http2Setting.SETTINGS_INITIAL_WINDOW_SIZE) { synchronized (flowControlLock) { if (setting.getValue() > Integer.MAX_VALUE) { sendGoAway(ERROR_FLOW_CONTROL_ERROR); return false; } initialSendWindowSize = (int) setting.getValue(); } } else if (setting.getId() == Http2Setting.SETTINGS_MAX_FRAME_SIZE) { if(setting.getValue() > MAX_FRAME_SIZE || setting.getValue() < DEFAULT_MAX_FRAME_SIZE) { UndertowLogger.REQUEST_IO_LOGGER.debug("Invalid value received for SETTINGS_MAX_FRAME_SIZE " + setting.getValue()); sendGoAway(ERROR_PROTOCOL_ERROR); return false; } sendMaxFrameSize = (int) setting.getValue(); } else if (setting.getId() == Http2Setting.SETTINGS_HEADER_TABLE_SIZE) { synchronized (this) { encoder.setMaxTableSize((int) setting.getValue()); } } else if (setting.getId() == Http2Setting.SETTINGS_ENABLE_PUSH) { int result = (int) setting.getValue(); //we allow the remote endpoint to disable push //but not enable it if it has been explictly disabled on this side if(result == 0) { pushEnabled = false; } else if(result != 1) { //invalid value UndertowLogger.REQUEST_IO_LOGGER.debug("Invalid value received for SETTINGS_ENABLE_PUSH " + result); sendGoAway(ERROR_PROTOCOL_ERROR); return false; } } else if (setting.getId() == Http2Setting.SETTINGS_MAX_CONCURRENT_STREAMS) { sendMaxConcurrentStreams = (int) setting.getValue(); } //ignore the rest for now } return true; } public int getHttp2Version() { return 3; } public int getInitialSendWindowSize() { return initialSendWindowSize; } public int getInitialReceiveWindowSize() { return initialReceiveWindowSize; } public int getSendMaxConcurrentStreams() { return sendMaxConcurrentStreams; } public void setSendMaxConcurrentStreams(int sendMaxConcurrentStreams) { this.sendMaxConcurrentStreams = sendMaxConcurrentStreams; sendSettings(); } public int getReceiveMaxConcurrentStreams() { return receiveMaxConcurrentStreams; } public void handleWindowUpdate(int streamId, int deltaWindowSize) throws IOException { if (streamId == 0) { if (deltaWindowSize == 0) { UndertowLogger.REQUEST_IO_LOGGER.debug("Invalid flow-control window increment of 0 received with WINDOW_UPDATE frame for connection"); sendGoAway(ERROR_PROTOCOL_ERROR); return; } synchronized (flowControlLock) { boolean exhausted = sendWindowSize <= FLOW_CONTROL_MIN_WINDOW; // sendWindowSize += deltaWindowSize; if (exhausted) { notifyFlowControlAllowed(); } if (sendWindowSize > Integer.MAX_VALUE) { sendGoAway(ERROR_FLOW_CONTROL_ERROR); } } } else { if (deltaWindowSize == 0) { UndertowLogger.REQUEST_IO_LOGGER.debug("Invalid flow-control window increment of 0 received with WINDOW_UPDATE frame for stream " + streamId); sendRstStream(streamId, ERROR_PROTOCOL_ERROR); return; } StreamHolder holder = currentStreams.get(streamId); Http2StreamSinkChannel stream = holder != null ? holder.sinkChannel : null; if (stream == null) { if(isIdle(streamId)) { sendGoAway(ERROR_PROTOCOL_ERROR); } } else { stream.updateFlowControlWindow(deltaWindowSize); } } } synchronized void notifyFlowControlAllowed() throws IOException { super.recalculateHeldFrames(); } public void sendPing(byte[] data) { sendPing(data, new Http2ControlMessageExceptionHandler()); } public void sendPing(byte[] data, final ChannelExceptionHandler exceptionHandler) { sendPing(data, exceptionHandler, false); } void sendPing(byte[] data, final ChannelExceptionHandler exceptionHandler, boolean ack) { Http2PingStreamSinkChannel ping = new Http2PingStreamSinkChannel(this, data, ack); try { ping.shutdownWrites(); if (!ping.flush()) { ping.getWriteSetter().set(ChannelListeners.flushingChannelListener(null, exceptionHandler)); ping.resumeWrites(); } } catch (IOException e) { if(exceptionHandler != null) { exceptionHandler.handleException(ping, e); } else { UndertowLogger.REQUEST_LOGGER.debug("Failed to send ping and no exception handler set", e); } } catch (Throwable t) { if(exceptionHandler != null) { exceptionHandler.handleException(ping, new IOException(t)); } else { UndertowLogger.REQUEST_LOGGER.debug("Failed to send ping and no exception handler set", t); } } } public void sendGoAway(int status) { sendGoAway(status, new Http2ControlMessageExceptionHandler()); } public void sendGoAway(int status, final ChannelExceptionHandler exceptionHandler) { if (thisGoneAway) { return; } thisGoneAway = true; if(UndertowLogger.REQUEST_IO_LOGGER.isTraceEnabled()) { UndertowLogger.REQUEST_IO_LOGGER.tracef(new ClosedChannelException(), "Sending goaway on channel %s", this); } Http2GoAwayStreamSinkChannel goAway = new Http2GoAwayStreamSinkChannel(this, status, getLastGoodStreamId()); try { goAway.shutdownWrites(); if (!goAway.flush()) { goAway.getWriteSetter().set(ChannelListeners.flushingChannelListener(new ChannelListener() { @Override public void handleEvent(Channel channel) { IoUtils.safeClose(Http2Channel.this); } }, exceptionHandler)); goAway.resumeWrites(); } else { IoUtils.safeClose(this); } } catch (IOException e) { exceptionHandler.handleException(goAway, e); } catch (Throwable t) { exceptionHandler.handleException(goAway, new IOException(t)); } } public void sendUpdateWindowSize(int streamId, int delta) throws IOException { Http2WindowUpdateStreamSinkChannel windowUpdateStreamSinkChannel = new Http2WindowUpdateStreamSinkChannel(this, streamId, delta); flushChannel(windowUpdateStreamSinkChannel); } public SSLSession getSslSession() { StreamConnection con = getUnderlyingConnection(); if (con instanceof SslConnection) { return ((SslConnection) con).getSslSession(); } return null; } public void updateReceiveFlowControlWindow(int read) throws IOException { if (read <= 0) { return; } int delta = -1; synchronized (flowControlLock) { receiveWindowSize -= read; //TODO: make this configurable, we should be able to set the policy that is used to determine when to update the window size int initialWindowSize = this.initialReceiveWindowSize; if (receiveWindowSize < (initialWindowSize / 2)) { delta = initialWindowSize - receiveWindowSize; receiveWindowSize += delta; } } if(delta > 0) { sendUpdateWindowSize(0, delta); } } /** * Creates a strema using a HEADERS frame * * @param requestHeaders * @return * @throws IOException */ public synchronized Http2HeadersStreamSinkChannel createStream(HeaderMap requestHeaders) throws IOException { if (!isClient()) { throw UndertowMessages.MESSAGES.headersStreamCanOnlyBeCreatedByClient(); } if (!isOpen()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } sendConcurrentStreamsAtomicUpdater.incrementAndGet(this); if(sendMaxConcurrentStreams > 0 && sendConcurrentStreams > sendMaxConcurrentStreams) { throw UndertowMessages.MESSAGES.streamLimitExceeded(); } int streamId = streamIdCounter; streamIdCounter += 2; Http2HeadersStreamSinkChannel http2SynStreamStreamSinkChannel = new Http2HeadersStreamSinkChannel(this, streamId, requestHeaders); currentStreams.put(streamId, new StreamHolder(http2SynStreamStreamSinkChannel)); return http2SynStreamStreamSinkChannel; } /** * Adds a received pushed stream into the current streams for a client. The * stream is added into the currentStream and lastAssignedStreamOtherSide is incremented. * * @param pushedStreamId The pushed stream returned by the server * @return true if pushedStreamId can be added, false if invalid * @throws IOException General error like not being a client or odd stream id */ public synchronized boolean addPushPromiseStream(int pushedStreamId) throws IOException { if (!isClient() || pushedStreamId % 2 != 0) { throw UndertowMessages.MESSAGES.pushPromiseCanOnlyBeCreatedByServer(); } if (!isOpen()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } if (!isIdle(pushedStreamId)) { UndertowLogger.REQUEST_IO_LOGGER.debugf("Non idle streamId %d received from the server as a pushed stream.", pushedStreamId); return false; } StreamHolder holder = new StreamHolder((Http2HeadersStreamSinkChannel) null); holder.sinkClosed = true; lastAssignedStreamOtherSide = Math.max(lastAssignedStreamOtherSide, pushedStreamId); currentStreams.put(pushedStreamId, holder); return true; } private synchronized int getLastAssignedStreamOtherSide() { return lastAssignedStreamOtherSide; } private synchronized int getLastGoodStreamId() { return lastGoodStreamId; } /** * Updates the lastGoodStreamId (last request ID to send in goaway frames), * and lastAssignedStreamOtherSide (the last received streamId from the other * side to check if it's idle). The lastAssignedStreamOtherSide in a server * is the same as lastGoodStreamId but in a client push promises can be * received and check for idle is different. * * @param streamNo The received streamId for the client or the server */ private synchronized void updateStreamIdsCountersInHeaders(int streamNo) { if (streamNo % 2 != 0) { // the last good stream is always the last client ID sent by the client or received by the server lastGoodStreamId = Math.max(lastGoodStreamId, streamNo); if (!isClient()) { // server received client request ID => update the last assigned for the server lastAssignedStreamOtherSide = lastGoodStreamId; } } else if (isClient()) { // client received push promise => update the last assigned for the client lastAssignedStreamOtherSide = Math.max(lastAssignedStreamOtherSide, streamNo); } } public synchronized Http2HeadersStreamSinkChannel sendPushPromise(int associatedStreamId, HeaderMap requestHeaders, HeaderMap responseHeaders) throws IOException { if (!isOpen()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } if (isClient()) { throw UndertowMessages.MESSAGES.pushPromiseCanOnlyBeCreatedByServer(); } sendConcurrentStreamsAtomicUpdater.incrementAndGet(this); if(sendMaxConcurrentStreams > 0 && sendConcurrentStreams > sendMaxConcurrentStreams) { throw UndertowMessages.MESSAGES.streamLimitExceeded(); } int streamId = streamIdCounter; streamIdCounter += 2; Http2PushPromiseStreamSinkChannel pushPromise = new Http2PushPromiseStreamSinkChannel(this, requestHeaders, associatedStreamId, streamId); flushChannel(pushPromise); Http2HeadersStreamSinkChannel http2SynStreamStreamSinkChannel = new Http2HeadersStreamSinkChannel(this, streamId, responseHeaders); currentStreams.put(streamId, new StreamHolder(http2SynStreamStreamSinkChannel)); return http2SynStreamStreamSinkChannel; } /** * Try and decrement the send window by the given amount of bytes. * * @param bytesToGrab The amount of bytes the sender is trying to send * @return The actual amount of bytes the sender can send */ int grabFlowControlBytes(int bytesToGrab) { if(bytesToGrab <= 0) { return 0; } int min; synchronized (flowControlLock) { min = (int) Math.min(bytesToGrab, sendWindowSize); if (bytesToGrab > FLOW_CONTROL_MIN_WINDOW && min <= FLOW_CONTROL_MIN_WINDOW) { //this can cause problems with padding, so we just return 0 return 0; } min = Math.min(sendMaxFrameSize, min); sendWindowSize -= min; } return min; } void registerStreamSink(Http2HeadersStreamSinkChannel synResponse) { StreamHolder existing = currentStreams.get(synResponse.getStreamId()); if(existing == null) { throw UndertowMessages.MESSAGES.streamNotRegistered(); } existing.sinkChannel = synResponse; } void removeStreamSink(int streamId) { StreamHolder existing = currentStreams.get(streamId); if(existing == null) { return; } existing.sinkClosed = true; existing.sinkChannel = null; if(existing.sourceClosed) { if(streamId % 2 == (isClient() ? 1 : 0)) { sendConcurrentStreamsAtomicUpdater.getAndDecrement(this); } else { receiveConcurrentStreamsAtomicUpdater.getAndDecrement(this); } currentStreams.remove(streamId); } if(isLastFrameReceived() && currentStreams.isEmpty()) { sendGoAway(ERROR_NO_ERROR); } else if(parseTimeoutUpdater != null && currentStreams.isEmpty()) { parseTimeoutUpdater.connectionIdle(); } } public boolean isClient() { return streamIdCounter % 2 == 1; } HpackEncoder getEncoder() { return encoder; } HpackDecoder getDecoder() { return decoder; } int getMaxHeaders() { return maxHeaders; } int getPaddingBytes() { if(paddingRandom == null) { return 0; } return paddingRandom.nextInt(maxPadding); } @Override public T getAttachment(AttachmentKey key) { if (key == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("key"); } return (T) attachments.get(key); } @Override public List getAttachmentList(AttachmentKey> key) { if (key == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("key"); } Object o = attachments.get(key); if (o == null) { return Collections.emptyList(); } return (List) o; } @Override public T putAttachment(AttachmentKey key, T value) { if (key == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("key"); } return key.cast(attachments.put(key, key.cast(value))); } @Override public T removeAttachment(AttachmentKey key) { return key.cast(attachments.remove(key)); } @Override public void addToAttachmentList(AttachmentKey> key, T value) { if (key == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("key"); } final Map, Object> attachments = this.attachments; synchronized (attachments) { final List list = key.cast(attachments.get(key)); if (list == null) { final AttachmentList newList = new AttachmentList<>((Class) Object.class); attachments.put(key, newList); newList.add(value); } else { list.add(value); } } } public void sendRstStream(int streamId, int statusCode) { if(!isOpen()) { //no point sending if the channel is closed return; } handleRstStream(streamId); if(UndertowLogger.REQUEST_IO_LOGGER.isDebugEnabled()) { UndertowLogger.REQUEST_IO_LOGGER.debugf(new ClosedChannelException(), "Sending rststream on channel %s stream %s", this, streamId); } Http2RstStreamSinkChannel channel = new Http2RstStreamSinkChannel(this, streamId, statusCode); flushChannelIgnoreFailure(channel); } private void handleRstStream(int streamId) { StreamHolder holder = currentStreams.remove(streamId); if(holder != null) { if(streamId % 2 == (isClient() ? 1 : 0)) { sendConcurrentStreamsAtomicUpdater.getAndDecrement(this); } else { receiveConcurrentStreamsAtomicUpdater.getAndDecrement(this); } if (holder.sinkChannel != null) { holder.sinkChannel.rstStream(); } if (holder.sourceChannel != null) { holder.sourceChannel.rstStream(); } } } /** * Creates a response stream to respond to the initial HTTP upgrade * * @return */ public synchronized Http2HeadersStreamSinkChannel createInitialUpgradeResponseStream() { if (lastGoodStreamId != 0) { throw new IllegalStateException(); } updateStreamIdsCountersInHeaders(1); Http2HeadersStreamSinkChannel stream = new Http2HeadersStreamSinkChannel(this, 1); StreamHolder streamHolder = new StreamHolder(stream); streamHolder.sourceClosed = true; currentStreams.put(1, streamHolder); receiveConcurrentStreamsAtomicUpdater.getAndIncrement(this); return stream; } public boolean isPushEnabled() { return pushEnabled; } public boolean isPeerGoneAway() { return peerGoneAway; } public boolean isThisGoneAway() { return thisGoneAway; } Http2StreamSourceChannel removeStreamSource(int streamId) { StreamHolder existing = currentStreams.get(streamId); if(existing == null){ return null; } existing.sourceClosed = true; Http2StreamSourceChannel ret = existing.sourceChannel; existing.sourceChannel = null; if(existing.sinkClosed) { if(streamId % 2 == (isClient() ? 1 : 0)) { sendConcurrentStreamsAtomicUpdater.getAndDecrement(this); } else { receiveConcurrentStreamsAtomicUpdater.getAndDecrement(this); } currentStreams.remove(streamId); } return ret; } Http2StreamSourceChannel getIncomingStream(int streamId) { StreamHolder existing = currentStreams.get(streamId); if(existing == null){ return null; } return existing.sourceChannel; } private class Http2ControlMessageExceptionHandler implements ChannelExceptionHandler { @Override public void handleException(AbstractHttp2StreamSinkChannel channel, IOException exception) { IoUtils.safeClose(channel); handleBrokenSinkChannel(exception); } } public int getReceiveMaxFrameSize() { return receiveMaxFrameSize; } public int getSendMaxFrameSize() { return sendMaxFrameSize; } public String getProtocol() { return protocol; } private synchronized boolean isIdle(int streamNo) { if(streamNo % 2 == streamIdCounter % 2) { // our side is controlled by us in the generated streamIdCounter return streamNo >= streamIdCounter; } else { // the other side should increase lastAssignedStreamOtherSide all the time return streamNo > lastAssignedStreamOtherSide; } } int getMaxHeaderListSize() { return maxHeaderListSize; } private static final class StreamHolder { boolean sourceClosed = false; boolean sinkClosed = false; Http2StreamSourceChannel sourceChannel; Http2StreamSinkChannel sinkChannel; StreamHolder(Http2StreamSourceChannel sourceChannel) { this.sourceChannel = sourceChannel; } StreamHolder(Http2StreamSinkChannel sinkChannel) { this.sinkChannel = sinkChannel; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2DataFrameParser.java000066400000000000000000000037141420065311100322140ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.nio.ByteBuffer; import org.xnio.Bits; /** * Parses the data frame. If the passing flag has not been set then there is nothing to parse. * * @author Stuart Douglas */ class Http2DataFrameParser extends Http2PushBackParser { private int padding = 0; Http2DataFrameParser(int frameLength) { super(frameLength); } @Override protected void handleData(ByteBuffer resource, Http2FrameHeaderParser headerParser) throws ConnectionErrorException { if (Bits.anyAreClear(headerParser.flags, Http2Channel.DATA_FLAG_PADDED)) { finish(); return; } if(headerParser.length == 0) { //empty frame with padding set //which is wrong throw new ConnectionErrorException(Http2Channel.ERROR_PROTOCOL_ERROR); } if (resource.remaining() > 0) { padding = resource.get() & 0xFF; headerParser.length--; //decrement the length by one as we have consumed a byte if(padding > headerParser.length) { throw new ConnectionErrorException(Http2Channel.ERROR_PROTOCOL_ERROR); } finish(); } } int getPadding() { return padding; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2DataStreamSinkChannel.java000066400000000000000000000377521420065311100333670ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import io.undertow.UndertowMessages; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.protocol.framed.SendFrameHeader; import io.undertow.util.HeaderMap; import io.undertow.util.ImmediatePooledByteBuffer; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import java.io.IOException; import java.nio.ByteBuffer; /** * Headers channel * * @author Stuart Douglas */ public class Http2DataStreamSinkChannel extends Http2StreamSinkChannel implements Http2Stream { private final HeaderMap headers; private boolean first = true; private final HpackEncoder encoder; private volatile ChannelListener completionListener; private final int frameType; private boolean completionListenerReady; private volatile boolean completionListenerFailure; //true if the request is broken, and we should invoke the completion listener on the next user op private TrailersProducer trailersProducer; Http2DataStreamSinkChannel(Http2Channel channel, int streamId, int frameType) { this(channel, streamId, new HeaderMap(), frameType); } Http2DataStreamSinkChannel(Http2Channel channel, int streamId, HeaderMap headers, int frameType) { super(channel, streamId); this.encoder = channel.getEncoder(); this.headers = headers; this.frameType = frameType; } public TrailersProducer getTrailersProducer() { return trailersProducer; } public void setTrailersProducer(TrailersProducer trailersProducer) { this.trailersProducer = trailersProducer; } @Override protected SendFrameHeader createFrameHeaderImpl() { //TODO: this is a mess WRT re-using between headers and push_promise, sort out a more reasonable abstraction int dataPaddingBytes = getChannel().getPaddingBytes(); int attempted = getBuffer().remaining() + dataPaddingBytes + (dataPaddingBytes > 0 ? 1 : 0); final int fcWindow = grabFlowControlBytes(attempted); if (fcWindow == 0 && getBuffer().hasRemaining()) { //flow control window is exhausted return new SendFrameHeader(getBuffer().remaining(), null); } if(fcWindow <= dataPaddingBytes + 1) { //so we won't actually be able to send any data, just padding, which is obviously not what we want if(getBuffer().remaining() >= fcWindow) { //easy fix, we just don't send any padding dataPaddingBytes = 0; } else if (getBuffer().remaining() == dataPaddingBytes ){ //corner case. dataPaddingBytes = 1; } else { dataPaddingBytes = fcWindow - getBuffer().remaining() - 1; } } final boolean finalFrame = isFinalFrameQueued() && fcWindow >= (getBuffer().remaining() + (dataPaddingBytes > 0 ? dataPaddingBytes + 1 : 0)); PooledByteBuffer firstHeaderBuffer = getChannel().getBufferPool().allocate(); PooledByteBuffer[] allHeaderBuffers = null; ByteBuffer firstBuffer = firstHeaderBuffer.getBuffer(); boolean firstFrame = false; HeaderMap trailers = null; if(finalFrame && this.trailersProducer != null) { trailers = this.trailersProducer.getTrailers(); if(trailers != null && trailers.size() == 0) { trailers = null; } } if (first) { firstFrame = true; first = false; //back fill the length firstBuffer.put((byte) 0); firstBuffer.put((byte) 0); firstBuffer.put((byte) 0); firstBuffer.put((byte) frameType); //type firstBuffer.put((byte) 0); //back fill the flags Http2ProtocolUtils.putInt(firstBuffer, getStreamId()); int paddingBytes = getChannel().getPaddingBytes(); if(paddingBytes > 0) { firstBuffer.put((byte) (paddingBytes & 0xFF)); } writeBeforeHeaderBlock(firstBuffer); HeaderMap headers = this.headers; HpackEncoder.State result = encoder.encode(headers, firstBuffer); PooledByteBuffer current = firstHeaderBuffer; int headerFrameLength = firstBuffer.position() - 9 + paddingBytes; firstBuffer.put(0, (byte) ((headerFrameLength >> 16) & 0xFF)); firstBuffer.put(1, (byte) ((headerFrameLength >> 8) & 0xFF)); firstBuffer.put(2, (byte) (headerFrameLength & 0xFF)); firstBuffer.put(4, (byte) ((isFinalFrameQueued() && !getBuffer().hasRemaining() && frameType == Http2Channel.FRAME_TYPE_HEADERS && trailers == null ? Http2Channel.HEADERS_FLAG_END_STREAM : 0) | (result == HpackEncoder.State.COMPLETE ? Http2Channel.HEADERS_FLAG_END_HEADERS : 0 ) | (paddingBytes > 0 ? Http2Channel.HEADERS_FLAG_PADDED : 0))); //flags ByteBuffer currentBuffer = firstBuffer; if(currentBuffer.remaining() < paddingBytes) { allHeaderBuffers = allocateAll(allHeaderBuffers, current); current = allHeaderBuffers[allHeaderBuffers.length - 1]; currentBuffer = current.getBuffer(); } for(int i = 0; i < paddingBytes; ++ i) { currentBuffer.put((byte) 0); } while (result != HpackEncoder.State.COMPLETE) { //todo: add some kind of limit here allHeaderBuffers = allocateAll(allHeaderBuffers, current); current = allHeaderBuffers[allHeaderBuffers.length - 1]; result = encodeContinuationFrame(headers, current); } } PooledByteBuffer currentPooled = allHeaderBuffers == null ? firstHeaderBuffer : allHeaderBuffers[allHeaderBuffers.length - 1]; ByteBuffer currentBuffer = currentPooled.getBuffer(); ByteBuffer trailer = null; int remainingInBuffer = 0; boolean requiresTrailers = false; if (getBuffer().remaining() > 0) { if (fcWindow > 0) { //make sure we have room in the header buffer if (currentBuffer.remaining() < 10) { allHeaderBuffers = allocateAll(allHeaderBuffers, currentPooled); currentPooled = allHeaderBuffers == null ? firstHeaderBuffer : allHeaderBuffers[allHeaderBuffers.length - 1]; currentBuffer = currentPooled.getBuffer(); } int toSend = fcWindow - dataPaddingBytes - (dataPaddingBytes > 0 ? 1 :0); remainingInBuffer = getBuffer().remaining() - toSend; getBuffer().limit(getBuffer().position() + toSend); currentBuffer.put((byte) ((fcWindow >> 16) & 0xFF)); currentBuffer.put((byte) ((fcWindow >> 8) & 0xFF)); currentBuffer.put((byte) (fcWindow & 0xFF)); currentBuffer.put((byte) Http2Channel.FRAME_TYPE_DATA); //type if(trailers == null) { currentBuffer.put((byte) ((finalFrame ? Http2Channel.DATA_FLAG_END_STREAM : 0) | (dataPaddingBytes > 0 ? Http2Channel.DATA_FLAG_PADDED : 0))); //flags } else { if(finalFrame) { requiresTrailers = true; } currentBuffer.put((byte) (dataPaddingBytes > 0 ? Http2Channel.DATA_FLAG_PADDED : 0)); //flags } Http2ProtocolUtils.putInt(currentBuffer, getStreamId()); if(dataPaddingBytes > 0) { currentBuffer.put((byte) (dataPaddingBytes & 0xFF)); trailer = ByteBuffer.allocate(dataPaddingBytes); } } else { remainingInBuffer = getBuffer().remaining(); } } else if (finalFrame && !firstFrame) { currentBuffer.put((byte) ((fcWindow >> 16) & 0xFF)); currentBuffer.put((byte) ((fcWindow >> 8) & 0xFF)); currentBuffer.put((byte) (fcWindow & 0xFF)); currentBuffer.put((byte) Http2Channel.FRAME_TYPE_DATA); //type if (trailers == null) { currentBuffer.put((byte) ((Http2Channel.HEADERS_FLAG_END_STREAM & 0xFF) | (dataPaddingBytes > 0 ? Http2Channel.DATA_FLAG_PADDED : 0))); //flags } else { requiresTrailers = true; currentBuffer.put((byte) (dataPaddingBytes > 0 ? Http2Channel.DATA_FLAG_PADDED : 0)); //flags } Http2ProtocolUtils.putInt(currentBuffer, getStreamId()); if (dataPaddingBytes > 0) { currentBuffer.put((byte) (dataPaddingBytes & 0xFF)); trailer = ByteBuffer.allocate(dataPaddingBytes); } } else if(finalFrame && trailers != null) { requiresTrailers = true; } if (requiresTrailers) { PooledByteBuffer firstTrailerBuffer = getChannel().getBufferPool().allocate(); if (trailer != null) { firstTrailerBuffer.getBuffer().put(trailer); } firstTrailerBuffer.getBuffer().put((byte) 0); firstTrailerBuffer.getBuffer().put((byte) 0); firstTrailerBuffer.getBuffer().put((byte) 0); firstTrailerBuffer.getBuffer().put((byte) Http2Channel.FRAME_TYPE_HEADERS); //type firstTrailerBuffer.getBuffer().put((byte) (Http2Channel.HEADERS_FLAG_END_STREAM | Http2Channel.HEADERS_FLAG_END_HEADERS)); //back fill the flags Http2ProtocolUtils.putInt(firstTrailerBuffer.getBuffer(), getStreamId()); HpackEncoder.State result = encoder.encode(trailers, firstTrailerBuffer.getBuffer()); if (result != HpackEncoder.State.COMPLETE) { throw UndertowMessages.MESSAGES.http2TrailerToLargeForSingleBuffer(); } int headerFrameLength = firstTrailerBuffer.getBuffer().position() - 9; firstTrailerBuffer.getBuffer().put(0, (byte) ((headerFrameLength >> 16) & 0xFF)); firstTrailerBuffer.getBuffer().put(1, (byte) ((headerFrameLength >> 8) & 0xFF)); firstTrailerBuffer.getBuffer().put(2, (byte) (headerFrameLength & 0xFF)); firstTrailerBuffer.getBuffer().flip(); int size = firstTrailerBuffer.getBuffer().remaining(); trailer = ByteBuffer.allocate(size); trailer.put(firstTrailerBuffer.getBuffer()); trailer.flip(); firstTrailerBuffer.close(); } if (allHeaderBuffers == null) { //only one buffer required currentBuffer.flip(); return new SendFrameHeader(remainingInBuffer, currentPooled, false, trailer); } else { //headers were too big to fit in one buffer //for now we will just copy them into a big buffer int length = 0; for (int i = 0; i < allHeaderBuffers.length; ++i) { length += allHeaderBuffers[i].getBuffer().position(); allHeaderBuffers[i].getBuffer().flip(); } try { ByteBuffer newBuf = ByteBuffer.allocate(length); for (int i = 0; i < allHeaderBuffers.length; ++i) { newBuf.put(allHeaderBuffers[i].getBuffer()); } newBuf.flip(); return new SendFrameHeader(remainingInBuffer, new ImmediatePooledByteBuffer(newBuf), false, trailer); } finally { //the allocate can oome for (int i = 0; i < allHeaderBuffers.length; ++i) { allHeaderBuffers[i].close(); } } } } private HpackEncoder.State encodeContinuationFrame(HeaderMap headers, PooledByteBuffer current) { ByteBuffer currentBuffer; HpackEncoder.State result;//continuation frame //note that if the buffers are small we may not actually need a continuation here //but it greatly reduces the code complexity //back fill the length currentBuffer = current.getBuffer(); currentBuffer.put((byte) 0); currentBuffer.put((byte) 0); currentBuffer.put((byte) 0); currentBuffer.put((byte) Http2Channel.FRAME_TYPE_CONTINUATION); //type currentBuffer.put((byte) 0); //back fill the flags Http2ProtocolUtils.putInt(currentBuffer, getStreamId()); result = encoder.encode(headers, currentBuffer); int contFrameLength = currentBuffer.position() - 9; currentBuffer.put(0, (byte) ((contFrameLength >> 16) & 0xFF)); currentBuffer.put(1, (byte) ((contFrameLength >> 8) & 0xFF)); currentBuffer.put(2, (byte) (contFrameLength & 0xFF)); currentBuffer.put(4, (byte) (result == HpackEncoder.State.COMPLETE ? Http2Channel.HEADERS_FLAG_END_HEADERS : 0 )); //flags return result; } @Override public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { handleFailedChannel(); return super.write(srcs, offset, length); } private void handleFailedChannel() { if(completionListenerFailure && completionListener != null) { ChannelListeners.invokeChannelListener(this, completionListener); completionListener = null; } } @Override public long write(ByteBuffer[] srcs) throws IOException { handleFailedChannel(); return super.write(srcs); } @Override public int write(ByteBuffer src) throws IOException { handleFailedChannel(); return super.write(src); } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { handleFailedChannel(); return super.writeFinal(srcs, offset, length); } @Override public long writeFinal(ByteBuffer[] srcs) throws IOException { handleFailedChannel(); return super.writeFinal(srcs); } @Override public int writeFinal(ByteBuffer src) throws IOException { handleFailedChannel(); return super.writeFinal(src); } @Override public boolean flush() throws IOException { handleFailedChannel(); if(completionListenerReady && completionListener != null) { ChannelListeners.invokeChannelListener(this, completionListener); completionListener = null; } return super.flush(); } protected void writeBeforeHeaderBlock(ByteBuffer buffer) { } protected boolean isFlushRequiredOnEmptyBuffer() { return first; } public HeaderMap getHeaders() { return headers; } @Override protected void handleFlushComplete(boolean finalFrame) { super.handleFlushComplete(finalFrame); if (finalFrame) { if (completionListener != null) { completionListenerReady = true; } } } @Override protected void channelForciblyClosed() throws IOException { super.channelForciblyClosed(); if (completionListener != null) { completionListenerFailure = true; } } public ChannelListener getCompletionListener() { return completionListener; } public void setCompletionListener(ChannelListener completionListener) { this.completionListener = completionListener; } public interface TrailersProducer { HeaderMap getTrailers(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2DiscardParser.java000066400000000000000000000024351420065311100317400ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.nio.ByteBuffer; /** * Parser for HTTP2 window update frames * * @author Stuart Douglas */ class Http2DiscardParser extends Http2PushBackParser { int remaining; Http2DiscardParser(int frameLength) { super(frameLength); remaining = frameLength; } @Override protected void handleData(ByteBuffer resource, Http2FrameHeaderParser frameHeaderParser) { int toUse = Math.min(resource.remaining(), remaining); remaining -= toUse; resource.position(resource.position() + toUse); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2FrameHeaderParser.java000066400000000000000000000273711420065311100325400ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import static io.undertow.protocols.http2.Http2Channel.DATA_FLAG_END_STREAM; import static io.undertow.protocols.http2.Http2Channel.FRAME_TYPE_CONTINUATION; import static io.undertow.protocols.http2.Http2Channel.FRAME_TYPE_DATA; import static io.undertow.protocols.http2.Http2Channel.FRAME_TYPE_GOAWAY; import static io.undertow.protocols.http2.Http2Channel.FRAME_TYPE_HEADERS; import static io.undertow.protocols.http2.Http2Channel.FRAME_TYPE_PRIORITY; import static io.undertow.protocols.http2.Http2Channel.FRAME_TYPE_PUSH_PROMISE; import static io.undertow.protocols.http2.Http2Channel.FRAME_TYPE_RST_STREAM; import static io.undertow.protocols.http2.Http2Channel.FRAME_TYPE_SETTINGS; import static io.undertow.protocols.http2.Http2Channel.FRAME_TYPE_WINDOW_UPDATE; import static io.undertow.protocols.http2.Http2Channel.HEADERS_FLAG_END_HEADERS; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.anyAreClear; import static org.xnio.Bits.anyAreSet; import java.io.IOException; import java.nio.ByteBuffer; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.server.protocol.framed.AbstractFramedStreamSourceChannel; import io.undertow.server.protocol.framed.FrameHeaderData; /** * @author Stuart Douglas */ class Http2FrameHeaderParser implements FrameHeaderData { final byte[] header = new byte[9]; int read = 0; int length; int type; int flags; int streamId; Http2PushBackParser parser = null; Http2HeadersParser continuationParser = null; private static final int SECOND_RESERVED_MASK = ~(1 << 7); private Http2Channel http2Channel; Http2FrameHeaderParser(Http2Channel http2Channel, Http2HeadersParser continuationParser) { this.http2Channel = http2Channel; this.continuationParser = continuationParser; } public boolean handle(final ByteBuffer byteBuffer) throws IOException { if (parser == null) { if (!parseFrameHeader(byteBuffer)) { return false; } if(continuationParser != null && type != FRAME_TYPE_CONTINUATION) { throw new ConnectionErrorException(Http2Channel.ERROR_PROTOCOL_ERROR, UndertowMessages.MESSAGES.expectedContinuationFrame()); } switch (type) { case FRAME_TYPE_DATA: { if (streamId == 0) { throw new ConnectionErrorException(Http2Channel.ERROR_PROTOCOL_ERROR, UndertowMessages.MESSAGES.streamIdMustNotBeZeroForFrameType(Http2Channel.FRAME_TYPE_DATA)); } parser = new Http2DataFrameParser(length); break; } case FRAME_TYPE_HEADERS: { if (streamId == 0) { throw new ConnectionErrorException(Http2Channel.ERROR_PROTOCOL_ERROR, UndertowMessages.MESSAGES.streamIdMustNotBeZeroForFrameType(Http2Channel.FRAME_TYPE_HEADERS)); } parser = new Http2HeadersParser(length, http2Channel.getDecoder(), http2Channel.isClient(), http2Channel.getMaxHeaders(), streamId, http2Channel.getMaxHeaderListSize()); if(allAreClear(flags, Http2Channel.HEADERS_FLAG_END_HEADERS)) { continuationParser = (Http2HeadersParser) parser; } break; } case FRAME_TYPE_RST_STREAM: { if(length != 4) { throw new ConnectionErrorException(Http2Channel.ERROR_FRAME_SIZE_ERROR, UndertowMessages.MESSAGES.incorrectFrameSize()); } parser = new Http2RstStreamParser(length); break; } case FRAME_TYPE_CONTINUATION: { if(continuationParser == null) { http2Channel.sendGoAway(Http2Channel.ERROR_PROTOCOL_ERROR); throw UndertowMessages.MESSAGES.http2ContinuationFrameNotExpected(); } if(continuationParser.getStreamId() != streamId) { http2Channel.sendGoAway(Http2Channel.ERROR_PROTOCOL_ERROR); throw UndertowMessages.MESSAGES.http2ContinuationFrameNotExpected(); } parser = continuationParser; continuationParser.moreData(length); break; } case FRAME_TYPE_PUSH_PROMISE: { parser = new Http2PushPromiseParser(length, http2Channel.getDecoder(), http2Channel.isClient(), http2Channel.getMaxHeaders(), streamId, http2Channel.getMaxHeaderListSize()); if(allAreClear(flags, Http2Channel.HEADERS_FLAG_END_HEADERS)) { continuationParser = (Http2HeadersParser) parser; } break; } case FRAME_TYPE_GOAWAY: { if (streamId != 0) { throw new ConnectionErrorException(Http2Channel.ERROR_PROTOCOL_ERROR, UndertowMessages.MESSAGES.streamIdMustBeZeroForFrameType(Http2Channel.FRAME_TYPE_GOAWAY)); } parser = new Http2GoAwayParser(length); break; } case Http2Channel.FRAME_TYPE_PING: { if (length != 8) { throw new ConnectionErrorException(Http2Channel.ERROR_FRAME_SIZE_ERROR, UndertowMessages.MESSAGES.invalidPingSize()); } if (streamId != 0) { throw new ConnectionErrorException(Http2Channel.ERROR_PROTOCOL_ERROR, UndertowMessages.MESSAGES.streamIdMustBeZeroForFrameType(Http2Channel.FRAME_TYPE_PING)); } parser = new Http2PingParser(length); break; } case FRAME_TYPE_SETTINGS: { if(length % 6 != 0) { throw new ConnectionErrorException(Http2Channel.ERROR_FRAME_SIZE_ERROR, UndertowMessages.MESSAGES.incorrectFrameSize()); } if (streamId != 0) { throw new ConnectionErrorException(Http2Channel.ERROR_PROTOCOL_ERROR, UndertowMessages.MESSAGES.streamIdMustBeZeroForFrameType(Http2Channel.FRAME_TYPE_SETTINGS)); } parser = new Http2SettingsParser(length); break; } case FRAME_TYPE_WINDOW_UPDATE: { if(length != 4) { throw new ConnectionErrorException(Http2Channel.ERROR_FRAME_SIZE_ERROR, UndertowMessages.MESSAGES.incorrectFrameSize()); } parser = new Http2WindowUpdateParser(length); break; } case FRAME_TYPE_PRIORITY: { if(length != 5) { throw new ConnectionErrorException(Http2Channel.ERROR_FRAME_SIZE_ERROR, UndertowMessages.MESSAGES.incorrectFrameSize()); } if (streamId == 0) { throw new ConnectionErrorException(Http2Channel.ERROR_PROTOCOL_ERROR, UndertowMessages.MESSAGES.streamIdMustNotBeZeroForFrameType(Http2Channel.FRAME_TYPE_PRIORITY)); } parser = new Http2PriorityParser(length); break; } default: { parser = new Http2DiscardParser(length); break; } } } parser.parse(byteBuffer, this); if(continuationParser != null) { if(anyAreSet(flags, HEADERS_FLAG_END_HEADERS)) { continuationParser = null; } } return parser.isFinished(); } private boolean parseFrameHeader(ByteBuffer byteBuffer) { while (read < 9 && byteBuffer.hasRemaining()) { header[read++] = byteBuffer.get(); } if (read != 9) { return false; } length = (header[0] & 0xFF) << 16; length += (header[1] & 0xff) << 8; length += header[2] & 0xff; type = header[3] & 0xff; flags = header[4] & 0xff; streamId = (header[5] & SECOND_RESERVED_MASK & 0xFF) << 24; streamId += (header[6] & 0xFF) << 16; streamId += (header[7] & 0xFF) << 8; streamId += (header[8] & 0xFF); return true; } @Override public long getFrameLength() { //we only consider data frames to have length, all other frames are fully consumed by header parsing if (type != FRAME_TYPE_DATA) { return 0; } return length; } int getActualLength() { return length; } @Override public AbstractFramedStreamSourceChannel getExistingChannel() { Http2StreamSourceChannel http2StreamSourceChannel; if (type == FRAME_TYPE_DATA || type == Http2Channel.FRAME_TYPE_CONTINUATION || type == Http2Channel.FRAME_TYPE_PRIORITY ) { if (anyAreSet(flags, Http2Channel.DATA_FLAG_END_STREAM)) { http2StreamSourceChannel = http2Channel.removeStreamSource(streamId); } else if (type == FRAME_TYPE_CONTINUATION) { http2StreamSourceChannel = http2Channel.getIncomingStream(streamId); if(http2StreamSourceChannel != null && http2StreamSourceChannel.isHeadersEndStream() && anyAreSet(flags, Http2Channel.CONTINUATION_FLAG_END_HEADERS)) { http2Channel.removeStreamSource(streamId); } } else { http2StreamSourceChannel = http2Channel.getIncomingStream(streamId); } if(type == FRAME_TYPE_DATA && http2StreamSourceChannel != null) { Http2DataFrameParser dataFrameParser = (Http2DataFrameParser) parser; http2StreamSourceChannel.updateContentSize(getFrameLength() - dataFrameParser.getPadding(), anyAreSet(flags, DATA_FLAG_END_STREAM)); } return http2StreamSourceChannel; } else if(type == FRAME_TYPE_HEADERS) { //headers can actually be a trailer Http2StreamSourceChannel channel = http2Channel.getIncomingStream(streamId); if(channel != null) { if(anyAreClear(flags, Http2Channel.HEADERS_FLAG_END_STREAM)) { //this is a protocol error UndertowLogger.REQUEST_IO_LOGGER.debug("Received HTTP/2 trailers header without end stream set"); http2Channel.sendGoAway(Http2Channel.ERROR_PROTOCOL_ERROR); } if (!channel.isHeadersEndStream() && anyAreSet(flags, Http2Channel.HEADERS_FLAG_END_HEADERS)) { http2Channel.removeStreamSource(streamId); } } return channel; } return null; } Http2PushBackParser getParser() { return parser; } Http2HeadersParser getContinuationParser() { return continuationParser; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2FramePriority.java000066400000000000000000000126441420065311100320110ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.util.Deque; import java.util.Iterator; import java.util.List; import io.undertow.UndertowLogger; import io.undertow.server.protocol.framed.FramePriority; import io.undertow.server.protocol.framed.SendFrameHeader; /** * TODO: real priority * * @author Stuart Douglas */ class Http2FramePriority implements FramePriority { private int nextId; Http2FramePriority(int nextId) { this.nextId = nextId; } @Override public boolean insertFrame(AbstractHttp2StreamSinkChannel newFrame, List pendingFrames) { //we need to deal with out of order streams //if multiple threads are creating streams they may not end up here in the correct order boolean incrementIfAccepted = false; if ((newFrame.getChannel().isClient() && newFrame instanceof Http2HeadersStreamSinkChannel) || newFrame instanceof Http2PushPromiseStreamSinkChannel) { if (newFrame instanceof Http2PushPromiseStreamSinkChannel) { int streamId = ((Http2PushPromiseStreamSinkChannel) newFrame).getPushedStreamId(); if (streamId > nextId) { return false; } else if (streamId == nextId) { incrementIfAccepted = true; } } else { int streamId = ((Http2HeadersStreamSinkChannel) newFrame).getStreamId(); if (streamId > nextId) { return false; } else if (streamId == nextId) { incrementIfAccepted = true; } } } //first deal with flow control if (newFrame instanceof Http2StreamSinkChannel) { if (newFrame.isBroken() || !newFrame.isOpen()) { return true; //just quietly drop the frame } try { SendFrameHeader header = ((Http2StreamSinkChannel) newFrame).generateSendFrameHeader(); //if no header is generated then flow control means we can't send anything if (header.getByteBuffer() == null) { //we clear the header, as we want to generate a new real header when the flow control window is updated ((Http2StreamSinkChannel) newFrame).clearHeader(); return false; } } catch (Exception e) { UndertowLogger.REQUEST_LOGGER.debugf("Failed to generate header %s", newFrame); } } pendingFrames.add(newFrame); if (incrementIfAccepted) { nextId += 2; } return true; } @Override public void frameAdded(AbstractHttp2StreamSinkChannel addedFrame, List pendingFrames, Deque holdFrames) { Iterator it = holdFrames.iterator(); while (it.hasNext()) { AbstractHttp2StreamSinkChannel pending = it.next(); boolean incrementNextId = false; if ((pending.getChannel().isClient() && pending instanceof Http2HeadersStreamSinkChannel) || pending instanceof Http2PushPromiseStreamSinkChannel) { if (pending instanceof Http2PushPromiseStreamSinkChannel) { int streamId = ((Http2PushPromiseStreamSinkChannel) pending).getPushedStreamId(); if (streamId > nextId) { continue; } else if (streamId == nextId) { incrementNextId = true; } } else { int streamId = ((Http2HeadersStreamSinkChannel) pending).getStreamId(); if (streamId > nextId) { continue; } else if (streamId == nextId) { incrementNextId = true; } } } if (pending instanceof Http2StreamSinkChannel) { SendFrameHeader header = ((Http2StreamSinkChannel) pending).generateSendFrameHeader(); if (header.getByteBuffer() != null) { pendingFrames.add(pending); it.remove(); it = holdFrames.iterator(); if (incrementNextId) { nextId += 2; } } else { //we clear the header, as we want to generate a new real header when the flow control window is updated ((Http2StreamSinkChannel) pending).clearHeader(); } } } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2GoAwayParser.java000066400000000000000000000027501420065311100315560ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.nio.ByteBuffer; /** * Parser for HTTP2 GO_AWAY frames * * @author Stuart Douglas */ public class Http2GoAwayParser extends Http2PushBackParser { private int statusCode; private int lastGoodStreamId; public Http2GoAwayParser(int frameLength) { super(frameLength); } @Override protected void handleData(ByteBuffer resource, Http2FrameHeaderParser headerParser) { if (resource.remaining() < 8) { return; } lastGoodStreamId = Http2ProtocolUtils.readInt(resource); statusCode = Http2ProtocolUtils.readInt(resource); } public int getStatusCode() { return statusCode; } public int getLastGoodStreamId() { return lastGoodStreamId; } } Http2GoAwayStreamSinkChannel.java000066400000000000000000000037311420065311100336140ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.nio.ByteBuffer; import io.undertow.server.protocol.framed.SendFrameHeader; import io.undertow.util.ImmediatePooledByteBuffer; /** * The go away *

* TODO: at the moment we don't allow the additional debug data * * @author Stuart Douglas */ class Http2GoAwayStreamSinkChannel extends Http2NoDataStreamSinkChannel { public static final int HEADER_FIRST_LINE = (8 << 8) | (Http2Channel.FRAME_TYPE_GOAWAY); private final int status; private final int lastGoodStreamId; protected Http2GoAwayStreamSinkChannel(Http2Channel channel, int status, int lastGoodStreamId) { super(channel); this.status = status; this.lastGoodStreamId = lastGoodStreamId; } @Override protected SendFrameHeader createFrameHeader() { ByteBuffer buf = ByteBuffer.allocate(17); Http2ProtocolUtils.putInt(buf, HEADER_FIRST_LINE); buf.put((byte)0); Http2ProtocolUtils.putInt(buf, 0); //stream id Http2ProtocolUtils.putInt(buf, lastGoodStreamId); Http2ProtocolUtils.putInt(buf, status); buf.flip(); return new SendFrameHeader(new ImmediatePooledByteBuffer(buf)); } @Override protected boolean isLastFrame() { return true; } } Http2GoAwayStreamSourceChannel.java000066400000000000000000000026761420065311100341570ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import io.undertow.connector.PooledByteBuffer; /** * A HTTP2 go away frame * * @author Stuart Douglas */ public class Http2GoAwayStreamSourceChannel extends AbstractHttp2StreamSourceChannel { private final int status; private final int lastGoodStreamId; Http2GoAwayStreamSourceChannel(Http2Channel framedChannel, PooledByteBuffer data, long frameDataRemaining, int status, int lastGoodStreamId) { super(framedChannel, data, frameDataRemaining); this.status = status; this.lastGoodStreamId = lastGoodStreamId; lastFrame(); } public int getStatus() { return status; } public int getLastGoodStreamId() { return lastGoodStreamId; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2HeaderBlockParser.java000066400000000000000000000160411420065311100325300ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.xnio.Bits; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.server.Connectors; import io.undertow.util.HeaderMap; import io.undertow.util.Headers; import io.undertow.util.HttpString; /** * Parser for HTTP2 headers * * @author Stuart Douglas */ abstract class Http2HeaderBlockParser extends Http2PushBackParser implements HpackDecoder.HeaderEmitter { private final HeaderMap headerMap = new HeaderMap(); private boolean beforeHeadersHandled = false; private final HpackDecoder decoder; private int frameRemaining = -1; private boolean invalid = false; private boolean processingPseudoHeaders = true; private final boolean client; private final int maxHeaders; private final int maxHeaderListSize; private int currentPadding; private final int streamId; private int headerSize; //headers the server is allowed to receive private static final Set SERVER_HEADERS; static { Set server = new HashSet<>(); server.add(Http2Channel.METHOD); server.add(Http2Channel.AUTHORITY); server.add(Http2Channel.SCHEME); server.add(Http2Channel.PATH); SERVER_HEADERS = Collections.unmodifiableSet(server); } Http2HeaderBlockParser(int frameLength, HpackDecoder decoder, boolean client, int maxHeaders, int streamId, int maxHeaderListSize) { super(frameLength); this.decoder = decoder; this.client = client; this.maxHeaders = maxHeaders; this.streamId = streamId; this.maxHeaderListSize = maxHeaderListSize; } @Override protected void handleData(ByteBuffer resource, Http2FrameHeaderParser header) throws IOException { boolean continuationFramesComing = Bits.anyAreClear(header.flags, Http2Channel.HEADERS_FLAG_END_HEADERS); if (frameRemaining == -1) { frameRemaining = header.length; } final boolean moreDataThisFrame = resource.remaining() < frameRemaining; final int pos = resource.position(); int readInBeforeHeader = 0; try { if (!beforeHeadersHandled) { if (!handleBeforeHeader(resource, header)) { return; } currentPadding = getPaddingLength(); readInBeforeHeader = resource.position() - pos; } beforeHeadersHandled = true; decoder.setHeaderEmitter(this); int oldLimit = -1; if(currentPadding > 0) { int actualData = frameRemaining - readInBeforeHeader - currentPadding; if(actualData < 0) { throw new ConnectionErrorException(Http2Channel.ERROR_PROTOCOL_ERROR); } if(resource.remaining() > actualData) { oldLimit = resource.limit(); resource.limit(resource.position() + actualData); } } try { decoder.decode(resource, moreDataThisFrame || continuationFramesComing); } catch (HpackException e) { throw new ConnectionErrorException(e.getCloseCode(), e); } if(maxHeaders > 0 && headerMap.size() > maxHeaders) { throw new StreamErrorException(Http2Channel.ERROR_FRAME_SIZE_ERROR); } if(oldLimit != -1) { if(resource.remaining() == 0) { int paddingInBuffer = oldLimit - resource.limit(); currentPadding -= paddingInBuffer; resource.limit(oldLimit); resource.position(oldLimit); } else { resource.limit(oldLimit); } } } finally { int used = resource.position() - pos; frameRemaining -= used; } } protected abstract boolean handleBeforeHeader(ByteBuffer resource, Http2FrameHeaderParser header); HeaderMap getHeaderMap() { return headerMap; } @Override public void emitHeader(HttpString name, String value, boolean neverIndex) throws HpackException { if(maxHeaderListSize > 0) { headerSize += (name.length() + value.length() + 32); if (headerSize > maxHeaderListSize) { throw new HpackException(UndertowMessages.MESSAGES.headerBlockTooLarge(), Http2Channel.ERROR_PROTOCOL_ERROR); } } if(maxHeaders > 0 && headerMap.size() > maxHeaders) { return; } headerMap.add(name, value); if(name.length() == 0) { throw UndertowMessages.MESSAGES.invalidHeader(); } if(name.equals(Headers.TRANSFER_ENCODING)) { throw new HpackException(Http2Channel.ERROR_PROTOCOL_ERROR); } if(name.byteAt(0) == ':') { if(client) { if(!name.equals(Http2Channel.STATUS)) { invalid = true; } } else { if(!SERVER_HEADERS.contains(name)) { invalid = true; } } if(!processingPseudoHeaders) { throw new HpackException(UndertowMessages.MESSAGES.pseudoHeaderInWrongOrder(name), Http2Channel.ERROR_PROTOCOL_ERROR); } } else { processingPseudoHeaders = false; } for(int i = 0; i < name.length(); ++i) { byte c = name.byteAt(i); if(c>= 'A' && c <= 'Z') { invalid = true; UndertowLogger.REQUEST_LOGGER.debugf("Malformed request, header %s contains uppercase characters", name); } else if(c != ':' && !Connectors.isValidTokenCharacter(c)) { invalid = true; UndertowLogger.REQUEST_LOGGER.debugf("Malformed request, header %s contains invalid token character", name); } } } protected abstract int getPaddingLength(); @Override protected void moreData(int data) { super.moreData(data); frameRemaining += data; } public boolean isInvalid() { return invalid; } public int getStreamId() { return streamId; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2HeadersParser.java000066400000000000000000000057371420065311100317520ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.nio.ByteBuffer; import org.xnio.Bits; /** * Parser for HTTP2 Headers frames * * @author Stuart Douglas */ class Http2HeadersParser extends Http2HeaderBlockParser { private static final int DEPENDENCY_MASK = ~(1 << 7); private int paddingLength = 0; private int dependentStreamId = 0; private int weight = 16; //default weight as per spec private boolean headersEndStream = false; private boolean exclusive; Http2HeadersParser(int frameLength, HpackDecoder hpackDecoder, boolean client,int maxHeaders, int streamId, int maxHeaderListSize) { super(frameLength, hpackDecoder, client, maxHeaders, streamId, maxHeaderListSize); } @Override protected boolean handleBeforeHeader(ByteBuffer resource, Http2FrameHeaderParser headerParser) { boolean hasPadding = Bits.anyAreSet(headerParser.flags, Http2Channel.HEADERS_FLAG_PADDED); boolean hasPriority = Bits.anyAreSet(headerParser.flags, Http2Channel.HEADERS_FLAG_PRIORITY); headersEndStream = Bits.allAreSet(headerParser.flags, Http2Channel.HEADERS_FLAG_END_STREAM); int reqLength = (hasPadding ? 1 : 0) + (hasPriority ? 5 : 0); if (reqLength == 0) { return true; } if (resource.remaining() < reqLength) { return false; } if (hasPadding) { paddingLength = (resource.get() & 0xFF); } if (hasPriority) { if (resource.remaining() < 4) { return false; } byte b = resource.get(); exclusive = (b & (1 << 7)) != 0; dependentStreamId = (b & DEPENDENCY_MASK & 0xFF) << 24; dependentStreamId += (resource.get() & 0xFF) << 16; dependentStreamId += (resource.get() & 0xFF) << 8; dependentStreamId += (resource.get() & 0xFF); weight = resource.get() & 0xFF; } return true; } protected int getPaddingLength() { return paddingLength; } int getDependentStreamId() { return dependentStreamId; } int getWeight() { return weight; } boolean isHeadersEndStream() { return headersEndStream; } public boolean isExclusive() { return exclusive; } } Http2HeadersStreamSinkChannel.java000066400000000000000000000023541420065311100340000ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import io.undertow.util.HeaderMap; /** * Headers channel * * @author Stuart Douglas */ public class Http2HeadersStreamSinkChannel extends Http2DataStreamSinkChannel { public Http2HeadersStreamSinkChannel(Http2Channel channel, int streamId) { super(channel, streamId, Http2Channel.FRAME_TYPE_HEADERS); } public Http2HeadersStreamSinkChannel(Http2Channel channel, int streamId, HeaderMap headers) { super(channel, streamId, headers, Http2Channel.FRAME_TYPE_HEADERS); } } Http2NoDataStreamSinkChannel.java000066400000000000000000000052631420065311100335750ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import org.xnio.channels.StreamSourceChannel; import io.undertow.UndertowMessages; /** * Stream sink channel that serves as the basis for channels that do not have the ability * to write data. *

* In particular these are: * - PING * - GO_AWAY * * @author Stuart Douglas */ abstract class Http2NoDataStreamSinkChannel extends AbstractHttp2StreamSinkChannel { protected Http2NoDataStreamSinkChannel(Http2Channel channel) { super(channel); } @Override public long transferFrom(FileChannel src, long position, long count) throws IOException { throw UndertowMessages.MESSAGES.controlFrameCannotHaveBodyContent(); } @Override public long transferFrom(StreamSourceChannel source, long count, ByteBuffer throughBuffer) throws IOException { throw UndertowMessages.MESSAGES.controlFrameCannotHaveBodyContent(); } @Override public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { throw UndertowMessages.MESSAGES.controlFrameCannotHaveBodyContent(); } @Override public long write(ByteBuffer[] srcs) throws IOException { throw UndertowMessages.MESSAGES.controlFrameCannotHaveBodyContent(); } @Override public int write(ByteBuffer src) throws IOException { throw UndertowMessages.MESSAGES.controlFrameCannotHaveBodyContent(); } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { throw UndertowMessages.MESSAGES.controlFrameCannotHaveBodyContent(); } @Override public long writeFinal(ByteBuffer[] srcs) throws IOException { throw UndertowMessages.MESSAGES.controlFrameCannotHaveBodyContent(); } @Override public int writeFinal(ByteBuffer src) throws IOException { throw UndertowMessages.MESSAGES.controlFrameCannotHaveBodyContent(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2PingParser.java000066400000000000000000000033631420065311100312650ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import io.undertow.UndertowMessages; import static io.undertow.protocols.http2.Http2Channel.PING_FRAME_LENGTH; import java.io.IOException; import java.nio.ByteBuffer; /** * Parser for HTTP2 ping frames. * * @author Stuart Douglas */ class Http2PingParser extends Http2PushBackParser { final byte[] data = new byte[PING_FRAME_LENGTH]; Http2PingParser(int frameLength) { super(frameLength); } @Override protected void handleData(ByteBuffer resource, Http2FrameHeaderParser parser) throws IOException { if(parser.length != PING_FRAME_LENGTH) { throw new IOException(UndertowMessages.MESSAGES.httpPingDataMustBeLength8()); } if(parser.streamId != 0) { throw new IOException(UndertowMessages.MESSAGES.streamIdMustBeZeroForFrameType(Http2Channel.FRAME_TYPE_PING)); } if (resource.remaining() < PING_FRAME_LENGTH) { return; } resource.get(data); } byte[] getData() { return data; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2PingStreamSinkChannel.java000066400000000000000000000040561420065311100334020ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import static io.undertow.protocols.http2.Http2Channel.PING_FRAME_LENGTH; import java.nio.ByteBuffer; import io.undertow.UndertowMessages; import io.undertow.server.protocol.framed.SendFrameHeader; import io.undertow.util.ImmediatePooledByteBuffer; /** * @author Stuart Douglas */ class Http2PingStreamSinkChannel extends Http2NoDataStreamSinkChannel { public static final int HEADER = (PING_FRAME_LENGTH << 8) | (Http2Channel.FRAME_TYPE_PING); private final byte[] data; private final boolean ack; protected Http2PingStreamSinkChannel(Http2Channel channel, byte[] data, boolean ack) { super(channel); if (data.length != PING_FRAME_LENGTH) { throw new IllegalArgumentException(UndertowMessages.MESSAGES.httpPingDataMustBeLength8()); } this.data = data; this.ack = ack; } @Override protected SendFrameHeader createFrameHeader() { ByteBuffer buf = ByteBuffer.allocate(17); Http2ProtocolUtils.putInt(buf, HEADER); buf.put((byte) (ack ? Http2Channel.PING_FLAG_ACK : 0)); Http2ProtocolUtils.putInt(buf, 0); //stream id, must be zero for (int i = 0; i < PING_FRAME_LENGTH; ++i) { buf.put(data[i]); } buf.flip(); return new SendFrameHeader(new ImmediatePooledByteBuffer(buf)); } } Http2PingStreamSourceChannel.java000066400000000000000000000023771420065311100336630ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; /** * A HTTP2 Ping frame * * @author Stuart Douglas */ public class Http2PingStreamSourceChannel extends AbstractHttp2StreamSourceChannel { private final byte[] data; private final boolean ack; Http2PingStreamSourceChannel(Http2Channel framedChannel, byte[] pingData, boolean ack) { super(framedChannel); this.data = pingData; this.ack = ack; lastFrame(); } public byte[] getData() { return data; } public boolean isAck() { return ack; } } Http2PrefaceStreamSinkChannel.java000066400000000000000000000024761420065311100337770ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.nio.ByteBuffer; import io.undertow.server.protocol.framed.SendFrameHeader; import io.undertow.util.ImmediatePooledByteBuffer; /** * channel implementation that sends the initial HTTP2 preface * * @author Stuart Douglas */ class Http2PrefaceStreamSinkChannel extends Http2StreamSinkChannel { Http2PrefaceStreamSinkChannel(Http2Channel channel) { super(channel, 0); } @Override protected SendFrameHeader createFrameHeaderImpl() { return new SendFrameHeader(new ImmediatePooledByteBuffer(ByteBuffer.wrap(Http2Channel.PREFACE_BYTES))); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2PriorityParser.java000066400000000000000000000034201420065311100322030ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import org.xnio.Bits; import java.nio.ByteBuffer; /** * Parser for HTTP2 window update frames * * @author Stuart Douglas */ class Http2PriorityParser extends Http2PushBackParser { private int streamDependency; private int weight; private boolean exclusive; Http2PriorityParser(int frameLength) { super(frameLength); } @Override protected void handleData(ByteBuffer resource, Http2FrameHeaderParser frameHeaderParser) { if (resource.remaining() < 5) { return; } int read = Http2ProtocolUtils.readInt(resource); if(Bits.anyAreSet(read, 1 << 31)) { exclusive = true; streamDependency = read & ~(1 << 31); } else { exclusive = false; streamDependency = read; } weight = resource.get(); } public int getWeight() { return weight; } public int getStreamDependency() { return streamDependency; } public boolean isExclusive() { return exclusive; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2PriorityTree.java000066400000000000000000000204761420065311100316600ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.util.Comparator; import java.util.HashMap; import java.util.Map; /** * A structure that represents HTTP2 priority information. * * Note that this structure is not thread safe, it is intended to be protected by an external lock * * @author Stuart Douglas */ public class Http2PriorityTree { private final Http2PriorityNode rootNode; private final Map nodesByID = new HashMap<>(); /** * fixed length queue of completed streams that have no dependents, they are kept around for a short time then expired. * */ private int[] evictionQueue; private int evictionQueuePosition; /** * The maximum number of streams that we store priority information for */ public Http2PriorityTree() { this.rootNode = new Http2PriorityNode(0, 0); nodesByID.put(0, this.rootNode); this.evictionQueue = new int[10]; //todo: make this size customisable } /** * Resisters a stream, with its dependency and dependent information * @param streamId The stream id * @param dependency The stream this stream depends on, if no stream is specified this should be zero * @param weighting The weighting. If no weighting is specified this should be 16 */ public void registerStream(int streamId, int dependency, int weighting, boolean exclusive) { final Http2PriorityNode node = new Http2PriorityNode(streamId, weighting); if(exclusive) { Http2PriorityNode existing = nodesByID.get(dependency); if(existing != null) { existing.exclusive(node); } } else { Http2PriorityNode existing = nodesByID.get(dependency); if(existing != null) { existing.addDependent(node); } } nodesByID.put(streamId, node); } /** * Method that is invoked when a stream has been removed * * @param streamId id of the stream removed */ public void streamRemoved(int streamId) { Http2PriorityNode node = nodesByID.get(streamId); if(node == null) { return; } if(!node.hasDependents()) { //add to eviction queue int toEvict = evictionQueue[evictionQueuePosition]; evictionQueue[evictionQueuePosition++] = streamId; Http2PriorityNode nodeToEvict = nodesByID.get(toEvict); //we don't remove the node if it has since got dependents since it was put into the queue //as this is the whole reason we maintain the queue in the first place if(nodeToEvict != null && !nodeToEvict.hasDependents()) { nodesByID.remove(toEvict); } } } /** * Creates a priority queue * @return */ public Comparator comparator() { return new Comparator() { @Override public int compare(Integer o1, Integer o2) { Http2PriorityNode n1 = nodesByID.get(o1); Http2PriorityNode n2 = nodesByID.get(o2); if(n1 == null && n2 == null) { return 0; } if(n1 == null) { return -1; } if(n2 == null) { return 1; } //do the comparison //this is kinda crap, but I can't really think of any better way to handle this double d1 = createWeightingProportion(n1); double d2 = createWeightingProportion(n2); return Double.compare(d1, d2); } }; } private double createWeightingProportion(Http2PriorityNode n1) { double ret = 1; Http2PriorityNode node = n1; while (node != null) { Http2PriorityNode parent = node.parent; if(parent != null) { ret *= (node.weighting/(double)parent.totalWeights); } node = parent; } return ret; } public void priorityFrame(int streamId, int streamDependency, int weight, boolean exlusive) { Http2PriorityNode existing = nodesByID.get(streamId); if(existing == null) { return; } int dif = weight - existing.weighting; existing.parent.totalWeights += dif; existing.weighting = weight; if(exlusive) { Http2PriorityNode newParent = nodesByID.get(streamDependency); if(newParent != null) { existing.parent.removeDependent(existing); newParent.exclusive(existing); } } else if(existing.parent.streamId != streamDependency) { Http2PriorityNode newParent = nodesByID.get(streamDependency); if(newParent != null) { newParent.addDependent(existing); } } } private static class Http2PriorityNode { private Http2PriorityNode parent; /** * This stream id of this node */ private final int streamId; /** * The stream weighting */ int weighting; /** * The sum of all dependencies weights */ int totalWeights; /** * streams that depend on this stream, in weighted order. May contains null at the end of the list */ private Http2PriorityNode[] dependents = null; Http2PriorityNode(int streamId, int weighting) { this.streamId = streamId; this.weighting = weighting; } void removeDependent(Http2PriorityNode node) { if(dependents == null) { return; } totalWeights -= node.weighting; boolean found = false; int i; for(i = 0; i < dependents.length - 1; ++i ) { if(dependents[i] == node) { found = true; } if(found) { dependents[i] = dependents[i + i]; } if(dependents[i] == null) { break; } } if(found) { dependents[i + 1] = null; } } boolean hasDependents() { return dependents != null && dependents[0] != null; } public void addDependent(Http2PriorityNode node) { if(dependents == null) { dependents = new Http2PriorityNode[5]; } int i = 0; boolean found = false; for(; i < dependents.length; ++i ) { if(dependents[i] == null) { found = true; break; } } if(!found) { Http2PriorityNode[] old = dependents; dependents = new Http2PriorityNode[dependents.length + 5]; System.arraycopy(old, 0, dependents, 0, old.length); ++i; } dependents[i] = node; node.parent = this; totalWeights += node.weighting; } public void exclusive(Http2PriorityNode node) { if(dependents == null) { dependents = new Http2PriorityNode[5]; } for(Http2PriorityNode i : dependents) { if(i != null) { node.addDependent(i); } } dependents[0] = node; for(int i = 1; i < dependents.length; ++ i) { dependents[i] = null; } totalWeights = node.weighting; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2ProtocolUtils.java000066400000000000000000000032011420065311100320240ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.nio.ByteBuffer; /** * @author Stuart Douglas */ class Http2ProtocolUtils { public static void putInt(final ByteBuffer buffer, int value) { buffer.put((byte) (value >> 24)); buffer.put((byte) (value >> 16)); buffer.put((byte) (value >> 8)); buffer.put((byte) value); } public static void putInt(final ByteBuffer buffer, int value, int position) { buffer.put(position, (byte) (value >> 24)); buffer.put(position + 1, (byte) (value >> 16)); buffer.put(position + 2, (byte) (value >> 8)); buffer.put(position + 3, (byte) value); } public static int readInt(ByteBuffer buffer) { int id = (buffer.get() & 0xFF) << 24; id += (buffer.get() & 0xFF) << 16; id += (buffer.get() & 0xFF) << 8; id += (buffer.get() & 0xFF); return id; } private Http2ProtocolUtils() { } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2PushBackParser.java000066400000000000000000000107221420065311100320650ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.io.IOException; import java.nio.ByteBuffer; import io.undertow.UndertowMessages; /** * Parser that supports push back when not all data can be read. * * @author Stuart Douglas */ public abstract class Http2PushBackParser { private byte[] pushedBackData; private boolean finished; private int remainingData; private final int frameLength; int cnt; public Http2PushBackParser(int frameLength) { this.remainingData = frameLength; this.frameLength = frameLength; } public void parse(ByteBuffer data, Http2FrameHeaderParser headerParser) throws IOException { int used = 0; ByteBuffer dataToParse = data; int oldLimit = data.limit(); Throwable original = null; try { if (pushedBackData != null) { int toCopy = Math.min(remainingData - pushedBackData.length, data.remaining()); dataToParse = ByteBuffer.wrap(new byte[pushedBackData.length + toCopy]); dataToParse.put(pushedBackData); data.limit(data.position() + toCopy); dataToParse.put(data); dataToParse.flip(); } if (dataToParse.remaining() > remainingData) { dataToParse.limit(dataToParse.position() + remainingData); } int rem = dataToParse.remaining(); handleData(dataToParse, headerParser); used = rem - dataToParse.remaining(); if(!isFinished() && remainingData > 0 && used == 0 && dataToParse.remaining() >= remainingData) { if(cnt++ == 100) { original = UndertowMessages.MESSAGES.parserDidNotMakeProgress(); } } } catch (Throwable t) { original = t; } finally { //it is possible that we finished the parsing without using up all the data //and the rest is to be consumed by the stream itself try { if (finished) { data.limit(oldLimit); } else { int leftOver = dataToParse.remaining(); if (leftOver > 0) { pushedBackData = new byte[leftOver]; dataToParse.get(pushedBackData); } else { pushedBackData = null; } data.limit(oldLimit); remainingData -= used; if (remainingData == 0) { finished = true; } } } catch (Throwable t) { if(original != null) { original.addSuppressed(t); } else { original = t; } } if (original != null) { if (original instanceof RuntimeException) { throw (RuntimeException) original; } if (original instanceof Error) { throw (Error) original; } if (original instanceof IOException) { throw (IOException) original; } } } } protected abstract void handleData(ByteBuffer resource, Http2FrameHeaderParser headerParser) throws IOException; public boolean isFinished() { if(pushedBackData != null && remainingData == pushedBackData.length) { return true; } return finished; } protected void finish() { finished = true; } protected void moreData(int data) { finished = false; this.remainingData += data; } public int getFrameLength() { return frameLength; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2PushPromiseParser.java000066400000000000000000000041561420065311100326470ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import org.xnio.Bits; import java.nio.ByteBuffer; /** * Parser for HTTP2 Headers frames * * @author Stuart Douglas */ class Http2PushPromiseParser extends Http2HeaderBlockParser { private int paddingLength = 0; private int promisedStreamId; private static final int STREAM_MASK = ~(1 << 7); Http2PushPromiseParser(int frameLength, HpackDecoder hpackDecoder, boolean client, int maxHeaders, int streamId, int maxHeaderListSize) { super(frameLength, hpackDecoder, client, maxHeaders, streamId, maxHeaderListSize); } @Override protected boolean handleBeforeHeader(ByteBuffer resource, Http2FrameHeaderParser headerParser) { boolean hasPadding = Bits.anyAreSet(headerParser.flags, Http2Channel.HEADERS_FLAG_PADDED); int reqLength = (hasPadding ? 1 : 0) + 4; if (resource.remaining() < reqLength) { return false; } if (hasPadding) { paddingLength = (resource.get() & 0xFF); } promisedStreamId = (resource.get() & STREAM_MASK) << 24; promisedStreamId += (resource.get() & 0xFF) << 16; promisedStreamId += (resource.get() & 0xFF) << 8; promisedStreamId += (resource.get() & 0xFF); return true; } protected int getPaddingLength() { return paddingLength; } public int getPromisedStreamId() { return promisedStreamId; } } Http2PushPromiseStreamSinkChannel.java000066400000000000000000000034551420065311100347060ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import io.undertow.util.HeaderMap; import java.nio.ByteBuffer; /** * Push promise channel * * @author Stuart Douglas */ public class Http2PushPromiseStreamSinkChannel extends Http2DataStreamSinkChannel { private final int pushedStreamId; Http2PushPromiseStreamSinkChannel(Http2Channel channel, HeaderMap requestHeaders, int associatedStreamId, int pushedStreamId) { super(channel, associatedStreamId, requestHeaders, Http2Channel.FRAME_TYPE_PUSH_PROMISE); this.pushedStreamId = pushedStreamId; } protected void writeBeforeHeaderBlock(ByteBuffer buffer) { buffer.put((byte) ((pushedStreamId >> 24) & 0xFF)); buffer.put((byte) ((pushedStreamId >> 16) & 0xFF)); buffer.put((byte) ((pushedStreamId >> 8) & 0xFF)); buffer.put((byte) (pushedStreamId & 0xFF)); } /** * this stream is not flow controlled * @param bytes * @return */ protected int grabFlowControlBytes(int bytes) { return bytes; } public int getPushedStreamId() { return pushedStreamId; } } Http2PushPromiseStreamSourceChannel.java000066400000000000000000000033071420065311100352360ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import io.undertow.util.HeaderMap; import io.undertow.connector.PooledByteBuffer; /** * A HTTP2 push promise frame * * @author Stuart Douglas */ public class Http2PushPromiseStreamSourceChannel extends AbstractHttp2StreamSourceChannel { private final HeaderMap headers; private final int pushedStreamId; private final int associatedStreamId; Http2PushPromiseStreamSourceChannel(Http2Channel framedChannel, PooledByteBuffer data, long frameDataRemaining, HeaderMap headers, int pushedStreamId, int associatedStreamId) { super(framedChannel, data, frameDataRemaining); this.headers = headers; this.pushedStreamId = pushedStreamId; this.associatedStreamId = associatedStreamId; lastFrame(); } public HeaderMap getHeaders() { return headers; } public int getPushedStreamId() { return pushedStreamId; } public int getAssociatedStreamId() { return associatedStreamId; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2RstStreamParser.java000066400000000000000000000024511420065311100323110ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.nio.ByteBuffer; /** * Parser for HTTP2 ping frames. * * @author Stuart Douglas */ class Http2RstStreamParser extends Http2PushBackParser { private int errorCode; Http2RstStreamParser(int frameLength) { super(frameLength); } @Override protected void handleData(ByteBuffer resource, Http2FrameHeaderParser headerParser) { if (resource.remaining() < 4) { return; } errorCode = Http2ProtocolUtils.readInt(resource); } public int getErrorCode() { return errorCode; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2RstStreamSinkChannel.java000066400000000000000000000033351420065311100332540ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.nio.ByteBuffer; import io.undertow.server.protocol.framed.SendFrameHeader; import io.undertow.util.ImmediatePooledByteBuffer; /** * @author Stuart Douglas */ class Http2RstStreamSinkChannel extends Http2NoDataStreamSinkChannel { public static final int HEADER_FIRST_LINE = (4 << 8) | (Http2Channel.FRAME_TYPE_RST_STREAM); private final int streamId; private final int errorCode; protected Http2RstStreamSinkChannel(Http2Channel channel, int streamId, int errorCode) { super(channel); this.errorCode = errorCode; this.streamId = streamId; } @Override protected SendFrameHeader createFrameHeader() { ByteBuffer buf = ByteBuffer.allocate(13); Http2ProtocolUtils.putInt(buf, HEADER_FIRST_LINE); buf.put((byte)0); Http2ProtocolUtils.putInt(buf, streamId); Http2ProtocolUtils.putInt(buf, errorCode); buf.flip(); return new SendFrameHeader(new ImmediatePooledByteBuffer(buf)); } } Http2RstStreamStreamSourceChannel.java000066400000000000000000000026011420065311100347000ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import io.undertow.connector.PooledByteBuffer; /** * A HTTP2 RST Stream channel * * @author Stuart Douglas */ public class Http2RstStreamStreamSourceChannel extends AbstractHttp2StreamSourceChannel { private final int errorCode; private final int streamId; Http2RstStreamStreamSourceChannel(Http2Channel framedChannel, PooledByteBuffer data, int errorCode, int streamId) { super(framedChannel, data, 0); this.errorCode = errorCode; this.streamId = streamId; lastFrame(); } public int getErrorCode() { return errorCode; } public int getStreamId() { return streamId; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2Setting.java000066400000000000000000000027011420065311100306230ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; /** * A Http2 Setting * * @author Stuart Douglas */ public class Http2Setting { public static final int SETTINGS_HEADER_TABLE_SIZE = 0x1; public static final int SETTINGS_ENABLE_PUSH = 0x2; public static final int SETTINGS_MAX_CONCURRENT_STREAMS = 0x3; public static final int SETTINGS_INITIAL_WINDOW_SIZE = 0x4; public static final int SETTINGS_MAX_FRAME_SIZE = 0x5; public static final int SETTINGS_MAX_HEADER_LIST_SIZE = 0x6; private final int id; private final long value; Http2Setting(int id, long value) { this.id = id; this.value = value; } public int getId() { return id; } public long getValue() { return value; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2SettingsParser.java000066400000000000000000000034021420065311100321620ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; /** * @author Stuart Douglas */ class Http2SettingsParser extends Http2PushBackParser { private int count = 0; private final List settings = new ArrayList<>(); Http2SettingsParser(int frameLength) { super(frameLength); } @Override protected void handleData(ByteBuffer resource, Http2FrameHeaderParser parser) { while (count < parser.length) { if (resource.remaining() < 6) { return; } int id = (resource.get() & 0xFF) << 8; id += (resource.get() & 0xFF); long value = (resource.get() & 0xFFL) << 24; value += (resource.get() & 0xFFL) << 16; value += (resource.get() & 0xFFL) << 8; value += (resource.get() & 0xFFL); settings.add(new Http2Setting(id, value)); count += 6; } } public List getSettings() { return settings; } } Http2SettingsStreamSinkChannel.java000066400000000000000000000057611420065311100342320ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.nio.ByteBuffer; import java.util.List; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.protocol.framed.SendFrameHeader; /** * //TODO: ack * * @author Stuart Douglas */ public class Http2SettingsStreamSinkChannel extends Http2StreamSinkChannel { private final List settings; Http2SettingsStreamSinkChannel(Http2Channel channel, List settings) { super(channel, 0); this.settings = settings; } /** * //an ack frame * * @param channel */ Http2SettingsStreamSinkChannel(Http2Channel channel) { super(channel, 0); this.settings = null; } @Override protected SendFrameHeader createFrameHeaderImpl() { PooledByteBuffer pooled = getChannel().getBufferPool().allocate(); ByteBuffer currentBuffer = pooled.getBuffer(); if (settings != null) { int size = settings.size() * 6; currentBuffer.put((byte) ((size >> 16) & 0xFF)); currentBuffer.put((byte) ((size >> 8) & 0xFF)); currentBuffer.put((byte) (size & 0xFF)); currentBuffer.put((byte) Http2Channel.FRAME_TYPE_SETTINGS); //type currentBuffer.put((byte) 0); //flags Http2ProtocolUtils.putInt(currentBuffer, getStreamId()); for (Http2Setting setting : settings) { currentBuffer.put((byte) ((setting.getId() >> 8) & 0xFF)); currentBuffer.put((byte) (setting.getId() & 0xFF)); currentBuffer.put((byte) ((setting.getValue() >> 24) & 0xFF)); currentBuffer.put((byte) ((setting.getValue() >> 16) & 0xFF)); currentBuffer.put((byte) ((setting.getValue() >> 8) & 0xFF)); currentBuffer.put((byte) (setting.getValue() & 0xFF)); } } else { currentBuffer.put((byte) 0); currentBuffer.put((byte) 0); currentBuffer.put((byte) 0); currentBuffer.put((byte) Http2Channel.FRAME_TYPE_SETTINGS); //type currentBuffer.put((byte) Http2Channel.SETTINGS_FLAG_ACK); //flags Http2ProtocolUtils.putInt(currentBuffer, getStreamId()); } currentBuffer.flip(); return new SendFrameHeader(pooled); } } Http2SettingsStreamSourceChannel.java000066400000000000000000000026211420065311100345560ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.util.Collections; import java.util.List; import io.undertow.connector.PooledByteBuffer; /** * A HTTP2 Settings frame * * @author Stuart Douglas */ public class Http2SettingsStreamSourceChannel extends AbstractHttp2StreamSourceChannel { private final List settings; Http2SettingsStreamSourceChannel(Http2Channel framedChannel, PooledByteBuffer data, long frameDataRemaining, List settings) { super(framedChannel, data, frameDataRemaining); this.settings = settings; lastFrame(); } public List getSettings() { return Collections.unmodifiableList(settings); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2Stream.java000066400000000000000000000015121420065311100304400ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; /** * @author Stuart Douglas */ public interface Http2Stream { int getStreamId(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2StreamSinkChannel.java000066400000000000000000000162031420065311100325610ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.io.IOException; import java.nio.ByteBuffer; import io.undertow.UndertowMessages; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.protocol.framed.SendFrameHeader; import org.xnio.IoUtils; /** * @author Stuart Douglas */ public abstract class Http2StreamSinkChannel extends AbstractHttp2StreamSinkChannel { private final int streamId; private volatile boolean reset = false; //flow control related items. Accessed under lock private int flowControlWindow; private int initialWindowSize; //we track the initial window size, and then re-query it to get any delta private SendFrameHeader header; private static final Object flowControlLock = new Object(); Http2StreamSinkChannel(Http2Channel channel, int streamId) { super(channel); this.streamId = streamId; this.flowControlWindow = channel.getInitialSendWindowSize(); this.initialWindowSize = this.flowControlWindow; } public int getStreamId() { return streamId; } SendFrameHeader generateSendFrameHeader() { header = createFrameHeaderImpl(); return header; } protected abstract SendFrameHeader createFrameHeaderImpl(); void clearHeader() { this.header = null; } @Override protected void channelForciblyClosed() throws IOException { getChannel().removeStreamSink(getStreamId()); if (reset) { return; } reset = true; if (streamId % 2 == (getChannel().isClient() ? 1 : 0)) { //we initiated the stream //we only actually reset if we have sent something to the other endpoint if (isFirstDataWritten() && !getChannel().isThisGoneAway()) { getChannel().sendRstStream(streamId, Http2Channel.ERROR_CANCEL); } } else if(!getChannel().isThisGoneAway()) { getChannel().sendRstStream(streamId, Http2Channel.ERROR_STREAM_CLOSED); } markBroken(); } @Override protected final SendFrameHeader createFrameHeader() { SendFrameHeader header = this.header; this.header = null; return header; } @Override protected void handleFlushComplete(boolean channelClosed) { if (channelClosed) { getChannel().removeStreamSink(getStreamId()); } if(reset) { IoUtils.safeClose(this); } } /** * This method should be called before sending. It will return the amount of * data that can be sent, taking into account the stream and connection flow * control windows, and the toSend parameter. *

* It will decrement the flow control windows by the amount that can be sent, * so this method should only be called as a frame is being queued. * * @return The number of bytes that can be sent */ protected int grabFlowControlBytes(int toSend) { synchronized (flowControlLock) { if (toSend == 0) { return 0; } int newWindowSize = this.getChannel().getInitialSendWindowSize(); int settingsDelta = newWindowSize - this.initialWindowSize; //first adjust for any settings frame updates this.initialWindowSize = newWindowSize; this.flowControlWindow += settingsDelta; int min = Math.min(toSend, this.flowControlWindow); int actualBytes = this.getChannel().grabFlowControlBytes(min); this.flowControlWindow -= actualBytes; return actualBytes; } } void updateFlowControlWindow(final int delta) throws IOException { boolean exhausted; synchronized (flowControlLock) { exhausted = flowControlWindow <= 0; long ld = delta; ld += flowControlWindow; if (ld > Integer.MAX_VALUE) { getChannel().sendRstStream(streamId, Http2Channel.ERROR_FLOW_CONTROL_ERROR); markBroken(); return; } flowControlWindow += delta; } if (exhausted) { getChannel().notifyFlowControlAllowed(); if (isWriteResumed()) { resumeWritesInternal(true); } } } protected PooledByteBuffer[] allocateAll(PooledByteBuffer[] allHeaderBuffers, PooledByteBuffer currentBuffer) { PooledByteBuffer[] ret; if (allHeaderBuffers == null) { ret = new PooledByteBuffer[2]; ret[0] = currentBuffer; ret[1] = getChannel().getBufferPool().allocate(); ByteBuffer newBuffer = ret[1].getBuffer(); if(newBuffer.remaining() > getChannel().getSendMaxFrameSize()) { newBuffer.limit(newBuffer.position() + getChannel().getSendMaxFrameSize()); //make sure the buffers are not too large to go over the max frame size } } else { ret = new PooledByteBuffer[allHeaderBuffers.length + 1]; System.arraycopy(allHeaderBuffers, 0, ret, 0, allHeaderBuffers.length); ret[ret.length - 1] = getChannel().getBufferPool().allocate(); ByteBuffer newBuffer = ret[ret.length - 1].getBuffer(); if(newBuffer.remaining() > getChannel().getSendMaxFrameSize()) { newBuffer.limit(newBuffer.position() + getChannel().getSendMaxFrameSize()); } } return ret; } /** * Invokes super awaitWritable, with an extra check for flowControlWindow. The purpose of this is to * warn clearly that peer is not updating the flow control window. * * @throws IOException if an IO error occurs */ public void awaitWritable() throws IOException { final int flowControlWindow; synchronized (flowControlLock) { flowControlWindow = this.flowControlWindow; } super.awaitWritable(); synchronized (flowControlLock) { if (isReadyForFlush() && flowControlWindow <= 0 && flowControlWindow == this.flowControlWindow) { throw UndertowMessages.MESSAGES.noWindowUpdate(getAwaitWritableTimeout()); } } } /** * Method that is invoked when the stream is reset. */ void rstStream() { if (reset) { return; } reset = true; if(!isReadyForFlush()) { IoUtils.safeClose(this); } getChannel().removeStreamSink(getStreamId()); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2StreamSourceChannel.java000066400000000000000000000251301420065311100331140ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import org.xnio.Bits; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import io.undertow.UndertowLogger; import io.undertow.connector.PooledByteBuffer; import org.xnio.IoUtils; import org.xnio.channels.StreamSinkChannel; import io.undertow.server.protocol.framed.FrameHeaderData; import io.undertow.util.HeaderMap; import io.undertow.util.Headers; /** * @author Stuart Douglas */ public class Http2StreamSourceChannel extends AbstractHttp2StreamSourceChannel implements Http2Stream{ /** * Flag that is set if the headers frame has the end stream flag set, but not end headers * which means the last continuation frame is the end of the stream. */ private boolean headersEndStream = false; private boolean rst = false; private final HeaderMap headers; private final int streamId; private Http2HeadersStreamSinkChannel response; private int flowControlWindow; private ChannelListener completionListener; private int remainingPadding; /** * This is a bit of a hack, basically it allows the container to delay sending a RST_STREAM on a channel that is knows is broken, * because it wants to delay the RST until after the response has been set * * Used for handling the super nasty 100-continue logic */ private boolean ignoreForceClose = false; private long contentLengthRemaining; private TrailersHandler trailersHandler; Http2StreamSourceChannel(Http2Channel framedChannel, PooledByteBuffer data, long frameDataRemaining, HeaderMap headers, int streamId) { super(framedChannel, data, frameDataRemaining); this.headers = headers; this.streamId = streamId; this.flowControlWindow = framedChannel.getInitialReceiveWindowSize(); String contentLengthString = headers.getFirst(Headers.CONTENT_LENGTH); if(contentLengthString != null) { contentLengthRemaining = Long.parseLong(contentLengthString); } else { contentLengthRemaining = -1; } } @Override protected void handleHeaderData(FrameHeaderData headerData) { Http2FrameHeaderParser data = (Http2FrameHeaderParser) headerData; Http2PushBackParser parser = data.getParser(); if(parser instanceof Http2DataFrameParser) { remainingPadding = ((Http2DataFrameParser) parser).getPadding(); if(remainingPadding > 0) { try { updateFlowControlWindow(remainingPadding + 1); } catch (IOException e) { IoUtils.safeClose(getFramedChannel()); throw new RuntimeException(e); } } } else if(parser instanceof Http2HeadersParser) { if(trailersHandler != null) { trailersHandler.handleTrailers(((Http2HeadersParser) parser).getHeaderMap()); } } handleFinalFrame(data); } @Override protected long updateFrameDataRemaining(PooledByteBuffer data, long frameDataRemaining) { long actualDataRemaining = frameDataRemaining - remainingPadding; if(data.getBuffer().remaining() > actualDataRemaining) { long paddingThisBuffer = data.getBuffer().remaining() - actualDataRemaining; data.getBuffer().limit((int) (data.getBuffer().position() + actualDataRemaining)); remainingPadding -= paddingThisBuffer; return frameDataRemaining - paddingThisBuffer; } return frameDataRemaining; } void handleFinalFrame(Http2FrameHeaderParser headerData) { Http2FrameHeaderParser data = headerData; if (data.type == Http2Channel.FRAME_TYPE_DATA) { if (Bits.anyAreSet(data.flags, Http2Channel.DATA_FLAG_END_STREAM)) { this.lastFrame(); } } else if (data.type == Http2Channel.FRAME_TYPE_HEADERS) { if (Bits.allAreSet(data.flags, Http2Channel.HEADERS_FLAG_END_STREAM)) { if (Bits.allAreSet(data.flags, Http2Channel.HEADERS_FLAG_END_HEADERS)) { this.lastFrame(); } else { //continuation frames are coming, then we end the stream headersEndStream = true; } } } else if (headersEndStream && data.type == Http2Channel.FRAME_TYPE_CONTINUATION) { if (Bits.anyAreSet(data.flags, Http2Channel.CONTINUATION_FLAG_END_HEADERS)) { this.lastFrame(); } } } public Http2HeadersStreamSinkChannel getResponseChannel() { if (response != null) { return response; } response = new Http2HeadersStreamSinkChannel(getHttp2Channel(), streamId); getHttp2Channel().registerStreamSink(response); return response; } @Override public int read(ByteBuffer dst) throws IOException { int read = super.read(dst); updateFlowControlWindow(read); return read; } @Override public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { long read = super.read(dsts, offset, length); updateFlowControlWindow((int) read); return read; } @Override public long read(ByteBuffer[] dsts) throws IOException { long read = super.read(dsts); updateFlowControlWindow((int) read); return read; } @Override public long transferTo(long count, ByteBuffer throughBuffer, StreamSinkChannel streamSinkChannel) throws IOException { long read = super.transferTo(count, throughBuffer, streamSinkChannel); updateFlowControlWindow((int) read + throughBuffer.remaining()); return read; } @Override public long transferTo(long position, long count, FileChannel target) throws IOException { long read = super.transferTo(position, count, target); updateFlowControlWindow((int) read); return read; } private void updateFlowControlWindow(final int read) throws IOException { if (read <= 0) { return; } flowControlWindow -= read; //TODO: RST stream if flow control limits are exceeded? //TODO: make this configurable, we should be able to set the policy that is used to determine when to update the window size Http2Channel http2Channel = getHttp2Channel(); http2Channel.updateReceiveFlowControlWindow(read); int initialWindowSize = http2Channel.getInitialReceiveWindowSize(); //TODO: this is not great, as we may have already received all the data so there is no need, need to have a way to figure out if all data is buffered if (flowControlWindow < (initialWindowSize / 2)) { int delta = initialWindowSize - flowControlWindow; flowControlWindow += delta; http2Channel.sendUpdateWindowSize(streamId, delta); } } @Override protected void complete() throws IOException { super.complete(); if (completionListener != null) { ChannelListeners.invokeChannelListener(this, completionListener); } } public HeaderMap getHeaders() { return headers; } public ChannelListener getCompletionListener() { return completionListener; } public void setCompletionListener(ChannelListener completionListener) { this.completionListener = completionListener; if(isComplete()) { ChannelListeners.invokeChannelListener(this, completionListener); } } @Override void rstStream(int error) { if (rst) { return; } rst = true; markStreamBroken(); } @Override protected void channelForciblyClosed() { if (completionListener != null) { completionListener.handleEvent(this); } if(!ignoreForceClose) { getHttp2Channel().sendRstStream(streamId, Http2Channel.ERROR_CANCEL); } markStreamBroken(); } public void setIgnoreForceClose(boolean ignoreForceClose) { this.ignoreForceClose = ignoreForceClose; } public boolean isIgnoreForceClose() { return ignoreForceClose; } public int getStreamId() { return streamId; } boolean isHeadersEndStream() { return headersEndStream; } public TrailersHandler getTrailersHandler() { return trailersHandler; } public void setTrailersHandler(TrailersHandler trailersHandler) { this.trailersHandler = trailersHandler; } @Override public String toString() { return "Http2StreamSourceChannel{" + "headers=" + headers + '}'; } /** * Checks that the actual content size matches the expected. We check this proactivly, rather than as the data is read * @param frameLength The amount of data in the frame * @param last If this is the last frame */ void updateContentSize(long frameLength, boolean last) { if(contentLengthRemaining != -1) { contentLengthRemaining -= frameLength; if(contentLengthRemaining < 0) { UndertowLogger.REQUEST_IO_LOGGER.debugf("Closing stream %s on %s as data length exceeds content size", streamId, getFramedChannel()); getFramedChannel().sendRstStream(streamId, Http2Channel.ERROR_PROTOCOL_ERROR); } else if(last && contentLengthRemaining != 0) { UndertowLogger.REQUEST_IO_LOGGER.debugf("Closing stream %s on %s as data length was less than content size", streamId, getFramedChannel()); getFramedChannel().sendRstStream(streamId, Http2Channel.ERROR_PROTOCOL_ERROR); } } } public interface TrailersHandler { void handleTrailers(HeaderMap headerMap); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/Http2WindowUpdateParser.java000066400000000000000000000025241420065311100330000ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.nio.ByteBuffer; /** * Parser for HTTP2 window update frames * * @author Stuart Douglas */ class Http2WindowUpdateParser extends Http2PushBackParser { private int deltaWindowSize; Http2WindowUpdateParser(int frameLength) { super(frameLength); } @Override protected void handleData(ByteBuffer resource, Http2FrameHeaderParser frameHeaderParser) { if (resource.remaining() < 4) { return; } deltaWindowSize = Http2ProtocolUtils.readInt(resource); } public int getDeltaWindowSize() { return deltaWindowSize; } } Http2WindowUpdateStreamSinkChannel.java000066400000000000000000000035501420065311100350360ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.nio.ByteBuffer; import io.undertow.server.protocol.framed.SendFrameHeader; import io.undertow.util.ImmediatePooledByteBuffer; /** * A window update frame. * * @author Stuart Douglas */ class Http2WindowUpdateStreamSinkChannel extends Http2NoDataStreamSinkChannel { //length (4) and frame type. There are never any flags public static final int HEADER_FIRST_LINE = (4 << 8) | (Http2Channel.FRAME_TYPE_WINDOW_UPDATE); private final int streamId; private final int deltaWindowSize; protected Http2WindowUpdateStreamSinkChannel(Http2Channel channel, int streamId, int deltaWindowSize) { super(channel); this.streamId = streamId; this.deltaWindowSize = deltaWindowSize; } @Override protected SendFrameHeader createFrameHeader() { ByteBuffer buf = ByteBuffer.allocate(13); Http2ProtocolUtils.putInt(buf, HEADER_FIRST_LINE); buf.put((byte)0); Http2ProtocolUtils.putInt(buf, streamId); Http2ProtocolUtils.putInt(buf, deltaWindowSize); buf.flip(); return new SendFrameHeader(new ImmediatePooledByteBuffer(buf)); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/http2/StreamErrorException.java000066400000000000000000000020331420065311100324060ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import java.io.IOException; /** * @author Stuart Douglas */ public class StreamErrorException extends IOException { private final int errorId; public StreamErrorException(int errorId) { this.errorId = errorId; } public int getErrorId() { return errorId; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ssl/000077500000000000000000000000001420065311100251615ustar00rootroot00000000000000ALPNHackClientByteArrayOutputStream.java000066400000000000000000000060071420065311100346500ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ssl/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.ssl; import javax.net.ssl.SSLEngine; import java.io.ByteArrayOutputStream; /** * Super hacky class that allows the client and server hello message to be modified and the corresponding hash generated * at runtime. * * * @author Stuart Douglas */ class ALPNHackClientByteArrayOutputStream extends ByteArrayOutputStream { private final SSLEngine sslEngine; private boolean ready = true; /** * the server hello that was sent over the wire, before we messed with it */ private byte[] receivedServerHello; private byte[] sentClientHello; ALPNHackClientByteArrayOutputStream(SSLEngine sslEngine) { this.sslEngine = sslEngine; } @Override public void write(byte[] b, int off, int len) { if(ready) { if(b[off] == 2) { // server hello ready = false; //we are done processing byte[] newData; if(receivedServerHello != null) { int b1 = b[off + 1]; int b2 = b[off + 2]; int b3 = b[off + 3]; int length = (b1 & 0xFF) << 16 | (b2 & 0xFF) << 8 | b3 & 0xFF; if(length + 4 == len) { newData = receivedServerHello; } else { newData = new byte[receivedServerHello.length + len - 4 - length]; System.arraycopy(receivedServerHello, 0, newData, 0, receivedServerHello.length); System.arraycopy(b, length + 4, newData, receivedServerHello.length, len - 4 -length); } } else { newData = new byte[len]; System.arraycopy(b, off, newData, 0, len); } ALPNHackSSLEngine.regenerateHashes(sslEngine, this, sentClientHello, newData); return; } } super.write(b, off, len); } byte[] getSentClientHello() { return sentClientHello; } void setSentClientHello(byte[] sentClientHello) { this.sentClientHello = sentClientHello; } byte[] getReceivedServerHello() { return receivedServerHello; } void setReceivedServerHello(byte[] receivedServerHello) { this.receivedServerHello = receivedServerHello; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ssl/ALPNHackClientHelloExplorer.java000066400000000000000000000355251420065311100332230ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.ssl; import io.undertow.UndertowMessages; import javax.net.ssl.SSLException; import java.io.ByteArrayOutputStream; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; /** * This class is used to both read and write the ALPN protocol names in the ClientHello SSL message. * * If the out parameter is not null then the read function is being used, while if it present then it is rewriting * the hello message to include ALPN. * * Even though this dual approach is not particularly clean it does remove the need to have two versions of each function, * that do almost exactly the same thing. * */ final class ALPNHackClientHelloExplorer { // Private constructor prevents construction outside this class. private ALPNHackClientHelloExplorer() { } /** * The header size of TLS/SSL records. *

* The value of this constant is {@value}. */ public static final int RECORD_HEADER_SIZE = 0x05; /** * * */ static List exploreClientHello(ByteBuffer source) throws SSLException { ByteBuffer input = source.duplicate(); // Do we have a complete header? if (input.remaining() < RECORD_HEADER_SIZE) { throw new BufferUnderflowException(); } List alpnProtocols = new ArrayList<>(); // Is it a handshake message? byte firstByte = input.get(); byte secondByte = input.get(); byte thirdByte = input.get(); if ((firstByte & 0x80) != 0 && thirdByte == 0x01) { // looks like a V2ClientHello, we ignore it. return null; } else if (firstByte == 22) { // 22: handshake record if(secondByte == 3 && thirdByte >= 1 && thirdByte <= 3) { exploreTLSRecord(input, firstByte, secondByte, thirdByte, alpnProtocols, null); return alpnProtocols; } return null; } else { throw UndertowMessages.MESSAGES.notHandshakeRecord(); } } static byte[] rewriteClientHello(byte[] source, List alpnProtocols) throws SSLException { ByteBuffer input = ByteBuffer.wrap(source); ByteArrayOutputStream out = new ByteArrayOutputStream(); // Do we have a complete header? if (input.remaining() < RECORD_HEADER_SIZE) { throw new BufferUnderflowException(); } try { // Is it a handshake message? byte firstByte = input.get(); byte secondByte = input.get(); byte thirdByte = input.get(); out.write(firstByte & 0xFF); out.write(secondByte & 0xFF); out.write(thirdByte & 0xFF); if ((firstByte & 0x80) != 0 && thirdByte == 0x01) { // looks like a V2ClientHello, we ignore it. return null; } else if (firstByte == 22) { // 22: handshake record if (secondByte == 3 && thirdByte == 3) { //TLS1.2 is the only one we care about. Previous versions can't use HTTP/2, newer versions won't be backported to JDK8 exploreTLSRecord(input, firstByte, secondByte, thirdByte, alpnProtocols, out); //we need to adjust the record length; int clientHelloLength = out.size() - 9; byte[] data = out.toByteArray(); int newLength = data.length - 5; data[3] = (byte) ((newLength >> 8) & 0xFF); data[4] = (byte) (newLength & 0xFF); //now we need to adjust the handshake frame length data[6] = (byte) ((clientHelloLength >> 16) & 0xFF); data[7] = (byte) ((clientHelloLength >> 8) & 0xFF); data[8] = (byte) (clientHelloLength & 0xFF); return data; } return null; } else { throw UndertowMessages.MESSAGES.notHandshakeRecord(); } } catch (ALPNPresentException e) { return null; } } /* * struct { * uint8 major; * uint8 minor; * } ProtocolVersion; * * enum { * change_cipher_spec(20), alert(21), handshake(22), * application_data(23), (255) * } ContentType; * * struct { * ContentType type; * ProtocolVersion version; * uint16 length; * opaque fragment[TLSPlaintext.length]; * } TLSPlaintext; */ private static void exploreTLSRecord( ByteBuffer input, byte firstByte, byte secondByte, byte thirdByte, List alpnProtocols, ByteArrayOutputStream out) throws SSLException { // Is it a handshake message? if (firstByte != 22) { // 22: handshake record throw UndertowMessages.MESSAGES.notHandshakeRecord(); } // Is there enough data for a full record? int recordLength = getInt16(input); if (recordLength > input.remaining()) { throw new BufferUnderflowException(); } if(out != null) { out.write(0); out.write(0); } // We have already had enough source bytes. try { exploreHandshake(input, secondByte, thirdByte, recordLength, alpnProtocols, out); } catch (BufferUnderflowException ignored) { throw UndertowMessages.MESSAGES.invalidHandshakeRecord(); } } /* * enum { * hello_request(0), client_hello(1), server_hello(2), * certificate(11), server_key_exchange (12), * certificate_request(13), server_hello_done(14), * certificate_verify(15), client_key_exchange(16), * finished(20) * (255) * } HandshakeType; * * struct { * HandshakeType msg_type; * uint24 length; * select (HandshakeType) { * case hello_request: HelloRequest; * case client_hello: ClientHello; * case server_hello: ServerHello; * case certificate: Certificate; * case server_key_exchange: ServerKeyExchange; * case certificate_request: CertificateRequest; * case server_hello_done: ServerHelloDone; * case certificate_verify: CertificateVerify; * case client_key_exchange: ClientKeyExchange; * case finished: Finished; * } body; * } Handshake; */ private static void exploreHandshake( ByteBuffer input, byte recordMajorVersion, byte recordMinorVersion, int recordLength, List alpnProtocols, ByteArrayOutputStream out) throws SSLException { // What is the handshake type? byte handshakeType = input.get(); if (handshakeType != 0x01) { // 0x01: client_hello message throw UndertowMessages.MESSAGES.expectedClientHello(); } if(out != null) { out.write(handshakeType & 0xFF); } // What is the handshake body length? int handshakeLength = getInt24(input); if(out != null) { //placeholder out.write(0); out.write(0); out.write(0); } // Theoretically, a single handshake message might span multiple // records, but in practice this does not occur. if (handshakeLength > recordLength - 4) { // 4: handshake header size throw UndertowMessages.MESSAGES.multiRecordSSLHandshake(); } input = input.duplicate(); input.limit(handshakeLength + input.position()); exploreClientHello(input, alpnProtocols, out); } /* * struct { * uint32 gmt_unix_time; * opaque random_bytes[28]; * } Random; * * opaque SessionID<0..32>; * * uint8 CipherSuite[2]; * * enum { null(0), (255) } CompressionMethod; * * struct { * ProtocolVersion client_version; * Random random; * SessionID session_id; * CipherSuite cipher_suites<2..2^16-2>; * CompressionMethod compression_methods<1..2^8-1>; * select (extensions_present) { * case false: * struct {}; * case true: * Extension extensions<0..2^16-1>; * }; * } ClientHello; */ private static void exploreClientHello( ByteBuffer input, List alpnProtocols, ByteArrayOutputStream out) throws SSLException { // client version byte helloMajorVersion = input.get(); byte helloMinorVersion = input.get(); if(out != null) { out.write(helloMajorVersion & 0xFF); out.write(helloMinorVersion & 0xFF); } if(helloMajorVersion != 3 && helloMinorVersion != 3) { //we only care about TLS 1.2 return; } // ignore random for(int i = 0; i < 32; ++i) {// 32: the length of Random byte d = input.get(); if(out != null) { out.write(d & 0xFF); } } // session id processByteVector8(input, out); // cipher_suites processByteVector16(input, out); // compression methods processByteVector8(input, out); if (input.remaining() > 0) { exploreExtensions(input, alpnProtocols, out); } else if(out != null) { byte[] data = generateAlpnExtension(alpnProtocols); writeInt16(out, data.length); out.write(data, 0, data.length); } } private static void writeInt16(ByteArrayOutputStream out, int l) { if(out == null) return; out.write((l >> 8) & 0xFF); out.write(l & 0xFF); } private static byte[] generateAlpnExtension(List alpnProtocols) { ByteArrayOutputStream alpnBits = new ByteArrayOutputStream(); alpnBits.write(0); alpnBits.write(16); //ALPN type int length = 2; for(String p : alpnProtocols) { length++; length += p.length(); } writeInt16(alpnBits, length); length -= 2; writeInt16(alpnBits, length); for(String p : alpnProtocols) { alpnBits.write(p.length() & 0xFF); for (int i = 0; i < p.length(); ++i) { alpnBits.write(p.charAt(i) & 0xFF); } } return alpnBits.toByteArray(); } /* * struct { * ExtensionType extension_type; * opaque extension_data<0..2^16-1>; * } Extension; * * enum { * server_name(0), max_fragment_length(1), * client_certificate_url(2), trusted_ca_keys(3), * truncated_hmac(4), status_request(5), (65535) * } ExtensionType; */ private static void exploreExtensions(ByteBuffer input, List alpnProtocols, ByteArrayOutputStream out) throws SSLException { ByteArrayOutputStream extensionOut = out == null ? null : new ByteArrayOutputStream(); int length = getInt16(input); // length of extensions writeInt16(extensionOut, 0); //placeholder while (length > 0) { int extType = getInt16(input); // extenson type writeInt16(extensionOut, extType); int extLen = getInt16(input); // length of extension data writeInt16(extensionOut, extLen); if (extType == 16) { // 0x00: ty if(out == null) { exploreALPNExt(input, alpnProtocols); } else { throw new ALPNPresentException(); } } else { // ignore other extensions processByteVector(input, extLen, extensionOut); } length -= extLen + 4; } if(out != null) { byte[] alpnBits = generateAlpnExtension(alpnProtocols); extensionOut.write(alpnBits,0 ,alpnBits.length); byte[] extensionsData = extensionOut.toByteArray(); int newLength = extensionsData.length - 2; extensionsData[0] = (byte) ((newLength >> 8) & 0xFF); extensionsData[1] = (byte) (newLength & 0xFF); out.write(extensionsData, 0, extensionsData.length); } } private static void exploreALPNExt(ByteBuffer input, List alpnProtocols) { int length = getInt16(input); int end = input.position() + length; while (input.position() < end) { alpnProtocols.add(readByteVector8(input)); } } private static int getInt8(ByteBuffer input) { return input.get(); } private static int getInt16(ByteBuffer input) { return (input.get() & 0xFF) << 8 | input.get() & 0xFF; } private static int getInt24(ByteBuffer input) { return (input.get() & 0xFF) << 16 | (input.get() & 0xFF) << 8 | input.get() & 0xFF; } private static void processByteVector8(ByteBuffer input, ByteArrayOutputStream out) { int int8 = getInt8(input); if(out != null) { out.write(int8 & 0xFF); } processByteVector(input, int8, out); } private static void processByteVector(ByteBuffer input, int length, ByteArrayOutputStream out) { for (int i = 0; i < length; ++i) { byte b = input.get(); if(out != null) { out.write(b & 0xFF); } } } private static String readByteVector8(ByteBuffer input) { int length = getInt8(input); byte[] data = new byte[length]; input.get(data); return new String(data, StandardCharsets.US_ASCII); } private static void processByteVector16(ByteBuffer input, ByteArrayOutputStream out) { int int16 = getInt16(input); writeInt16(out, int16); processByteVector(input, int16, out); } private static final class ALPNPresentException extends RuntimeException { } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ssl/ALPNHackSSLEngine.java000066400000000000000000000446531420065311100310710ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.ssl; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import java.io.ByteArrayOutputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSession; /** * SSLEngine wrapper that provides some super hacky ALPN support on JDK8. * * Even though this is a nasty hack that relies on JDK internals it is still preferable to modifying the boot class path. * * It is expected to work with all JDK8 versions, however this cannot be guaranteed if the SSL internals are changed * in an incompatible way. * * This class will go away once JDK8 is no longer in use. * * @author Stuart Douglas */ public class ALPNHackSSLEngine extends SSLEngine { public static final boolean ENABLED; private static final Field HANDSHAKER; private static final Field HANDSHAKER_PROTOCOL_VERSION; private static final Field HANDSHAKE_HASH; private static final Field HANDSHAKE_HASH_VERSION; private static final Method HANDSHAKE_HASH_UPDATE; private static final Method HANDSHAKE_HASH_PROTOCOL_DETERMINED; private static final Field HANDSHAKE_HASH_DATA; private static final Field HANDSHAKE_HASH_FIN_MD; private static final Class SSL_ENGINE_IMPL_CLASS; static { boolean enabled = true; Field handshaker; Field handshakeHash; Field handshakeHashVersion; Field handshakeHashData; Field handshakeHashFinMd; Field protocolVersion; Method handshakeHashUpdate; Method handshakeHashProtocolDetermined; Class sslEngineImpleClass; try { Class protocolVersionClass = Class.forName("sun.security.ssl.ProtocolVersion", true, ClassLoader.getSystemClassLoader()); sslEngineImpleClass = Class.forName("sun.security.ssl.SSLEngineImpl", true, ClassLoader.getSystemClassLoader()); handshaker = sslEngineImpleClass.getDeclaredField("handshaker"); handshaker.setAccessible(true); handshakeHash = handshaker.getType().getDeclaredField("handshakeHash"); handshakeHash.setAccessible(true); protocolVersion = handshaker.getType().getDeclaredField("protocolVersion"); protocolVersion.setAccessible(true); handshakeHashVersion = handshakeHash.getType().getDeclaredField("version"); handshakeHashVersion.setAccessible(true); handshakeHashUpdate = handshakeHash.getType().getDeclaredMethod("update", byte[].class, int.class, int.class); handshakeHashUpdate.setAccessible(true); handshakeHashProtocolDetermined = handshakeHash.getType().getDeclaredMethod("protocolDetermined", protocolVersionClass); handshakeHashProtocolDetermined.setAccessible(true); handshakeHashData = handshakeHash.getType().getDeclaredField("data"); handshakeHashData.setAccessible(true); handshakeHashFinMd = handshakeHash.getType().getDeclaredField("finMD"); handshakeHashFinMd.setAccessible(true); } catch (Exception e) { UndertowLogger.ROOT_LOGGER.debug("JDK8 ALPN Hack failed ", e); enabled = false; handshaker = null; handshakeHash = null; handshakeHashVersion = null; handshakeHashUpdate = null; handshakeHashProtocolDetermined = null; handshakeHashData = null; handshakeHashFinMd = null; protocolVersion = null; sslEngineImpleClass = null; } ENABLED = enabled && !Boolean.getBoolean("io.undertow.disable-jdk8-alpn"); HANDSHAKER = handshaker; HANDSHAKE_HASH = handshakeHash; HANDSHAKE_HASH_PROTOCOL_DETERMINED = handshakeHashProtocolDetermined; HANDSHAKE_HASH_VERSION = handshakeHashVersion; HANDSHAKE_HASH_UPDATE = handshakeHashUpdate; HANDSHAKE_HASH_DATA = handshakeHashData; HANDSHAKE_HASH_FIN_MD = handshakeHashFinMd; HANDSHAKER_PROTOCOL_VERSION = protocolVersion; SSL_ENGINE_IMPL_CLASS = sslEngineImpleClass; } private final SSLEngine delegate; //ALPN Hack specific variables private boolean unwrapHelloSeen = false; private boolean ourHelloSent = false; private ALPNHackServerByteArrayOutputStream alpnHackServerByteArrayOutputStream; private ALPNHackClientByteArrayOutputStream ALPNHackClientByteArrayOutputStream; private List applicationProtocols; private String selectedApplicationProtocol; private ByteBuffer bufferedWrapData; public ALPNHackSSLEngine(SSLEngine delegate) { this.delegate = delegate; } public static boolean isEnabled(SSLEngine engine) { if(!ENABLED) { return false; } return SSL_ENGINE_IMPL_CLASS.isAssignableFrom(engine.getClass()); } @Override public SSLEngineResult wrap(ByteBuffer[] byteBuffers, int i, int i1, ByteBuffer byteBuffer) throws SSLException { if(bufferedWrapData != null) { int prod = bufferedWrapData.remaining(); byteBuffer.put(bufferedWrapData); bufferedWrapData = null; return new SSLEngineResult(SSLEngineResult.Status.OK, SSLEngineResult.HandshakeStatus.NEED_WRAP, 0, prod); } int pos = byteBuffer.position(); int limit = byteBuffer.limit(); SSLEngineResult res = delegate.wrap(byteBuffers, i, i1, byteBuffer); if(!ourHelloSent && res.bytesProduced() > 0) { if(delegate.getUseClientMode() && applicationProtocols != null && !applicationProtocols.isEmpty()) { ourHelloSent = true; ALPNHackClientByteArrayOutputStream = replaceClientByteOutput(delegate); ByteBuffer newBuf = byteBuffer.duplicate(); newBuf.flip(); byte[] data = new byte[newBuf.remaining()]; newBuf.get(data); byte[] newData = ALPNHackClientHelloExplorer.rewriteClientHello(data, applicationProtocols); if(newData != null) { byte[] clientHelloMesage = new byte[newData.length - 5]; System.arraycopy(newData, 5, clientHelloMesage, 0 , clientHelloMesage.length); ALPNHackClientByteArrayOutputStream.setSentClientHello(clientHelloMesage); byteBuffer.clear(); byteBuffer.put(newData); } } else if (!getUseClientMode()) { if(selectedApplicationProtocol != null && alpnHackServerByteArrayOutputStream != null) { byte[] newServerHello = alpnHackServerByteArrayOutputStream.getServerHello(); //this is the new server hello, it will be part of the first TLS plaintext record if (newServerHello != null) { byteBuffer.flip(); List records = ALPNHackServerHelloExplorer.extractRecords(byteBuffer); ByteBuffer newData = ALPNHackServerHelloExplorer.createNewOutputRecords(newServerHello, records); byteBuffer.position(pos); //erase the data byteBuffer.limit(limit); if (newData.remaining() > byteBuffer.remaining()) { int old = newData.limit(); newData.limit(newData.position() + byteBuffer.remaining()); res = new SSLEngineResult(res.getStatus(), res.getHandshakeStatus(), res.bytesConsumed(), newData.remaining()); byteBuffer.put(newData); newData.limit(old); bufferedWrapData = newData; } else { res = new SSLEngineResult(res.getStatus(), res.getHandshakeStatus(), res.bytesConsumed(), newData.remaining()); byteBuffer.put(newData); } } } } } if(res.bytesProduced() > 0) { ourHelloSent = true; } return res; } @Override public SSLEngineResult unwrap(ByteBuffer dataToUnwrap, ByteBuffer[] byteBuffers, int i, int i1) throws SSLException { if(!unwrapHelloSeen) { if(!delegate.getUseClientMode() && applicationProtocols != null) { try { List result = ALPNHackClientHelloExplorer.exploreClientHello(dataToUnwrap.duplicate()); if(result != null) { for(String protocol : applicationProtocols) { if(result.contains(protocol)) { selectedApplicationProtocol = protocol; break; } } } unwrapHelloSeen = true; } catch (BufferUnderflowException e) { return new SSLEngineResult(SSLEngineResult.Status.BUFFER_UNDERFLOW, SSLEngineResult.HandshakeStatus.NEED_UNWRAP, 0, 0); } } else if(delegate.getUseClientMode() && ALPNHackClientByteArrayOutputStream != null) { if(!dataToUnwrap.hasRemaining()) { return delegate.unwrap(dataToUnwrap, byteBuffers, i, i1); } try { ByteBuffer dup = dataToUnwrap.duplicate(); int type = dup.get(); int major = dup.get(); int minor = dup.get(); if(type == 22 && major == 3 && minor == 3) { //we only care about TLS 1.2 //split up the records, there may be multiple when doing a fast session resume List records = ALPNHackServerHelloExplorer.extractRecords(dataToUnwrap.duplicate()); ByteBuffer firstRecord = records.get(0); //this will be the handshake record final AtomicReference alpnResult = new AtomicReference<>(); ByteBuffer dupFirst = firstRecord.duplicate(); dupFirst.position(firstRecord.position() + 5); ByteBuffer firstLessFraming = dupFirst.duplicate(); byte[] result = ALPNHackServerHelloExplorer.removeAlpnExtensionsFromServerHello(dupFirst, alpnResult); firstLessFraming.limit(dupFirst.position()); unwrapHelloSeen = true; if (result != null) { selectedApplicationProtocol = alpnResult.get(); int newFirstRecordLength = result.length + dupFirst.remaining(); byte[] newFirstRecord = new byte[newFirstRecordLength]; System.arraycopy(result, 0, newFirstRecord, 0, result.length); dupFirst.get(newFirstRecord, result.length, dupFirst.remaining()); dataToUnwrap.position(dataToUnwrap.limit()); byte[] originalFirstRecord = new byte[firstLessFraming.remaining()]; firstLessFraming.get(originalFirstRecord); ByteBuffer newData = ALPNHackServerHelloExplorer.createNewOutputRecords(newFirstRecord, records); dataToUnwrap.clear(); dataToUnwrap.put(newData); dataToUnwrap.flip(); ALPNHackClientByteArrayOutputStream.setReceivedServerHello(originalFirstRecord); } } } catch (BufferUnderflowException e) { return new SSLEngineResult(SSLEngineResult.Status.BUFFER_UNDERFLOW, SSLEngineResult.HandshakeStatus.NEED_UNWRAP, 0, 0); } } } SSLEngineResult res = delegate.unwrap(dataToUnwrap, byteBuffers, i, i1); if(!delegate.getUseClientMode() && selectedApplicationProtocol != null && alpnHackServerByteArrayOutputStream == null) { alpnHackServerByteArrayOutputStream = replaceServerByteOutput(delegate, selectedApplicationProtocol); } return res; } @Override public Runnable getDelegatedTask() { return delegate.getDelegatedTask(); } @Override public void closeInbound() throws SSLException { delegate.closeInbound(); } @Override public boolean isInboundDone() { return delegate.isInboundDone(); } @Override public void closeOutbound() { delegate.closeOutbound(); } @Override public boolean isOutboundDone() { return delegate.isOutboundDone(); } @Override public String[] getSupportedCipherSuites() { return delegate.getSupportedCipherSuites(); } @Override public String[] getEnabledCipherSuites() { return delegate.getEnabledCipherSuites(); } @Override public void setEnabledCipherSuites(String[] strings) { delegate.setEnabledCipherSuites(strings); } @Override public String[] getSupportedProtocols() { return delegate.getSupportedProtocols(); } @Override public String[] getEnabledProtocols() { return delegate.getEnabledProtocols(); } @Override public void setEnabledProtocols(String[] strings) { delegate.setEnabledProtocols(strings); } @Override public SSLSession getSession() { return delegate.getSession(); } @Override public void beginHandshake() throws SSLException { delegate.beginHandshake(); } @Override public SSLEngineResult.HandshakeStatus getHandshakeStatus() { return delegate.getHandshakeStatus(); } @Override public void setUseClientMode(boolean b) { delegate.setUseClientMode(b); } @Override public boolean getUseClientMode() { return delegate.getUseClientMode(); } @Override public void setNeedClientAuth(boolean b) { delegate.setNeedClientAuth(b); } @Override public boolean getNeedClientAuth() { return delegate.getNeedClientAuth(); } @Override public void setWantClientAuth(boolean b) { delegate.setWantClientAuth(b); } @Override public boolean getWantClientAuth() { return delegate.getWantClientAuth(); } @Override public void setEnableSessionCreation(boolean b) { delegate.setEnableSessionCreation(b); } @Override public boolean getEnableSessionCreation() { return delegate.getEnableSessionCreation(); } /** * JDK8 ALPN hack support method. * * These methods will be removed once JDK8 ALPN support is no longer required * @param applicationProtocols */ public void setApplicationProtocols(List applicationProtocols) { this.applicationProtocols = applicationProtocols; } /** * JDK8 ALPN hack support method. * * These methods will be removed once JDK8 ALPN support is no longer required */ public List getApplicationProtocols() { return applicationProtocols; } /** * JDK8 ALPN hack support method. * * These methods will be removed once JDK8 ALPN support is no longer required */ public String getSelectedApplicationProtocol() { return selectedApplicationProtocol; } static ALPNHackServerByteArrayOutputStream replaceServerByteOutput(SSLEngine sslEngine, String selectedAlpnProtocol) throws SSLException { try { Object handshaker = HANDSHAKER.get(sslEngine); Object hash = HANDSHAKE_HASH.get(handshaker); ByteArrayOutputStream existing = (ByteArrayOutputStream) HANDSHAKE_HASH_DATA.get(hash); ALPNHackServerByteArrayOutputStream out = new ALPNHackServerByteArrayOutputStream(sslEngine, existing.toByteArray(), selectedAlpnProtocol); HANDSHAKE_HASH_DATA.set(hash, out); return out; } catch (Exception e) { throw UndertowMessages.MESSAGES.failedToReplaceHashOutputStream(e); } } static ALPNHackClientByteArrayOutputStream replaceClientByteOutput(SSLEngine sslEngine) throws SSLException { try { Object handshaker = HANDSHAKER.get(sslEngine); Object hash = HANDSHAKE_HASH.get(handshaker); ALPNHackClientByteArrayOutputStream out = new ALPNHackClientByteArrayOutputStream(sslEngine); HANDSHAKE_HASH_DATA.set(hash, out); return out; } catch (Exception e) { throw UndertowMessages.MESSAGES.failedToReplaceHashOutputStream(e); } } static void regenerateHashes(SSLEngine sslEngineToHack, ByteArrayOutputStream data, byte[]... hashBytes) { //hack up the SSL engine internal state try { Object handshaker = HANDSHAKER.get(sslEngineToHack); Object hash = HANDSHAKE_HASH.get(handshaker); data.reset(); Object protocolVersion = HANDSHAKER_PROTOCOL_VERSION.get(handshaker); HANDSHAKE_HASH_VERSION.set(hash, -1); HANDSHAKE_HASH_PROTOCOL_DETERMINED.invoke(hash, protocolVersion); MessageDigest digest = (MessageDigest) HANDSHAKE_HASH_FIN_MD.get(hash); digest.reset(); for (byte[] b : hashBytes) { HANDSHAKE_HASH_UPDATE.invoke(hash, b, 0, b.length); } } catch (Exception e) { throw UndertowMessages.MESSAGES.failedToReplaceHashOutputStreamOnWrite(e); } } } ALPNHackServerByteArrayOutputStream.java000066400000000000000000000046061420065311100347030ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ssl/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.ssl; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; import java.io.ByteArrayOutputStream; import java.io.IOException; /** * Super hacky class that allows the ServerHello message to be modified and the corresponding hash generated at runtime. * * * @author Stuart Douglas */ class ALPNHackServerByteArrayOutputStream extends ByteArrayOutputStream { private final SSLEngine sslEngine; private byte[] serverHello; private final String alpnProtocol; private boolean ready = false; ALPNHackServerByteArrayOutputStream(SSLEngine sslEngine, byte[] bytes, String alpnProtocol) { this.sslEngine = sslEngine; this.alpnProtocol = alpnProtocol; try { write(bytes); } catch (IOException e) { throw new RuntimeException(e); //never happen } ready = true; } @Override public void write(byte[] b, int off, int len) { if(ready) { if(b[off] == 2) { // server hello ready = false; //we are done processing serverHello = new byte[len]; //TODO: actual ALPN System.arraycopy(b, off, serverHello, 0, len); try { serverHello = ALPNHackServerHelloExplorer.addAlpnExtensionsToServerHello(serverHello, alpnProtocol); } catch (SSLException e) { throw new RuntimeException(e); } ALPNHackSSLEngine.regenerateHashes(sslEngine, this, toByteArray(), serverHello); return; } } super.write(b, off, len); } byte[] getServerHello() { return serverHello; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ssl/ALPNHackServerHelloExplorer.java000066400000000000000000000315051420065311100332450ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.ssl; import io.undertow.UndertowMessages; import javax.net.ssl.SSLException; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; /** * Hacks up ALPN support into the server hello message * * This has two different usage modes, one is adding a selected protocol into the extensions, the other is removing * all mention of ALPN and retuning the selected protocol. This dual mode does not make for the cleanest code * but removes the need to have duplicate nearly identical methods. * * The if the selected protocol is set then this will be added. If the selected protocol is null then ALPN will be * parsed and removed. * *

* We only care about TLS 1.2, as TLS 1.1 is not allowed to use ALPN. *

* Super hacky, but slightly less hacky than modifying the boot class path */ final class ALPNHackServerHelloExplorer { // Private constructor prevents construction outside this class. private ALPNHackServerHelloExplorer() { } static byte[] addAlpnExtensionsToServerHello(byte[] source, String selectedAlpnProtocol) throws SSLException { ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteBuffer input = ByteBuffer.wrap(source); try { exploreHandshake(input, source.length, new AtomicReference<>(selectedAlpnProtocol), out); //we need to adjust the record length; int serverHelloLength = out.size() - 4; out.write(source, input.position(), input.remaining()); //there may be more messages (cert etc), so we append them byte[] data = out.toByteArray(); //now we need to adjust the handshake frame length data[1] = (byte) ((serverHelloLength >> 16) & 0xFF); data[2] = (byte) ((serverHelloLength >> 8) & 0xFF); data[3] = (byte) (serverHelloLength & 0xFF); return data; } catch (AlpnProcessingException e) { return source; } } /** * removes the ALPN extensions from the server hello * @param source * @return * @throws SSLException */ static byte[] removeAlpnExtensionsFromServerHello(ByteBuffer source, final AtomicReference selectedAlpnProtocol) throws SSLException { ByteArrayOutputStream out = new ByteArrayOutputStream(); try { exploreHandshake(source, source.remaining(), selectedAlpnProtocol, out); //we need to adjust the record length; int serverHelloLength = out.size() - 4; byte[] data = out.toByteArray(); //now we need to adjust the handshake frame length data[1] = (byte) ((serverHelloLength >> 16) & 0xFF); data[2] = (byte) ((serverHelloLength >> 8) & 0xFF); data[3] = (byte) (serverHelloLength & 0xFF); return data; } catch (AlpnProcessingException e) { return null; } } private static void exploreHandshake(ByteBuffer input, int recordLength, AtomicReference selectedAlpnProtocol, ByteArrayOutputStream out) throws SSLException { // What is the handshake type? byte handshakeType = input.get(); if (handshakeType != 0x02) { // 0x01: server_hello message throw UndertowMessages.MESSAGES.expectedServerHello(); } out.write(handshakeType); // What is the handshake body length? int handshakeLength = getInt24(input); out.write(0); //placeholders out.write(0); out.write(0); // Theoretically, a single handshake message might span multiple // records, but in practice this does not occur. if (handshakeLength > recordLength - 4) { // 4: handshake header size throw UndertowMessages.MESSAGES.multiRecordSSLHandshake(); } int old = input.limit(); input.limit(handshakeLength + input.position()); exploreServerHello(input, selectedAlpnProtocol, out); input.limit(old); } private static void exploreServerHello( ByteBuffer input, AtomicReference alpnProtocolReference, ByteArrayOutputStream out) throws SSLException { // server version byte helloMajorVersion = input.get(); byte helloMinorVersion = input.get(); out.write(helloMajorVersion); out.write(helloMinorVersion); for (int i = 0; i < 32; ++i) { //the Random is 32 bytes out.write(input.get() & 0xFF); } // ignore session id processByteVector8(input, out); // ignore cipher_suite out.write(input.get() & 0xFF); out.write(input.get() & 0xFF); // ignore compression methods out.write(input.get() & 0xFF); String existingAlpn = null; ByteArrayOutputStream extensionsOutput = null; if (input.remaining() > 0) { extensionsOutput = new ByteArrayOutputStream(); existingAlpn = exploreExtensions(input, extensionsOutput, alpnProtocolReference.get() == null); } if (existingAlpn != null) { if(alpnProtocolReference.get() != null) { throw new AlpnProcessingException(); } alpnProtocolReference.set(existingAlpn); byte[] existing = extensionsOutput.toByteArray(); out.write(existing, 0, existing.length); } else if(alpnProtocolReference.get() != null) { String selectedAlpnProtocol = alpnProtocolReference.get(); ByteArrayOutputStream alpnBits = new ByteArrayOutputStream(); alpnBits.write(0); alpnBits.write(16); //ALPN type int length = 3 + selectedAlpnProtocol.length(); //length of extension data alpnBits.write((length >> 8) & 0xFF); alpnBits.write(length & 0xFF); length -= 2; alpnBits.write((length >> 8) & 0xFF); alpnBits.write(length & 0xFF); alpnBits.write(selectedAlpnProtocol.length() & 0xFF); for (int i = 0; i < selectedAlpnProtocol.length(); ++i) { alpnBits.write(selectedAlpnProtocol.charAt(i) & 0xFF); } if (extensionsOutput != null) { byte[] existing = extensionsOutput.toByteArray(); int newLength = existing.length - 2 + alpnBits.size(); existing[0] = (byte) ((newLength >> 8) & 0xFF); existing[1] = (byte) (newLength & 0xFF); try { out.write(existing); out.write(alpnBits.toByteArray()); } catch (IOException e) { throw new RuntimeException(e); } } else { int al = alpnBits.size(); out.write((al >> 8) & 0xFF); out.write(al & 0xFF); try { out.write(alpnBits.toByteArray()); } catch (IOException e) { throw new RuntimeException(e); } } } else if(extensionsOutput != null){ byte[] existing = extensionsOutput.toByteArray(); out.write(existing, 0, existing.length); } } static List extractRecords(ByteBuffer data) { List ret = new ArrayList<>(); while (data.hasRemaining()) { byte d1 = data.get(); byte d2 = data.get(); byte d3 = data.get(); byte d4 = data.get(); byte d5 = data.get(); int length = (d4 & 0xFF) << 8 | d5 & 0xFF; byte[] b = new byte[length + 5]; b[0] = d1; b[1] = d2; b[2] = d3; b[3] = d4; b[4] = d5; data.get(b, 5, length); ret.add(ByteBuffer.wrap(b)); } return ret; } private static String exploreExtensions(ByteBuffer input, ByteArrayOutputStream extensionOut, boolean removeAlpn) throws SSLException { ByteArrayOutputStream out = new ByteArrayOutputStream(); String ret = null; int length = getInt16(input); // length of extensions out.write((length >> 8) & 0xFF); out.write(length & 0xFF); int originalLength = length; while (length > 0) { int extType = getInt16(input); // extenson type int extLen = getInt16(input); // length of extension data if(extType == 16) { int vlen = getInt16(input); ret = readByteVector8(input); if(!removeAlpn) { //we write the extension data back to the output stream out.write((extType >> 8) & 0xFF); out.write(extType & 0xFF); out.write((extLen >> 8) & 0xFF); out.write(extLen & 0xFF); out.write((vlen >> 8) & 0xFF); out.write(vlen & 0xFF); out.write(ret.length() & 0xFF); for(int i = 0; i < ret.length(); ++i) { out.write(ret.charAt(i) & 0xFF); } } else { originalLength -= 6; originalLength -= vlen; } } else { out.write((extType >> 8) & 0xFF); out.write(extType & 0xFF); out.write((extLen >> 8) & 0xFF); out.write(extLen & 0xFF); processByteVector(input, extLen, out); } length -= extLen + 4; } if(removeAlpn && ret == null) { //there was not ALPN to remove, so this whole thing is unnecessary, throw an exception to abort throw new AlpnProcessingException(); } byte[] data = out.toByteArray(); data[0] = (byte) ((originalLength >> 8) & 0xFF); data[1] = (byte) (originalLength & 0xFF); extensionOut.write(data, 0, data.length); return ret; } private static String readByteVector8(ByteBuffer input) { int length = getInt8(input); byte[] data = new byte[length]; input.get(data); return new String(data, StandardCharsets.US_ASCII); } private static int getInt8(ByteBuffer input) { return input.get(); } private static int getInt16(ByteBuffer input) { return (input.get() & 0xFF) << 8 | input.get() & 0xFF; } private static int getInt24(ByteBuffer input) { return (input.get() & 0xFF) << 16 | (input.get() & 0xFF) << 8 | input.get() & 0xFF; } private static void processByteVector8(ByteBuffer input, ByteArrayOutputStream out) { int int8 = getInt8(input); out.write(int8 & 0xFF); processByteVector(input, int8, out); } private static void processByteVector(ByteBuffer input, int length, ByteArrayOutputStream out) { for (int i = 0; i < length; ++i) { out.write(input.get() & 0xFF); } } static ByteBuffer createNewOutputRecords(byte[] newFirstMessage, List records) { int length = newFirstMessage.length; length += 5; //Framing layer for (int i = 1; i < records.size(); ++i) { //the first record is the old server hello, so we start at 1 rather than zero ByteBuffer rec = records.get(i); length += rec.remaining(); } byte[] newData = new byte[length]; ByteBuffer ret = ByteBuffer.wrap(newData); ByteBuffer oldHello = records.get(0); ret.put(oldHello.get()); //type ret.put(oldHello.get()); //major ret.put(oldHello.get()); //minor ret.put((byte) ((newFirstMessage.length >> 8) & 0xFF)); ret.put((byte) (newFirstMessage.length & 0xFF)); ret.put(newFirstMessage); for (int i = 1; i < records.size(); ++i) { ByteBuffer rec = records.get(i); ret.put(rec); } ret.flip(); return ret; } private static final class AlpnProcessingException extends RuntimeException { } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ssl/MechanismDatabase.properties000066400000000000000000001006621420065311100326350ustar00rootroot00000000000000# # JBoss, Home of Professional Open Source. # Copyright 2014 Red Hat, Inc., and individual contributors # as indicated by the @author tags. # # 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. # # ┌─────────────────────────────────────────────────────────────────────────────── # │ Elytron SSL/TLS mechanism information database # │ # │ File information: # │ # │ • Encoding must be UTF-8 # │ • Ciphers are read in order, and order is preserved unless a re-sort occurs # │ • Key = stdName # │ • Value = openSslNames,kex,auth,enc,digest,prot,export,level,fips,strBits,algBits,byte1,byte2 # │ • If cipher has more OpenSSL names, they are delimited by '|' # │ • Valid kex names: EECDH RSA DHr DHd DHE PSK FZA KRB5 ECDHr ECDHe GOST SRP # │ RSAPSK DHEPSK ECDHEPSK # │ • Valid auth names: NULL RSA DSS DH ECDH KRB5 ECDSA PSK GOST94 GOST01 FZA # │ • Valid enc names: NULL AES256GCM AES256 AES128GCM AES128 CAMELLIA256 # │ CAMELLIA128 3DES DES IDEA GOST2814789CNT SEED FZA RC4 RC2 # │ • Valid digest names: MD5 SHA1 GOST94 GOST89MAC SHA256 SHA384 AEAD # │ • Valid prot names: SSLv2 SSLv3 TLSv1 TLSv1.2 # │ • Valid export values: true false # │ • Valid level names: NONE EXP40 EXP56 LOW MEDIUM HIGH FIPS # │ • Valid fips values: true false # │ • Valid strBits values: >= 0 # │ • Valid algBits values: >= 0 # │ • The byte1 and byte2 values represent the TLS encoding of that cipher suite; must # │ be a base16 two-digit byte value # │ • Note that all EDH ciphers automatically get a DHE OpenSSL-style alias (and vice-versa) # │ • Note that all TLS_ cipher suites automatically get a SSL_ alias # └─────────────────────────────────────────────────────────────────────────────── # OpenSSL TLS v1.2 TLS_RSA_WITH_NULL_SHA256 = NULL-SHA256,RSA,RSA,NULL,SHA256,TLSv1.2,false,NONE,true,0,0,00,3B TLS_RSA_WITH_AES_128_CBC_SHA256 = AES128-SHA256,RSA,RSA,AES128,SHA256,TLSv1.2,false,HIGH,true,128,128,00,3C TLS_RSA_WITH_AES_256_CBC_SHA256 = AES256-SHA256,RSA,RSA,AES256,SHA256,TLSv1.2,false,HIGH,true,256,256,00,3D TLS_RSA_WITH_AES_128_GCM_SHA256 = AES128-GCM-SHA256,RSA,RSA,AES128GCM,AEAD,TLSv1.2,false,HIGH,true,128,128,00,9C TLS_RSA_WITH_AES_256_GCM_SHA384 = AES256-GCM-SHA384,RSA,RSA,AES256GCM,AEAD,TLSv1.2,false,HIGH,true,256,256,00,9D TLS_DH_RSA_WITH_AES_128_CBC_SHA256 = DH-RSA-AES128-SHA256,DHr,DH,AES128,SHA256,TLSv1.2,false,HIGH,true,128,128,00,3F TLS_DH_RSA_WITH_AES_256_CBC_SHA256 = DH-RSA-AES256-SHA256,DHr,DH,AES256,SHA256,TLSv1.2,false,HIGH,true,256,256,00,69 TLS_DH_RSA_WITH_AES_128_GCM_SHA256 = DH-RSA-AES128-GCM-SHA256,DHr,DH,AES128GCM,AEAD,TLSv1.2,false,HIGH,true,128,128,00,A0 TLS_DH_RSA_WITH_AES_256_GCM_SHA384 = DH-RSA-AES256-GCM-SHA384,DHr,DH,AES256GCM,AEAD,TLSv1.2,false,HIGH,true,256,256,00,A1 TLS_DH_DSS_WITH_AES_128_CBC_SHA256 = DH-DSS-AES128-SHA256,DHd,DH,AES128,SHA256,TLSv1.2,false,HIGH,true,128,128,00,3E TLS_DH_DSS_WITH_AES_256_CBC_SHA256 = DH-DSS-AES256-SHA256,DHd,DH,AES256,SHA256,TLSv1.2,false,HIGH,true,256,256,00,68 TLS_DH_DSS_WITH_AES_128_GCM_SHA256 = DH-DSS-AES128-GCM-SHA256,DHd,DH,AES128GCM,AEAD,TLSv1.2,false,HIGH,true,128,128,00,A4 TLS_DH_DSS_WITH_AES_256_GCM_SHA384 = DH-DSS-AES256-GCM-SHA384,DHd,DH,AES256GCM,AEAD,TLSv1.2,false,HIGH,true,256,256,00,A5 TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = DHE-RSA-AES128-SHA256,DHE,RSA,AES128,SHA256,TLSv1.2,false,HIGH,true,128,128,00,67 TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = DHE-RSA-AES256-SHA256,DHE,RSA,AES256,SHA256,TLSv1.2,false,HIGH,true,256,256,00,6B TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = DHE-RSA-AES128-GCM-SHA256,DHE,RSA,AES128GCM,AEAD,TLSv1.2,false,HIGH,true,128,128,00,9E TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = DHE-RSA-AES256-GCM-SHA384,DHE,RSA,AES256GCM,AEAD,TLSv1.2,false,HIGH,true,256,256,00,9F TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 = DHE-DSS-AES128-SHA256,DHE,DSS,AES128,SHA256,TLSv1.2,false,HIGH,true,128,128,00,40 TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 = DHE-DSS-AES256-SHA256,DHE,DSS,AES256,SHA256,TLSv1.2,false,HIGH,true,256,256,00,6A TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 = DHE-DSS-AES128-GCM-SHA256,DHE,DSS,AES128GCM,AEAD,TLSv1.2,false,HIGH,true,128,128,00,A2 TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 = DHE-DSS-AES256-GCM-SHA384,DHE,DSS,AES256GCM,AEAD,TLSv1.2,false,HIGH,true,256,256,00,A3 TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 = ECDH-RSA-AES128-SHA256,ECDHr,ECDH,AES128,SHA256,TLSv1.2,false,HIGH,true,128,128,C0,29 TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 = ECDH-RSA-AES256-SHA384,ECDHr,ECDH,AES256,SHA384,TLSv1.2,false,HIGH,true,256,256,C0,2A TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 = ECDH-RSA-AES128-GCM-SHA256,ECDHr,ECDH,AES128GCM,AEAD,TLSv1.2,false,HIGH,true,128,128,C0,31 TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 = ECDH-RSA-AES256-GCM-SHA384,ECDHr,ECDH,AES256GCM,AEAD,TLSv1.2,false,HIGH,true,256,256,C0,32 TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 = ECDH-ECDSA-AES128-SHA256,ECDHe,ECDH,AES128,SHA256,TLSv1.2,false,HIGH,true,128,128,C0,25 TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 = ECDH-ECDSA-AES256-SHA384,ECDHe,ECDH,AES256,SHA384,TLSv1.2,false,HIGH,true,256,256,C0,26 TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 = ECDH-ECDSA-AES128-GCM-SHA256,ECDHe,ECDH,AES128GCM,AEAD,TLSv1.2,false,HIGH,true,128,128,C0,2D TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 = ECDH-ECDSA-AES256-GCM-SHA384,ECDHe,ECDH,AES256GCM,AEAD,TLSv1.2,false,HIGH,true,256,256,C0,2E TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = ECDHE-RSA-AES128-SHA256,EECDH,RSA,AES128,SHA256,TLSv1.2,false,HIGH,true,128,128,C0,27 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = ECDHE-RSA-AES256-SHA384,EECDH,RSA,AES256,SHA384,TLSv1.2,false,HIGH,true,256,256,C0,28 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = ECDHE-RSA-AES128-GCM-SHA256,EECDH,RSA,AES128GCM,AEAD,TLSv1.2,false,HIGH,true,128,128,C0,2F TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = ECDHE-RSA-AES256-GCM-SHA384,EECDH,RSA,AES256GCM,AEAD,TLSv1.2,false,HIGH,true,256,256,C0,30 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = ECDHE-ECDSA-AES128-SHA256,EECDH,ECDSA,AES128,SHA256,TLSv1.2,false,HIGH,true,128,128,C0,23 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = ECDHE-ECDSA-AES256-SHA384,EECDH,ECDSA,AES256,SHA384,TLSv1.2,false,HIGH,true,256,256,C0,24 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = ECDHE-ECDSA-AES128-GCM-SHA256,EECDH,ECDSA,AES128GCM,AEAD,TLSv1.2,false,HIGH,true,128,128,C0,2B TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = ECDHE-ECDSA-AES256-GCM-SHA384,EECDH,ECDSA,AES256GCM,AEAD,TLSv1.2,false,HIGH,true,256,256,C0,2C TLS_DH_anon_WITH_AES_128_CBC_SHA256 = ADH-AES128-SHA256,DHE,NULL,AES128,SHA256,TLSv1.2,false,HIGH,true,128,128,00,6C TLS_DH_anon_WITH_AES_256_CBC_SHA256 = ADH-AES256-SHA256,DHE,NULL,AES256,SHA256,TLSv1.2,false,HIGH,true,256,256,00,6D TLS_DH_anon_WITH_AES_128_GCM_SHA256 = ADH-AES128-GCM-SHA256,DHE,NULL,AES128GCM,AEAD,TLSv1.2,false,HIGH,true,128,128,00,A6 TLS_DH_anon_WITH_AES_256_GCM_SHA384 = ADH-AES256-GCM-SHA384,DHE,NULL,AES256GCM,AEAD,TLSv1.2,false,HIGH,true,256,256,00,A7 # OpenSSL TLS v1.2 Camellia extensions (RFC 6367 - http://tools.ietf.org/html/rfc6367) TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 = ECDHE-ECDSA-CAMELLIA128-SHA256,EECDH,ECDSA,CAMELLIA128,SHA256,TLSv1.2,false,HIGH,false,128,128,C0,72 TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 = ECDH-ECDSA-CAMELLIA128-SHA256,ECDHe,ECDH,CAMELLIA128,SHA256,TLSv1.2,false,HIGH,false,128,128,C0,74 TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 = ECDHE-RSA-CAMELLIA128-SHA256,EECDH,RSA,CAMELLIA128,SHA256,TLSv1.2,false,HIGH,false,128,128,C0,76 TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 = ECDH-RSA-CAMELLIA128-SHA256,ECDHr,ECDH,CAMELLIA128,SHA256,TLSv1.2,false,HIGH,false,128,128,C0,78 TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 = ECDHE-ECDSA-CAMELLIA256-SHA384,EECDH,ECDSA,CAMELLIA256,SHA384,TLSv1.2,false,HIGH,false,256,256,C0,73 TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 = ECDH-ECDSA-CAMELLIA256-SHA384,ECDHe,ECDH,CAMELLIA256,SHA384,TLSv1.2,false,HIGH,false,256,256,C0,75 TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 = ECDHE-RSA-CAMELLIA256-SHA384,EECDH,RSA,CAMELLIA256,SHA384,TLSv1.2,false,HIGH,false,256,256,C0,77 TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 = ECDH-RSA-CAMELLIA256-SHA384,ECDHr,ECDH,CAMELLIA256,SHA384,TLSv1.2,false,HIGH,false,256,256,C0,79 # TLS v1.2 Enhancements to Camellia extensions (RFC 5932 - http://tools.ietf.org/html/rfc5932) TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 = CAMELLIA128-SHA256,RSA,RSA,CAMELLIA128,SHA256,TLSv1.2,false,HIGH,false,128,128,00,BA TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 = DH-DSS-CAMELLIA128-SHA256,DHd,DH,CAMELLIA128,SHA256,TLSv1.2,false,HIGH,false,128,128,00,BB TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 = DH-RSA-CAMELLIA128-SHA256,DHr,DH,CAMELLIA128,SHA256,TLSv1.2,false,HIGH,false,128,128,00,BC TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 = DHE-DSS-CAMELLIA128-SHA256,DHE,DSS,CAMELLIA128,SHA256,TLSv1.2,false,HIGH,false,128,128,00,BD TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 = DHE-RSA-CAMELLIA128-SHA256,DHE,RSA,CAMELLIA128,SHA256,TLSv1.2,false,HIGH,false,128,128,00,BE TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256 = ADH-CAMELLIA128-SHA256,DHE,NULL,CAMELLIA128,SHA256,TLSv1.2,false,HIGH,false,128,128,00,BF TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 = CAMELLIA256-SHA256,RSA,RSA,CAMELLIA256,SHA256,TLSv1.2,false,HIGH,false,256,256,00,C0 TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 = DH-DSS-CAMELLIA256-SHA256,DHd,DH,CAMELLIA256,SHA256,TLSv1.2,false,HIGH,false,256,256,00,C1 TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 = DH-RSA-CAMELLIA256-SHA256,DHr,DH,CAMELLIA256,SHA256,TLSv1.2,false,HIGH,false,256,256,00,C2 TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 = DHE-DSS-CAMELLIA256-SHA256,DHE,DSS,CAMELLIA256,SHA256,TLSv1.2,false,HIGH,false,256,256,00,C3 TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 = DHE-RSA-CAMELLIA256-SHA256,DHE,RSA,CAMELLIA256,SHA256,TLSv1.2,false,HIGH,false,256,256,00,C4 TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256 = ADH-CAMELLIA256-SHA256,DHE,NULL,CAMELLIA256,SHA256,TLSv1.2,false,HIGH,false,256,256,00,C5 # TLS v1.2 PSK cipher suites with SHA-256/384 and GCM (RFC 5487 - http://tools.ietf.org/html/rfc5487) TLS_PSK_WITH_AES_128_GCM_SHA256 = PSK-AES128-GCM-SHA256,PSK,PSK,AES128GCM,SHA256,TLSv1.2,false,HIGH,true,128,128,00,A8 TLS_PSK_WITH_AES_256_GCM_SHA384 = PSK-AES256-GCM-SHA384,PSK,PSK,AES256GCM,SHA384,TLSv1.2,false,HIGH,true,256,256,00,A9 TLS_PSK_WITH_AES_128_CBC_SHA256 = PSK-AES128-CBC-SHA256,PSK,PSK,AES128,SHA256,TLSv1.2,false,HIGH,true,128,128,00,AE TLS_PSK_WITH_AES_256_CBC_SHA384 = PSK-AES256-CBC-SHA384,PSK,PSK,AES256,SHA384,TLSv1.2,false,HIGH,true,256,256,00,AF TLS_PSK_WITH_NULL_SHA256 = PSK-NULL-SHA256,PSK,PSK,NULL,SHA256,TLSv1.2,false,NONE,true,0,0,00,B0 TLS_PSK_WITH_NULL_SHA384 = PSK-NULL-SHA384,PSK,PSK,NULL,SHA384,TLSv1.2,false,NONE,true,0,0,00,B1 TLS_DHE_PSK_WITH_AES_128_GCM_SHA256 = DHE-PSK-AES128-GCM-SHA256,DHEPSK,PSK,AES128GCM,SHA256,TLSv1.2,false,HIGH,true,128,128,00,AA TLS_DHE_PSK_WITH_AES_256_GCM_SHA384 = DHE-PSK-AES256-GCM-SHA384,DHEPSK,PSK,AES256GCM,SHA384,TLSv1.2,false,HIGH,true,256,256,00,AB TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 = DHE-PSK-AES128-CBC-SHA256,DHEPSK,PSK,AES128,SHA256,TLSv1.2,false,HIGH,true,128,128,00,B2 TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 = DHE-PSK-AES256-CBC-SHA384,DHEPSK,PSK,AES256,SHA384,TLSv1.2,false,HIGH,true,256,256,00,B3 TLS_DHE_PSK_WITH_NULL_SHA256 = DHE-PSK-NULL-SHA256,DHEPSK,PSK,NULL,SHA256,TLSv1.2,false,NONE,true,0,0,00,B4 TLS_DHE_PSK_WITH_NULL_SHA384 = DHE-PSK-NULL-SHA384,DHEPSK,PSK,NULL,SHA384,TLSv1.2,false,NONE,true,0,0,00,B5 TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 = RSA-PSK-AES128-GCM-SHA256,RSAPSK,PSK,AES128GCM,SHA256,TLSv1.2,false,HIGH,true,128,128,00,AC TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 = RSA-PSK-AES256-GCM-SHA384,RSAPSK,PSK,AES256GCM,SHA384,TLSv1.2,false,HIGH,true,256,256,00,AD TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 = RSA-PSK-AES128-CBC-SHA256,RSAPSK,PSK,AES128,SHA256,TLSv1.2,false,HIGH,true,128,128,00,B6 TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 = RSA-PSK-AES256-CBC-SHA384,RSAPSK,PSK,AES256,SHA384,TLSv1.2,false,HIGH,true,256,256,00,B7 TLS_RSA_PSK_WITH_NULL_SHA256 = RSA-PSK-NULL-SHA256,RSAPSK,PSK,NULL,SHA256,TLSv1.2,false,NONE,true,0,0,00,B8 TLS_RSA_PSK_WITH_NULL_SHA384 = RSA-PSK-NULL-SHA384,RSAPSK,PSK,NULL,SHA384,TLSv1.2,false,NONE,true,0,0,00,B9 # TLS v1.2 ECDHE PSK cipher suites - RFC 5489 (http://tools.ietf.org/html/rfc5489) TLS_ECDHE_PSK_WITH_NULL_SHA = ECDHE-PSK-NULL-SHA,ECDHEPSK,ECDH,NULL,SHA1,TLSv1.2,false,NONE,true,0,0,C0,39 TLS_ECDHE_PSK_WITH_RC4_128_SHA = ECDHE-PSK-RC4-SHA,ECDHEPSK,ECDH,RC4,SHA1,TLSv1.2,false,MEDIUM,false,128,128,C0,33 TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA = ECDHE-PSK-3DES-EDE-SHA,ECDHEPSK,ECDH,3DES,SHA1,TLSv1.2,false,HIGH,false,168,168,C0,34 TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA = ECDHE-PSK-AES128-CBC-SHA,ECDHEPSK,ECDH,AES128,SHA1,TLSv1.2,false,HIGH,false,128,128,C0,35 TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA = ECDHE-PSK-AES256-CBC-SHA,ECDHEPSK,ECDH,AES128,SHA1,TLSv1.2,false,HIGH,false,256,256,C0,36 TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 = ECDHE-PSK-AES128-CBC-SHA256,ECDHEPSK,ECDH,AES128,SHA256,TLSv1.2,false,HIGH,false,128,128,C0,37 TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 = ECDHE-PSK-AES256-CBC-SHA384,ECDHEPSK,ECDH,AES256,SHA384,TLSv1.2,false,HIGH,false,256,256,C0,38 TLS_ECDHE_PSK_WITH_NULL_SHA256 = ECDHE-PSK-NULL-SHA256,ECDHEPSK,ECDH,NULL,SHA256,TLSv1.2,false,NONE,true,0,0,C0,3A TLS_ECDHE_PSK_WITH_NULL_SHA384 = ECDHE-PSK-NULL-SHA384,ECDHEPSK,ECDH,NULL,SHA384,TLSv1.2,false,NONE,true,0,0,C0,3B # Potential ECDHE PSK cipher suites using GCM (from a disappeared draft) TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256 = ECDHE-PSK-AES128-GCM-SHA256,ECDHEPSK,ECDH,AES128GCM,SHA256,TLSv1.2,false,HIGH,false,128,128 TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384 = ECDHE-PSK-AES256-GCM-SHA384,ECDHEPSK,ECDH,AES256GCM,SHA384,TLSv1.2,false,HIGH,false,256,256 # OpenSSL TLS v1.0 TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = EXP-EDH-RSA-DES-CBC-SHA,DHE,RSA,DES,SHA1,SSLv3,true,EXP40,false,40,56,00,14 TLS_DHE_RSA_WITH_DES_CBC_SHA = EDH-RSA-DES-CBC-SHA,DHE,RSA,DES,SHA1,SSLv3,false,LOW,false,56,56,00,15 TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = EDH-RSA-DES-CBC3-SHA,DHE,RSA,3DES,SHA1,SSLv3,false,HIGH,true,168,168,00,16 TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 = EXP-ADH-RC4-MD5,DHE,NULL,RC4,MD5,SSLv3,true,EXP40,false,40,128,00,17 TLS_DH_anon_WITH_RC4_128_MD5 = ADH-RC4-MD5,DHE,NULL,RC4,MD5,SSLv3,false,MEDIUM,false,128,128,00,18 TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA = EXP-ADH-DES-CBC-SHA,DHE,NULL,DES,SHA1,SSLv3,true,EXP40,false,40,128,00,19 TLS_DH_anon_WITH_DES_CBC_SHA = ADH-DES-CBC-SHA,DHE,NULL,DES,SHA1,SSLv3,false,LOW,false,56,56,00,1A TLS_DH_anon_WITH_3DES_EDE_CBC_SHA = ADH-DES-CBC3-SHA,DHE,NULL,3DES,SHA1,SSLv3,false,HIGH,true,168,168,00,1B # OpenSSL TLS v1.0 new TLS Export CipherSuites from expired ID TLS_RSA_EXPORT1024_WITH_RC4_56_MD5 = EXP1024-RC4-MD5,RSA,RSA,RC4,MD5,TLSv1,true,EXP56,false,56,128 TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD = EXP1024-RC2-CBC-MD5,RSA,RSA,RC2,MD5,TLSv1,true,EXP56,false,56,128 TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA = EXP1024-DES-CBC-SHA,RSA,RSA,DES,SHA1,TLSv1,true,EXP56,false,56,56 TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA = EXP1024-DHE-DSS-DES-CBC-SHA,DHE,DSS,DES,SHA1,TLSv1,true,EXP56,false,56,56 TLS_RSA_EXPORT1024_WITH_RC4_56_SHA = EXP1024-RC4-SHA,RSA,RSA,RC4,SHA1,TLSv1,true,EXP56,false,56,128 TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA = EXP1024-DHE-DSS-RC4-SHA,DHE,DSS,RC4,SHA1,TLSv1,true,EXP56,false,56,128 TLS_DHE_DSS_WITH_RC4_128_SHA = DHE-DSS-RC4-SHA,DHE,DSS,RC4,SHA1,TLSv1,false,MEDIUM,false,128,128 # OpenSSL TLS v1.0 AES extensions (RFC 3268 - http://tools.ietf.org/html/rfc3268) TLS_RSA_WITH_AES_128_CBC_SHA = AES128-SHA,RSA,RSA,AES128,SHA1,TLSv1,false,HIGH,true,128,128,00,2F TLS_DH_DSS_WITH_AES_128_CBC_SHA = DH-DSS-AES128-SHA,DHd,DH,AES128,SHA1,TLSv1,false,HIGH,true,128,128,00,30 TLS_DH_RSA_WITH_AES_128_CBC_SHA = DH-RSA-AES128-SHA,DHr,DH,AES128,SHA1,TLSv1,false,HIGH,true,128,128,00,31 TLS_DHE_DSS_WITH_AES_128_CBC_SHA = DHE-DSS-AES128-SHA,DHE,DSS,AES128,SHA1,TLSv1,false,HIGH,true,128,128,00,32 TLS_DHE_RSA_WITH_AES_128_CBC_SHA = DHE-RSA-AES128-SHA,DHE,RSA,AES128,SHA1,TLSv1,false,HIGH,true,128,128,00,33 TLS_DH_anon_WITH_AES_128_CBC_SHA = ADH-AES128-SHA,DHE,NULL,AES128,SHA1,TLSv1,false,HIGH,true,128,128,00,34 TLS_RSA_WITH_AES_256_CBC_SHA = AES256-SHA,RSA,RSA,AES256,SHA1,TLSv1,false,HIGH,true,256,256,00,35 TLS_DH_DSS_WITH_AES_256_CBC_SHA = DH-DSS-AES256-SHA,DHd,DH,AES256,SHA1,TLSv1,false,HIGH,true,256,256,00,36 TLS_DH_RSA_WITH_AES_256_CBC_SHA = DH-RSA-AES256-SHA,DHr,DH,AES256,SHA1,TLSv1,false,HIGH,true,256,256,00,37 TLS_DHE_DSS_WITH_AES_256_CBC_SHA = DHE-DSS-AES256-SHA,DHE,DSS,AES256,SHA1,TLSv1,false,HIGH,true,256,256,00,38 TLS_DHE_RSA_WITH_AES_256_CBC_SHA = DHE-RSA-AES256-SHA,DHE,RSA,AES256,SHA1,TLSv1,false,HIGH,true,256,256,00,39 TLS_DH_anon_WITH_AES_256_CBC_SHA = ADH-AES256-SHA,DHE,NULL,AES256,SHA1,TLSv1,false,HIGH,true,256,256,00,3A # OpenSSL TLS v1.0 SRP suites (RFC 5054 - http://tools.ietf.org/html/rfc5054) TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA = SRP-3DES-EDE-CBC-SHA,SRP,NULL,3DES,SHA1,TLSv1,false,HIGH,false,168,168,C0,1A TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA = SRP-RSA-3DES-EDE-CBC-SHA,SRP,RSA,3DES,SHA1,TLSv1,false,HIGH,false,168,168,C0,1B TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA = SRP-DSS-3DES-EDE-CBC-SHA,SRP,DSS,3DES,SHA1,TLSv1,false,HIGH,false,168,168,C0,1C TLS_SRP_SHA_WITH_AES_128_CBC_SHA = SRP-AES-128-CBC-SHA,SRP,NULL,AES128,SHA1,TLSv1,false,HIGH,false,128,128,C0,1D TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA = SRP-RSA-AES-128-CBC-SHA,SRP,RSA,AES128,SHA1,TLSv1,false,HIGH,false,128,128,C0,1E TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA = SRP-DSS-AES-128-CBC-SHA,SRP,DSS,AES128,SHA1,TLSv1,false,HIGH,false,128,128,C0,1F TLS_SRP_SHA_WITH_AES_256_CBC_SHA = SRP-AES-256-CBC-SHA,SRP,NULL,AES256,SHA1,TLSv1,false,HIGH,false,256,256,C0,20 TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA = SRP-RSA-AES-256-CBC-SHA,SRP,RSA,AES256,SHA1,TLSv1,false,HIGH,false,256,256,C0,21 TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA = SRP-DSS-AES-256-CBC-SHA,SRP,DSS,AES256,SHA1,TLSv1,false,HIGH,false,256,256,C0,22 # OpenSSL TLS v1.0 Camellia extensions (RFC 4132 - http://tools.ietf.org/html/rfc4132) TLS_RSA_WITH_CAMELLIA_128_CBC_SHA = CAMELLIA128-SHA,RSA,RSA,CAMELLIA128,SHA1,TLSv1,false,HIGH,false,128,128,00,41 TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA = DH-DSS-CAMELLIA128-SHA,DHd,DH,CAMELLIA128,SHA1,TLSv1,false,HIGH,false,128,128,00,42 TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA = DH-RSA-CAMELLIA128-SHA,DHr,DH,CAMELLIA128,SHA1,TLSv1,false,HIGH,false,128,128,00,43 TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA = DHE-DSS-CAMELLIA128-SHA,DHE,DSS,CAMELLIA128,SHA1,TLSv1,false,HIGH,false,128,128,00,44 TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA = DHE-RSA-CAMELLIA128-SHA,DHE,RSA,CAMELLIA128,SHA1,TLSv1,false,HIGH,false,128,128,00,45 TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA = ADH-CAMELLIA128-SHA,DHE,NULL,CAMELLIA128,SHA1,TLSv1,false,HIGH,false,128,128,00,46 TLS_RSA_WITH_CAMELLIA_256_CBC_SHA = CAMELLIA256-SHA,RSA,RSA,CAMELLIA256,SHA1,TLSv1,false,HIGH,false,256,256,00,84 TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA = DH-DSS-CAMELLIA256-SHA,DHd,DH,CAMELLIA256,SHA1,TLSv1,false,HIGH,false,256,256,00,85 TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA = DH-RSA-CAMELLIA256-SHA,DHr,DH,CAMELLIA256,SHA1,TLSv1,false,HIGH,false,256,256,00,86 TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA = DHE-DSS-CAMELLIA256-SHA,DHE,DSS,CAMELLIA256,SHA1,TLSv1,false,HIGH,false,256,256,00,87 TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA = DHE-RSA-CAMELLIA256-SHA,DHE,RSA,CAMELLIA256,SHA1,TLSv1,false,HIGH,false,256,256,00,88 TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA = ADH-CAMELLIA256-SHA,DHE,NULL,CAMELLIA256,SHA1,TLSv1,false,HIGH,false,256,256,00,89 # OpenSSL TLS v1.0 SEED extensions (RFC 4162 - http://tools.ietf.org/html/rfc4162) TLS_RSA_WITH_SEED_CBC_SHA = SEED-SHA,RSA,RSA,SEED,SHA1,TLSv1,false,MEDIUM,false,128,128,00,96 TLS_DH_DSS_WITH_SEED_CBC_SHA = DH-DSS-SEED-SHA,DHd,DH,SEED,SHA1,TLSv1,false,MEDIUM,false,128,128,00,97 TLS_DH_RSA_WITH_SEED_CBC_SHA = DH-RSA-SEED-SHA,DHr,DH,SEED,SHA1,TLSv1,false,MEDIUM,false,128,128,00,98 TLS_DHE_DSS_WITH_SEED_CBC_SHA = DHE-DSS-SEED-SHA,DHE,DSS,SEED,SHA1,TLSv1,false,MEDIUM,false,128,128,00,99 TLS_DHE_RSA_WITH_SEED_CBC_SHA = DHE-RSA-SEED-SHA,DHE,RSA,SEED,SHA1,TLSv1,false,MEDIUM,false,128,128,00,9A TLS_DH_anon_WITH_SEED_CBC_SHA = ADH-SEED-SHA,DHE,NULL,SEED,SHA1,TLSv1,false,MEDIUM,false,128,128,00,9B # OpenSSL TLS v1.0 GOST extensions (http://tools.ietf.org/html/draft-chudov-cryptopro-cptls-04) TLS_GOSTR341094_WITH_28147_CNT_IMIT = GOST94-GOST89-GOST89,GOST,GOST94,GOST2814789CNT,GOST89MAC,TLSv1,false,HIGH,false,256,256 TLS_GOSTR341001_WITH_28147_CNT_IMIT = GOST2001-GOST89-GOST89,GOST,GOST01,GOST2814789CNT,GOST89MAC,TLSv1,false,HIGH,false,256,256 TLS_GOSTR341094_WITH_NULL_GOSTR3411 = GOST94-NULL-GOST94,GOST,GOST94,NULL,GOST94,TLSv1,false,NONE,false,0,0 TLS_GOSTR341001_WITH_NULL_GOSTR3411 = GOST2001-NULL-GOST94,GOST,GOST01,NULL,GOST94,TLSv1,false,NONE,false,0,0 # OpenSSL TLS v1.0 more spooky GOSTs TLS_GOSTR341094_RSA_WITH_28147_CNT_MD5 = GOST-MD5,RSA,RSA,GOST2814789CNT,MD5,TLSv1,false,HIGH,false,256,256 TLS_RSA_WITH_28147_CNT_GOST94 = GOST-GOST94,RSA,RSA,GOST2814789CNT,GOST94,TLSv1,false,HIGH,false,256,256 TLS_RSA_WITH_28147_CNT_GOST89MAC = GOST-GOST89MAC,RSA,RSA,GOST2814789CNT,GOST89MAC,TLSv1,false,HIGH,false,256,256 TLS_RSA_WITH_28147_CNT_GOST89STREAM = GOST-GOST89STREAM,RSA,RSA,GOST2814789CNT,GOST89MAC,TLSv1,false,HIGH,false,256,256 # OpenSSL Elliptic Curve cipher suites (RFC 4492 - http://tools.ietf.org/html/rfc4492) TLS_ECDH_ECDSA_WITH_NULL_SHA = ECDH-ECDSA-NULL-SHA,ECDHe,ECDH,NULL,SHA1,TLSv1,false,NONE,true,0,0,C0,01 TLS_ECDH_ECDSA_WITH_RC4_128_SHA = ECDH-ECDSA-RC4-SHA,ECDHe,ECDH,RC4,SHA1,TLSv1,false,MEDIUM,false,128,128,C0,02 TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA = ECDH-ECDSA-DES-CBC3-SHA,ECDHe,ECDH,3DES,SHA1,TLSv1,false,HIGH,true,168,168,C0,03 TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA = ECDH-ECDSA-AES128-SHA,ECDHe,ECDH,AES128,SHA1,TLSv1,false,HIGH,true,128,128,C0,04 TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA = ECDH-ECDSA-AES256-SHA,ECDHe,ECDH,AES256,SHA1,TLSv1,false,HIGH,true,256,256,C0,05 TLS_ECDHE_ECDSA_WITH_NULL_SHA = ECDHE-ECDSA-NULL-SHA,EECDH,ECDSA,NULL,SHA1,TLSv1,false,NONE,true,0,0,C0,06 TLS_ECDHE_ECDSA_WITH_RC4_128_SHA = ECDHE-ECDSA-RC4-SHA,EECDH,ECDSA,RC4,SHA1,TLSv1,false,MEDIUM,false,128,128,C0,07 TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA = ECDHE-ECDSA-DES-CBC3-SHA,EECDH,ECDSA,3DES,SHA1,TLSv1,false,HIGH,true,168,168,C0,08 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = ECDHE-ECDSA-AES128-SHA,EECDH,ECDSA,AES128,SHA1,TLSv1,false,HIGH,true,128,128,C0,09 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = ECDHE-ECDSA-AES256-SHA,EECDH,ECDSA,AES256,SHA1,TLSv1,false,HIGH,true,256,256,C0,0A TLS_ECDH_RSA_WITH_NULL_SHA = ECDH-RSA-NULL-SHA,ECDHr,ECDH,NULL,SHA1,TLSv1,false,NONE,true,0,0,C0,0B TLS_ECDH_RSA_WITH_RC4_128_SHA = ECDH-RSA-RC4-SHA,ECDHr,ECDH,RC4,SHA1,TLSv1,false,MEDIUM,false,128,128,C0,0C TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA = ECDH-RSA-DES-CBC3-SHA,ECDHr,ECDH,3DES,SHA1,TLSv1,false,HIGH,true,168,168,C0,0D TLS_ECDH_RSA_WITH_AES_128_CBC_SHA = ECDH-RSA-AES128-SHA,ECDHr,ECDH,AES128,SHA1,TLSv1,false,HIGH,true,128,128,C0,0E TLS_ECDH_RSA_WITH_AES_256_CBC_SHA = ECDH-RSA-AES256-SHA,ECDHr,ECDH,AES256,SHA1,TLSv1,false,HIGH,true,256,256,C0,0F TLS_ECDHE_RSA_WITH_NULL_SHA = ECDHE-RSA-NULL-SHA,EECDH,RSA,NULL,SHA1,TLSv1,false,NONE,true,0,0,C0,10 TLS_ECDHE_RSA_WITH_RC4_128_SHA = ECDHE-RSA-RC4-SHA,EECDH,RSA,RC4,SHA1,TLSv1,false,MEDIUM,false,128,128,C0,11 TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA = ECDHE-RSA-DES-CBC3-SHA,EECDH,RSA,3DES,SHA1,TLSv1,false,HIGH,true,168,168,C0,12 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = ECDHE-RSA-AES128-SHA,EECDH,RSA,AES128,SHA1,TLSv1,false,HIGH,true,128,128,C0,13 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = ECDHE-RSA-AES256-SHA,EECDH,RSA,AES256,SHA1,TLSv1,false,HIGH,true,256,256,C0,14 TLS_ECDH_anon_WITH_NULL_SHA = AECDH-NULL-SHA,EECDH,NULL,NULL,SHA1,TLSv1,false,NONE,true,0,0,C0,15 TLS_ECDH_anon_WITH_RC4_128_SHA = AECDH-RC4-SHA,EECDH,NULL,RC4,SHA1,TLSv1,false,MEDIUM,false,128,128,C0,16 TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA = AECDH-DES-CBC3-SHA,EECDH,NULL,3DES,SHA1,TLSv1,false,HIGH,true,168,168,C0,17 TLS_ECDH_anon_WITH_AES_128_CBC_SHA = AECDH-AES128-SHA,EECDH,NULL,AES128,SHA1,TLSv1,false,HIGH,true,128,128,C0,18 TLS_ECDH_anon_WITH_AES_256_CBC_SHA = AECDH-AES256-SHA,EECDH,NULL,AES256,SHA1,TLSv1,false,HIGH,true,256,256,C0,19 # OpenSSL TLS v1.0 PSK (RFC 4279 - http://tools.ietf.org/html/rfc4279) TLS_PSK_WITH_RC4_128_SHA = PSK-RC4-SHA,PSK,PSK,RC4,SHA1,TLSv1,false,MEDIUM,false,128,128,00,8A TLS_PSK_WITH_3DES_EDE_CBC_SHA = PSK-3DES-EDE-CBC-SHA,PSK,PSK,3DES,SHA1,TLSv1,false,HIGH,true,168,168,00,8B TLS_PSK_WITH_AES_128_CBC_SHA = PSK-AES128-CBC-SHA,PSK,PSK,AES128,SHA1,TLSv1,false,HIGH,true,128,128,00,8C TLS_PSK_WITH_AES_256_CBC_SHA = PSK-AES256-CBC-SHA,PSK,PSK,AES256,SHA1,TLSv1,false,HIGH,true,256,256,00,8D # Non-OpenSSL TLS v1.0 PSK (RFC 4279 - http://tools.ietf.org/html/rfc4279) TLS_DHE_PSK_WITH_RC4_128_SHA = DHE-PSK-RC4-SHA,DHEPSK,PSK,RC4,SHA1,TLSv1,false,MEDIUM,false,128,128,00,8E TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA = DHE-PSK-3DES-EDE-SHA,DHEPSK,PSK,3DES,SHA1,TLSv1,false,HIGH,false,168,168,00,8F TLS_DHE_PSK_WITH_AES_128_CBC_SHA = DHE-PSK-AES128-CBC-SHA,DHEPSK,PSK,AES128,SHA1,TLSv1,false,HIGH,false,128,128,00,90 TLS_DHE_PSK_WITH_AES_256_CBC_SHA = DHE-PSK-AES256-CBC-SHA,DHEPSK,PSK,AES128,SHA1,TLSv1,false,HIGH,false,256,256,00,91 TLS_RSA_PSK_WITH_RC4_128_SHA = RSA-PSK-RC4-SHA,RSAPSK,PSK,RC4,SHA1,TLSv1,false,MEDIUM,false,128,128,00,92 TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA = RSA-PSK-3DES-EDE-SHA,RSAPSK,PSK,3DES,SHA1,TLSv1,false,HIGH,false,168,168,00,93 TLS_RSA_PSK_WITH_AES_128_CBC_SHA = RSA-PSK-AES128-CBC-SHA,RSAPSK,PSK,AES128,SHA1,TLSv1,false,HIGH,false,128,128,00,94 TLS_RSA_PSK_WITH_AES_256_CBC_SHA = RSA-PSK-AES256-CBC-SHA,RSAPSK,PSK,AES128,SHA1,TLSv1,false,HIGH,false,256,256,00,95 # PSK with NULL encryption (RFC 4785 - http://tools.ietf.org/html/rfc4785) TLS_PSK_WITH_NULL_SHA = PSK-NULL-SHA,PSK,PSK,NULL,SHA1,TLSv1,false,NONE,true,0,0,00,2C TLS_DHE_PSK_WITH_NULL_SHA = DHE-PSK-NULL-SHA,DHEPSK,PSK,NULL,SHA1,TLSv1,false,NONE,true,0,0,00,2D TLS_RSA_PSK_WITH_NULL_SHA = RSA-PSK-NULL-SHA,RSAPSK,PSK,NULL,SHA1,TLSv1,false,NONE,true,0,0,00,2E # There are no standard GCM variants of the above; use below instead # OpenSSL SSL v3.0 Kerberos suites TLS_KRB5_WITH_DES_CBC_SHA = KRB5-DES-CBC-SHA,KRB5,KRB5,DES,SHA1,SSLv3,false,LOW,false,56,56,00,1E TLS_KRB5_WITH_3DES_EDE_CBC_SHA = KRB5-DES-CBC3-SHA,KRB5,KRB5,3DES,SHA1,SSLv3,false,HIGH,true,168,168,00,1F TLS_KRB5_WITH_RC4_128_SHA = KRB5-RC4-SHA,KRB5,KRB5,RC4,SHA1,SSLv3,false,MEDIUM,false,128,128,00,20 TLS_KRB5_WITH_IDEA_CBC_SHA = KRB5-IDEA-CBC-SHA,KRB5,KRB5,IDEA,SHA1,SSLv3,false,MEDIUM,false,128,128,00,21 TLS_KRB5_WITH_DES_CBC_MD5 = KRB5-DES-CBC-MD5,KRB5,KRB5,DES,MD5,SSLv3,false,LOW,false,56,56,00,22 TLS_KRB5_WITH_3DES_EDE_CBC_MD5 = KRB5-DES-CBC3-MD5,KRB5,KRB5,3DES,MD5,SSLv3,false,HIGH,false,168,168,00,23 TLS_KRB5_WITH_RC4_128_MD5 = KRB5-RC4-MD5,KRB5,KRB5,RC4,MD5,SSLv3,false,MEDIUM,false,128,128,00,24 TLS_KRB5_WITH_IDEA_CBC_MD5 = KRB5-IDEA-CBC-MD5,KRB5,KRB5,IDEA,MD5,SSLv3,false,MEDIUM,false,128,128,00,25 TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA = EXP-KRB5-DES-CBC-SHA,KRB5,KRB5,DES,SHA1,SSLv3,true,EXP40,false,40,56,00,26 TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA = EXP-KRB5-RC2-CBC-SHA,KRB5,KRB5,RC2,SHA1,SSLv3,true,EXP40,false,40,128,00,27 TLS_KRB5_EXPORT_WITH_RC4_40_SHA = EXP-KRB5-RC4-SHA,KRB5,KRB5,RC4,SHA1,SSLv3,true,EXP40,false,40,128,00,28 TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 = EXP-KRB5-DES-CBC-MD5,KRB5,KRB5,DES,MD5,SSLv3,true,EXP40,false,40,56,00,29 TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 = EXP-KRB5-RC2-CBC-MD5,KRB5,KRB5,RC2,MD5,SSLv3,true,EXP40,false,40,128,00,2A TLS_KRB5_EXPORT_WITH_RC4_40_MD5 = EXP-KRB5-RC4-MD5,KRB5,KRB5,RC4,MD5,SSLv3,true,EXP40,false,40,128,00,2B # OpenSSL SSL v3.0 TLS_RSA_WITH_NULL_MD5 = NULL-MD5,RSA,RSA,NULL,MD5,SSLv3,false,NONE,false,0,0,00,01 TLS_RSA_WITH_NULL_SHA = NULL-SHA,RSA,RSA,NULL,SHA1,SSLv3,false,NONE,true,0,0,00,02 TLS_RSA_EXPORT_WITH_RC4_40_MD5 = EXP-RC4-MD5,RSA,RSA,RC4,MD5,SSLv3,true,EXP40,false,40,128,00,03 TLS_RSA_WITH_RC4_128_MD5 = RC4-MD5,RSA,RSA,RC4,MD5,SSLv3,false,MEDIUM,false,128,128,00,04 TLS_RSA_WITH_RC4_128_SHA = RC4-SHA,RSA,RSA,RC4,SHA1,SSLv3,false,MEDIUM,false,128,128,00,05 TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = EXP-RC2-CBC-MD5,RSA,RSA,RC2,MD5,SSLv3,true,EXP40,false,40,128,00,06 TLS_RSA_WITH_IDEA_CBC_SHA = IDEA-CBC-SHA,RSA,RSA,IDEA,SHA1,SSLv3,false,MEDIUM,false,128,128,00,07 TLS_RSA_EXPORT_WITH_DES40_CBC_SHA = EXP-DES-CBC-SHA,RSA,RSA,DES,SHA1,SSLv3,true,EXP40,false,40,56,00,08 TLS_RSA_WITH_DES_CBC_SHA = DES-CBC-SHA,RSA,RSA,DES,SHA1,SSLv3,false,LOW,false,56,56,00,09 TLS_RSA_WITH_3DES_EDE_CBC_SHA = DES-CBC3-SHA,RSA,RSA,3DES,SHA1,SSLv3,false,HIGH,true,168,168,00,0A TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = EXP-DH-DSS-DES-CBC-SHA,DHd,DH,DES,SHA1,SSLv3,true,EXP40,false,40,56,00,0B TLS_DH_DSS_WITH_DES_CBC_SHA = DH-DSS-DES-CBC-SHA,DHd,DH,DES,SHA1,SSLv3,false,LOW,false,56,56,00,0C TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = DH-DSS-DES-CBC3-SHA,DHd,DH,3DES,SHA1,SSLv3,false,HIGH,true,168,168,00,0D TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = EXP-DH-RSA-DES-CBC-SHA,DHr,DH,DES,SHA1,SSLv3,true,EXP40,false,40,56,00,0E TLS_DH_RSA_WITH_DES_CBC_SHA = DH-RSA-DES-CBC-SHA,DHr,DH,DES,SHA1,SSLv3,false,LOW,false,56,56,00,0F TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = DH-RSA-DES-CBC3-SHA,DHr,DH,3DES,SHA1,SSLv3,false,HIGH,true,168,168,00,10 TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = EXP-EDH-DSS-DES-CBC-SHA,DHE,DSS,DES,SHA1,SSLv3,true,EXP40,false,40,56,00,11 TLS_DHE_DSS_WITH_DES_CBC_SHA = EDH-DSS-DES-CBC-SHA|EDH-DSS-CBC-SHA,DHE,DSS,DES,SHA1,SSLv3,false,LOW,false,56,56,00,12 TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = EDH-DSS-DES-CBC3-SHA,DHE,DSS,3DES,SHA1,SSLv3,false,HIGH,true,168,168,00,13 # OpenSSL Fortezza cipher suite from SSL 3.0 spec # TLS_FORTEZZA_KEA_WITH_NULL_SHA ⎫ # TLS_FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA ⎬ Not implemented - see http://marc.info/?l=openssl-dev&m=102820036228328&w=2 # TLS_FORTEZZA_KEA_WITH_RC4_128_SHA ⎭ ← this one in particular has an ID conflict with KRB5 and should not be used TLS_FORTEZZA_DMS_WITH_NULL_SHA = FZA-NULL-SHA,FZA,FZA,NULL,SHA1,SSLv3,false,NONE,false,0,0 TLS_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA = FZA-FZA-CBC-SHA,FZA,FZA,FZA,SHA1,SSLv3,false,NONE,false,0,0 TLS_FORTEZZA_DMS_WITH_RC4_128_SHA = FZA-RC4-SHA,FZA,FZA,RC4,SHA1,SSLv3,false,MEDIUM,false,128,128 # OpenSSL SSL v2 deprecated # TLS_CK_RC4_128_WITH_MD5 = RC4-MD5,RSA,RSA,RC4,MD5,SSLv2,false,MEDIUM,false,128,128 # TLS_CK_RC4_128_EXPORT40_WITH_MD5 = EXP-RC4-MD5,RSA,RSA,RC4,MD5,SSLv2,true,EXP40,false,40,128 # TLS_CK_RC2_128_CBC_WITH_MD5 = RC2-MD5,RSA,RSA,RC2,MD5,SSLv2,false,MEDIUM,false,128,128 # TLS_CK_RC2_128_CBC_EXPORT40_WITH_MD5 = EXP-RC2-MD5,RSA,RSA,RC2,MD5,SSLv2,true,EXP40,false,40,128 # TLS_CK_IDEA_128_CBC_WITH_MD5 = IDEA-CBC-MD5,RSA,RSA,IDEA,MD5,SSLv2,false,MEDIUM,false,128,128 # TLS_CK_DES_64_CBC_WITH_MD5 = DES-CBC-MD5,RSA,RSA,DES,MD5,SSLv2,false,LOW,false,56,56 # TLS_CK_DES_192_EDE3_CBC_WITH_MD5 = DES-CBC3-MD5,RSA,RSA,3DES,MD5,SSLv2,false,HIGH,false,168,168 # JDK FIPS modes not in OpenSSL TLS_RSA_FIPS_WITH_DES_CBC_SHA = alias:TLS_RSA_WITH_DES_CBC_SHA TLS_RSA_FIPS_WITH_3DES_EDE_CBC_SHA = alias:TLS_RSA_WITH_3DES_EDE_CBC_SHA undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ssl/SNIAlpnEngineManager.java000066400000000000000000000025021420065311100317100ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2018 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * 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. */ package io.undertow.protocols.ssl; import java.util.function.Function; import javax.net.ssl.SSLEngine; import io.undertow.protocols.alpn.ALPNEngineManager; public class SNIAlpnEngineManager implements ALPNEngineManager { @Override public int getPriority() { return 100; } @Override public boolean registerEngine(SSLEngine engine, Function selectedFunction) { if(!(engine instanceof SNISSLEngine)) { return false; } SNISSLEngine snisslEngine = (SNISSLEngine) engine; snisslEngine.setSelectionCallback(selectedFunction); return true; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ssl/SNIContextMatcher.java000066400000000000000000000062471420065311100313370ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2018 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * 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. */ package io.undertow.protocols.ssl; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.net.ssl.SNIHostName; import javax.net.ssl.SNIMatcher; import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLContext; import io.undertow.UndertowMessages; public class SNIContextMatcher { private final SSLContext defaultContext; private final Map wildcards; private final Map exacts; SNIContextMatcher(SSLContext defaultContext, Map wildcards, Map exacts) { this.defaultContext = defaultContext; this.wildcards = wildcards; this.exacts = exacts; } public SSLContext getContext(List servers) { for (Map.Entry entry : exacts.entrySet()) { for (SNIServerName server : servers) { if (entry.getKey().matches(server)) { return entry.getValue(); } } } for (Map.Entry entry : wildcards.entrySet()) { for (SNIServerName server : servers) { if (entry.getKey().matches(server)) { return entry.getValue(); } } } return defaultContext; } public SSLContext getDefaultContext() { return defaultContext; } public static class Builder { private SSLContext defaultContext; private final Map wildcards = new LinkedHashMap<>(); private final Map exacts = new LinkedHashMap<>(); public SNIContextMatcher build() { if(defaultContext == null) { throw UndertowMessages.MESSAGES.defaultContextCannotBeNull(); } return new SNIContextMatcher(defaultContext, wildcards, exacts); } public SSLContext getDefaultContext() { return defaultContext; } public Builder setDefaultContext(SSLContext defaultContext) { this.defaultContext = defaultContext; return this; } public Builder addMatch(String name, SSLContext context) { if (name.contains("*")) { wildcards.put(SNIHostName.createSNIMatcher(name), context); } else { exacts.put(SNIHostName.createSNIMatcher(name), context); } return this; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ssl/SNISSLContext.java000066400000000000000000000017641420065311100304140ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2018 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * 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. */ package io.undertow.protocols.ssl; import javax.net.ssl.SSLContext; public class SNISSLContext extends SSLContext { public SNISSLContext(SNIContextMatcher matcher) { super(new SNISSLContextSpi(matcher), matcher.getDefaultContext().getProvider(), matcher.getDefaultContext().getProtocol()); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ssl/SNISSLContextSpi.java000066400000000000000000000045071420065311100310660ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2018 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * 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. */ package io.undertow.protocols.ssl; import java.security.KeyManagementException; import java.security.SecureRandom; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContextSpi; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLSessionContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; /** * SSLContext that can be used to do SNI matching. * * This */ class SNISSLContextSpi extends SSLContextSpi { private final SNIContextMatcher matcher; SNISSLContextSpi(SNIContextMatcher matcher) { this.matcher = matcher; } @Override protected void engineInit(KeyManager[] keyManagers, TrustManager[] trustManagers, SecureRandom secureRandom) throws KeyManagementException { //noop } @Override protected SSLSocketFactory engineGetSocketFactory() { return matcher.getDefaultContext().getSocketFactory(); } @Override protected SSLServerSocketFactory engineGetServerSocketFactory() { return matcher.getDefaultContext().getServerSocketFactory(); } @Override protected SSLEngine engineCreateSSLEngine() { return new SNISSLEngine(matcher); } @Override protected SSLEngine engineCreateSSLEngine(String s, int i) { return new SNISSLEngine(matcher, s, i); } @Override protected SSLSessionContext engineGetServerSessionContext() { return matcher.getDefaultContext().getServerSessionContext(); } @Override protected SSLSessionContext engineGetClientSessionContext() { return matcher.getDefaultContext().getClientSessionContext(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ssl/SNISSLEngine.java000066400000000000000000000441031420065311100301670ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2018 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * 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. */ package io.undertow.protocols.ssl; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.security.Principal; import java.security.cert.Certificate; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSessionContext; import javax.security.cert.X509Certificate; import io.undertow.UndertowMessages; /** * @author David M. Lloyd */ class SNISSLEngine extends SSLEngine { private static final SSLEngineResult UNDERFLOW_UNWRAP = new SSLEngineResult(SSLEngineResult.Status.BUFFER_UNDERFLOW, SSLEngineResult.HandshakeStatus.NEED_UNWRAP, 0, 0); private static final SSLEngineResult OK_UNWRAP = new SSLEngineResult(SSLEngineResult.Status.OK, SSLEngineResult.HandshakeStatus.NEED_UNWRAP, 0, 0); private final AtomicReference currentRef; private Function selectionCallback = Function.identity(); SNISSLEngine(final SNIContextMatcher selector) { currentRef = new AtomicReference<>(new InitialState(selector, SSLContext::createSSLEngine)); } SNISSLEngine(final SNIContextMatcher selector, final String host, final int port) { super(host, port); currentRef = new AtomicReference<>(new InitialState(selector, sslContext -> sslContext.createSSLEngine(host, port))); } public Function getSelectionCallback() { return selectionCallback; } public void setSelectionCallback(Function selectionCallback) { this.selectionCallback = selectionCallback; } public SSLEngineResult wrap(final ByteBuffer[] srcs, final int offset, final int length, final ByteBuffer dst) throws SSLException { return currentRef.get().wrap(srcs, offset, length, dst); } public SSLEngineResult wrap(final ByteBuffer src, final ByteBuffer dst) throws SSLException { return currentRef.get().wrap(src, dst); } public SSLEngineResult wrap(final ByteBuffer[] srcs, final ByteBuffer dst) throws SSLException { return currentRef.get().wrap(srcs, dst); } public SSLEngineResult unwrap(final ByteBuffer src, final ByteBuffer[] dsts, final int offset, final int length) throws SSLException { return currentRef.get().unwrap(src, dsts, offset, length); } public SSLEngineResult unwrap(final ByteBuffer src, final ByteBuffer dst) throws SSLException { return currentRef.get().unwrap(src, dst); } public SSLEngineResult unwrap(final ByteBuffer src, final ByteBuffer[] dsts) throws SSLException { return currentRef.get().unwrap(src, dsts); } public String getPeerHost() { return currentRef.get().getPeerHost(); } public int getPeerPort() { return currentRef.get().getPeerPort(); } public SSLSession getHandshakeSession() { return currentRef.get().getHandshakeSession(); } public SSLParameters getSSLParameters() { return currentRef.get().getSSLParameters(); } public void setSSLParameters(final SSLParameters params) { currentRef.get().setSSLParameters(params); } public Runnable getDelegatedTask() { return currentRef.get().getDelegatedTask(); } public void closeInbound() throws SSLException { currentRef.get().closeInbound(); } public boolean isInboundDone() { return currentRef.get().isInboundDone(); } public void closeOutbound() { currentRef.get().closeOutbound(); } public boolean isOutboundDone() { return currentRef.get().isOutboundDone(); } public String[] getSupportedCipherSuites() { return currentRef.get().getSupportedCipherSuites(); } public String[] getEnabledCipherSuites() { return currentRef.get().getEnabledCipherSuites(); } public void setEnabledCipherSuites(final String[] cipherSuites) { currentRef.get().setEnabledCipherSuites(cipherSuites); } public String[] getSupportedProtocols() { return currentRef.get().getSupportedProtocols(); } public String[] getEnabledProtocols() { return currentRef.get().getEnabledProtocols(); } public void setEnabledProtocols(final String[] protocols) { currentRef.get().setEnabledProtocols(protocols); } public SSLSession getSession() { return currentRef.get().getSession(); } public void beginHandshake() throws SSLException { currentRef.get().beginHandshake(); } public SSLEngineResult.HandshakeStatus getHandshakeStatus() { return currentRef.get().getHandshakeStatus(); } public void setUseClientMode(final boolean clientMode) { currentRef.get().setUseClientMode(clientMode); } public boolean getUseClientMode() { return currentRef.get().getUseClientMode(); } public void setNeedClientAuth(final boolean clientAuth) { currentRef.get().setNeedClientAuth(clientAuth); } public boolean getNeedClientAuth() { return currentRef.get().getNeedClientAuth(); } public void setWantClientAuth(final boolean want) { currentRef.get().setWantClientAuth(want); } public boolean getWantClientAuth() { return currentRef.get().getWantClientAuth(); } public void setEnableSessionCreation(final boolean flag) { currentRef.get().setEnableSessionCreation(flag); } public boolean getEnableSessionCreation() { return currentRef.get().getEnableSessionCreation(); } static final int FL_WANT_C_AUTH = 1 << 0; static final int FL_NEED_C_AUTH = 1 << 1; static final int FL_SESSION_CRE = 1 << 2; class InitialState extends SSLEngine { private final SNIContextMatcher selector; private final AtomicInteger flags = new AtomicInteger(FL_SESSION_CRE); private final Function engineFunction; private int packetBufferSize = SNISSLExplorer.RECORD_HEADER_SIZE; private String[] enabledSuites; private String[] enabledProtocols; private final SSLSession handshakeSession = new SSLSession() { public byte[] getId() { throw new UnsupportedOperationException(); } public SSLSessionContext getSessionContext() { throw new UnsupportedOperationException(); } public long getCreationTime() { throw new UnsupportedOperationException(); } public long getLastAccessedTime() { throw new UnsupportedOperationException(); } public void invalidate() { throw new UnsupportedOperationException(); } public boolean isValid() { return false; } public void putValue(final String s, final Object o) { throw new UnsupportedOperationException(); } public Object getValue(final String s) { return null; } public void removeValue(final String s) { } public String[] getValueNames() { throw new UnsupportedOperationException(); } public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { throw new UnsupportedOperationException(); } public Certificate[] getLocalCertificates() { return null; } public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { throw new UnsupportedOperationException(); } public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { throw new UnsupportedOperationException(); } public Principal getLocalPrincipal() { throw new UnsupportedOperationException(); } public String getCipherSuite() { throw new UnsupportedOperationException(); } public String getProtocol() { throw new UnsupportedOperationException(); } public String getPeerHost() { return SNISSLEngine.this.getPeerHost(); } public int getPeerPort() { return SNISSLEngine.this.getPeerPort(); } public int getPacketBufferSize() { return packetBufferSize; } public int getApplicationBufferSize() { throw new UnsupportedOperationException(); } }; InitialState(final SNIContextMatcher selector, final Function engineFunction) { this.selector = selector; this.engineFunction = engineFunction; } public SSLSession getHandshakeSession() { return handshakeSession; } public SSLEngineResult wrap(final ByteBuffer[] srcs, final int offset, final int length, final ByteBuffer dst) throws SSLException { return OK_UNWRAP; } public SSLEngineResult unwrap(final ByteBuffer src, final ByteBuffer[] dsts, final int offset, final int length) throws SSLException { SSLEngine next; final int mark = src.position(); try { if (src.remaining() < SNISSLExplorer.RECORD_HEADER_SIZE) { packetBufferSize = SNISSLExplorer.RECORD_HEADER_SIZE; return UNDERFLOW_UNWRAP; } final int requiredSize = SNISSLExplorer.getRequiredSize(src); if (src.remaining() < requiredSize) { packetBufferSize = requiredSize; return UNDERFLOW_UNWRAP; } List names = SNISSLExplorer.explore(src); SSLContext sslContext = selector.getContext(names); if (sslContext == null) { // no SSL context is available throw UndertowMessages.MESSAGES.noContextForSslConnection(); } next = engineFunction.apply(sslContext); if (enabledSuites != null) { next.setEnabledCipherSuites(enabledSuites); } if (enabledProtocols != null) { next.setEnabledProtocols(enabledProtocols); } next.setUseClientMode(false); final int flagsVal = flags.get(); if ((flagsVal & FL_WANT_C_AUTH) != 0) { next.setWantClientAuth(true); } else if ((flagsVal & FL_NEED_C_AUTH) != 0) { next.setNeedClientAuth(true); } if ((flagsVal & FL_SESSION_CRE) != 0) { next.setEnableSessionCreation(true); } next = selectionCallback.apply(next); currentRef.set(next); } finally { src.position(mark); } return next.unwrap(src, dsts, offset, length); } public Runnable getDelegatedTask() { return null; } public void closeInbound() throws SSLException { currentRef.set(CLOSED_STATE); } public boolean isInboundDone() { return false; } public void closeOutbound() { currentRef.set(CLOSED_STATE); } public boolean isOutboundDone() { return false; } public String[] getSupportedCipherSuites() { if(enabledSuites == null) { return new String[0]; } return enabledSuites; } public String[] getEnabledCipherSuites() { return enabledSuites; } public void setEnabledCipherSuites(final String[] suites) { this.enabledSuites = suites; } public String[] getSupportedProtocols() { if(enabledProtocols == null) { return new String[0]; } //this kinda sucks, but there is not much else we can do return enabledProtocols; } public String[] getEnabledProtocols() { return enabledProtocols; } public void setEnabledProtocols(final String[] protocols) { this.enabledProtocols = protocols; } public SSLSession getSession() { return null; } public void beginHandshake() throws SSLException { } public SSLEngineResult.HandshakeStatus getHandshakeStatus() { return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; } public void setUseClientMode(final boolean mode) { if (mode) throw new UnsupportedOperationException(); } public boolean getUseClientMode() { return false; } public void setNeedClientAuth(final boolean need) { final AtomicInteger flags = this.flags; int oldVal, newVal; do { oldVal = flags.get(); if (((oldVal & FL_NEED_C_AUTH) != 0) == need) { return; } newVal = oldVal & FL_SESSION_CRE | FL_NEED_C_AUTH; } while (!flags.compareAndSet(oldVal, newVal)); } public boolean getNeedClientAuth() { return (flags.get() & FL_NEED_C_AUTH) != 0; } public void setWantClientAuth(final boolean want) { final AtomicInteger flags = this.flags; int oldVal, newVal; do { oldVal = flags.get(); if (((oldVal & FL_WANT_C_AUTH) != 0) == want) { return; } newVal = oldVal & FL_SESSION_CRE | FL_WANT_C_AUTH; } while (!flags.compareAndSet(oldVal, newVal)); } public boolean getWantClientAuth() { return (flags.get() & FL_WANT_C_AUTH) != 0; } public void setEnableSessionCreation(final boolean flag) { final AtomicInteger flags = this.flags; int oldVal, newVal; do { oldVal = flags.get(); if (((oldVal & FL_SESSION_CRE) != 0) == flag) { return; } newVal = oldVal ^ FL_SESSION_CRE; } while (!flags.compareAndSet(oldVal, newVal)); } public boolean getEnableSessionCreation() { return (flags.get() & FL_SESSION_CRE) != 0; } } static final SSLEngine CLOSED_STATE = new SSLEngine() { public SSLEngineResult wrap(final ByteBuffer[] srcs, final int offset, final int length, final ByteBuffer dst) throws SSLException { throw new SSLException(new ClosedChannelException()); } public SSLEngineResult unwrap(final ByteBuffer src, final ByteBuffer[] dsts, final int offset, final int length) throws SSLException { throw new SSLException(new ClosedChannelException()); } public Runnable getDelegatedTask() { return null; } public void closeInbound() throws SSLException { } public boolean isInboundDone() { return true; } public void closeOutbound() { } public boolean isOutboundDone() { return true; } public String[] getSupportedCipherSuites() { throw new UnsupportedOperationException(); } public String[] getEnabledCipherSuites() { throw new UnsupportedOperationException(); } public void setEnabledCipherSuites(final String[] suites) { throw new UnsupportedOperationException(); } public String[] getSupportedProtocols() { throw new UnsupportedOperationException(); } public String[] getEnabledProtocols() { throw new UnsupportedOperationException(); } public void setEnabledProtocols(final String[] protocols) { throw new UnsupportedOperationException(); } public SSLSession getSession() { return null; } public void beginHandshake() throws SSLException { throw new SSLException(new ClosedChannelException()); } public SSLEngineResult.HandshakeStatus getHandshakeStatus() { return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; } public void setUseClientMode(final boolean mode) { throw new UnsupportedOperationException(); } public boolean getUseClientMode() { return false; } public void setNeedClientAuth(final boolean need) { } public boolean getNeedClientAuth() { return false; } public void setWantClientAuth(final boolean want) { } public boolean getWantClientAuth() { return false; } public void setEnableSessionCreation(final boolean flag) { } public boolean getEnableSessionCreation() { return false; } }; } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ssl/SNISSLExplorer.java000066400000000000000000000444051420065311100305670ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2018 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * 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. */ package io.undertow.protocols.ssl; import java.nio.ByteBuffer; import java.nio.BufferUnderflowException; import java.io.IOException; import javax.net.ssl.SNIHostName; import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLException; import javax.net.ssl.StandardConstants; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import io.undertow.UndertowMessages; /** * Instances of this class acts as an explorer of the network data of an * SSL/TLS connection. */ final class SNISSLExplorer { // Private constructor prevents construction outside this class. private SNISSLExplorer() { } /** * The header size of TLS/SSL records. *

* The value of this constant is {@value}. */ public static final int RECORD_HEADER_SIZE = 0x05; /** * Returns the required number of bytes in the {@code source} * {@link ByteBuffer} necessary to explore SSL/TLS connection. *

* This method tries to parse as few bytes as possible from * {@code source} byte buffer to get the length of an * SSL/TLS record. *

* This method accesses the {@code source} parameter in read-only * mode, and does not update the buffer's properties such as capacity, * limit, position, and mark values. * * @param source * a {@link ByteBuffer} containing * inbound or outbound network data for an SSL/TLS connection. * @throws BufferUnderflowException if less than {@code RECORD_HEADER_SIZE} * bytes remaining in {@code source} * @return the required size in byte to explore an SSL/TLS connection */ public static int getRequiredSize(ByteBuffer source) { ByteBuffer input = source.duplicate(); // Do we have a complete header? if (input.remaining() < RECORD_HEADER_SIZE) { throw new BufferUnderflowException(); } // Is it a handshake message? byte firstByte = input.get(); input.get(); byte thirdByte = input.get(); if ((firstByte & 0x80) != 0 && thirdByte == 0x01) { return RECORD_HEADER_SIZE; // Only need the header fields } else { return ((input.get() & 0xFF) << 8 | input.get() & 0xFF) + 5; } } /** * Returns the required number of bytes in the {@code source} byte array * necessary to explore SSL/TLS connection. *

* This method tries to parse as few bytes as possible from * {@code source} byte array to get the length of an * SSL/TLS record. * * @param source * a byte array containing inbound or outbound network data for * an SSL/TLS connection. * @param offset * the start offset in array {@code source} at which the * network data is read from. * @param length * the maximum number of bytes to read. * * @throws BufferUnderflowException if less than {@code RECORD_HEADER_SIZE} * bytes remaining in {@code source} * @return the required size in byte to explore an SSL/TLS connection */ public static int getRequiredSize(byte[] source, int offset, int length) throws IOException { ByteBuffer byteBuffer = ByteBuffer.wrap(source, offset, length).asReadOnlyBuffer(); return getRequiredSize(byteBuffer); } /** * Launch and explore the security capabilities from byte buffer. *

* This method tries to parse as few records as possible from * {@code source} byte buffer to get the capabilities * of an SSL/TLS connection. *

* Please NOTE that this method must be called before any handshaking * occurs. The behavior of this method is not defined in this release * if the handshake has begun, or has completed. *

* This method accesses the {@code source} parameter in read-only * mode, and does not update the buffer's properties such as capacity, * limit, position, and mark values. * * @param source * a {@link ByteBuffer} containing * inbound or outbound network data for an SSL/TLS connection. * * @throws IOException on network data error * @throws BufferUnderflowException if not enough source bytes available * to make a complete exploration. * * @return the explored capabilities of the SSL/TLS * connection */ public static List explore(ByteBuffer source) throws SSLException { ByteBuffer input = source.duplicate(); // Do we have a complete header? if (input.remaining() < RECORD_HEADER_SIZE) { throw new BufferUnderflowException(); } // Is it a handshake message? byte firstByte = input.get(); byte secondByte = input.get(); byte thirdByte = input.get(); if ((firstByte & 0x80) != 0 && thirdByte == 0x01) { // looks like a V2ClientHello return Collections.emptyList(); } else if (firstByte == 22) { // 22: handshake record return exploreTLSRecord(input, firstByte, secondByte, thirdByte); } else { throw UndertowMessages.MESSAGES.notHandshakeRecord(); } } /** * Launch and explore the security capabilities from byte array. *

* Please NOTE that this method must be called before any handshaking * occurs. The behavior of this method is not defined in this release * if the handshake has begun, or has completed. Once handshake has * begun, or has completed, the security capabilities can not and * should not be launched with this method. * * @param source * a byte array containing inbound or outbound network data for * an SSL/TLS connection. * @param offset * the start offset in array {@code source} at which the * network data is read from. * @param length * the maximum number of bytes to read. * * @throws IOException on network data error * @throws BufferUnderflowException if not enough source bytes available * to make a complete exploration. * @return the explored capabilities of the SSL/TLS * connection * * @see #explore(ByteBuffer) */ public static List explore(byte[] source, int offset, int length) throws IOException { ByteBuffer byteBuffer = ByteBuffer.wrap(source, offset, length).asReadOnlyBuffer(); return explore(byteBuffer); } /* * struct { * uint8 major; * uint8 minor; * } ProtocolVersion; * * enum { * change_cipher_spec(20), alert(21), handshake(22), * application_data(23), (255) * } ContentType; * * struct { * ContentType type; * ProtocolVersion version; * uint16 length; * opaque fragment[TLSPlaintext.length]; * } TLSPlaintext; */ private static List exploreTLSRecord( ByteBuffer input, byte firstByte, byte secondByte, byte thirdByte) throws SSLException { // Is it a handshake message? if (firstByte != 22) { // 22: handshake record throw UndertowMessages.MESSAGES.notHandshakeRecord(); } // Is there enough data for a full record? int recordLength = getInt16(input); if (recordLength > input.remaining()) { throw new BufferUnderflowException(); } // We have already had enough source bytes. try { return exploreHandshake(input, secondByte, thirdByte, recordLength); } catch (BufferUnderflowException ignored) { throw UndertowMessages.MESSAGES.invalidHandshakeRecord(); } } /* * enum { * hello_request(0), client_hello(1), server_hello(2), * certificate(11), server_key_exchange (12), * certificate_request(13), server_hello_done(14), * certificate_verify(15), client_key_exchange(16), * finished(20) * (255) * } HandshakeType; * * struct { * HandshakeType msg_type; * uint24 length; * select (HandshakeType) { * case hello_request: HelloRequest; * case client_hello: ClientHello; * case server_hello: ServerHello; * case certificate: Certificate; * case server_key_exchange: ServerKeyExchange; * case certificate_request: CertificateRequest; * case server_hello_done: ServerHelloDone; * case certificate_verify: CertificateVerify; * case client_key_exchange: ClientKeyExchange; * case finished: Finished; * } body; * } Handshake; */ private static List exploreHandshake( ByteBuffer input, byte recordMajorVersion, byte recordMinorVersion, int recordLength) throws SSLException { // What is the handshake type? byte handshakeType = input.get(); if (handshakeType != 0x01) { // 0x01: client_hello message throw UndertowMessages.MESSAGES.expectedClientHello(); } // What is the handshake body length? int handshakeLength = getInt24(input); // Theoretically, a single handshake message might span multiple // records, but in practice this does not occur. if (handshakeLength > recordLength - 4) { // 4: handshake header size throw UndertowMessages.MESSAGES.multiRecordSSLHandshake(); } input = input.duplicate(); input.limit(handshakeLength + input.position()); return exploreClientHello(input, recordMajorVersion, recordMinorVersion); } /* * struct { * uint32 gmt_unix_time; * opaque random_bytes[28]; * } Random; * * opaque SessionID<0..32>; * * uint8 CipherSuite[2]; * * enum { null(0), (255) } CompressionMethod; * * struct { * ProtocolVersion client_version; * Random random; * SessionID session_id; * CipherSuite cipher_suites<2..2^16-2>; * CompressionMethod compression_methods<1..2^8-1>; * select (extensions_present) { * case false: * struct {}; * case true: * Extension extensions<0..2^16-1>; * }; * } ClientHello; */ private static List exploreClientHello( ByteBuffer input, byte recordMajorVersion, byte recordMinorVersion) throws SSLException { ExtensionInfo info = null; // client version input.get(); //helloMajorVersion input.get(); //helloMinorVersion // ignore random int position = input.position(); input.position(position + 32); // 32: the length of Random // ignore session id ignoreByteVector8(input); // ignore cipher_suites int csLen = getInt16(input); while (csLen > 0) { getInt8(input); getInt8(input); csLen -= 2; } // ignore compression methods ignoreByteVector8(input); if (input.remaining() > 0) { info = exploreExtensions(input); } final List snList = info != null ? info.sni : Collections.emptyList(); return snList; } /* * struct { * ExtensionType extension_type; * opaque extension_data<0..2^16-1>; * } Extension; * * enum { * server_name(0), max_fragment_length(1), * client_certificate_url(2), trusted_ca_keys(3), * truncated_hmac(4), status_request(5), (65535) * } ExtensionType; */ private static ExtensionInfo exploreExtensions(ByteBuffer input) throws SSLException { List sni = Collections.emptyList(); List alpn = Collections.emptyList(); int length = getInt16(input); // length of extensions while (length > 0) { int extType = getInt16(input); // extension type int extLen = getInt16(input); // length of extension data if (extType == 0x00) { // 0x00: type of server name indication sni = exploreSNIExt(input, extLen); } else if (extType == 0x10) { // 0x10: type of alpn alpn = exploreALPN(input, extLen); } else { // ignore other extensions ignoreByteVector(input, extLen); } length -= extLen + 4; } return new ExtensionInfo(sni, alpn); } /* * opaque ProtocolName<1..2^8-1>; * * struct { * ProtocolName protocol_name_list<2..2^16-1> * } ProtocolNameList; * */ private static List exploreALPN(ByteBuffer input, int extLen) throws SSLException { final ArrayList strings = new ArrayList<>(); int rem = extLen; if (extLen >= 2) { int listLen = getInt16(input); if (listLen == 0 || listLen + 2 != extLen) { throw UndertowMessages.MESSAGES.invalidTlsExt(); } rem -= 2; while (rem > 0) { int len = getInt8(input); if (len > rem) { throw UndertowMessages.MESSAGES.notEnoughData(); } byte[] b = new byte[len]; input.get(b); strings.add(new String(b, StandardCharsets.UTF_8)); rem -= len + 1; } } return strings.isEmpty() ? Collections.emptyList() : strings; } /* * struct { * NameType name_type; * select (name_type) { * case host_name: HostName; * } name; * } ServerName; * * enum { * host_name(0), (255) * } NameType; * * opaque HostName<1..2^16-1>; * * struct { * ServerName server_name_list<1..2^16-1> * } ServerNameList; */ private static List exploreSNIExt(ByteBuffer input, int extLen) throws SSLException { Map sniMap = new LinkedHashMap<>(); int remains = extLen; if (extLen >= 2) { // "server_name" extension in ClientHello int listLen = getInt16(input); // length of server_name_list if (listLen == 0 || listLen + 2 != extLen) { throw UndertowMessages.MESSAGES.invalidTlsExt(); } remains -= 2; // 0x02: the length field of server_name_list while (remains > 0) { int code = getInt8(input); // name_type int snLen = getInt16(input); // length field of server name if (snLen > remains) { throw UndertowMessages.MESSAGES.notEnoughData(); } byte[] encoded = new byte[snLen]; input.get(encoded); SNIServerName serverName; switch (code) { case StandardConstants.SNI_HOST_NAME: if (encoded.length == 0) { throw UndertowMessages.MESSAGES.emptyHostNameSni(); } serverName = new SNIHostName(encoded); break; default: serverName = new UnknownServerName(code, encoded); } // check for duplicated server name type if (sniMap.put(serverName.getType(), serverName) != null) { throw UndertowMessages.MESSAGES.duplicatedSniServerName(serverName.getType()); } remains -= encoded.length + 3; // NameType: 1 byte // HostName length: 2 bytes } } else if (extLen == 0) { // "server_name" extension in ServerHello throw UndertowMessages.MESSAGES.invalidTlsExt(); } if (remains != 0) { throw UndertowMessages.MESSAGES.invalidTlsExt(); } return Collections.unmodifiableList(new ArrayList<>(sniMap.values())); } private static int getInt8(ByteBuffer input) { return input.get(); } private static int getInt16(ByteBuffer input) { return (input.get() & 0xFF) << 8 | input.get() & 0xFF; } private static int getInt24(ByteBuffer input) { return (input.get() & 0xFF) << 16 | (input.get() & 0xFF) << 8 | input.get() & 0xFF; } private static void ignoreByteVector8(ByteBuffer input) { ignoreByteVector(input, getInt8(input)); } private static void ignoreByteVector(ByteBuffer input, int length) { if (length != 0) { int position = input.position(); input.position(position + length); } } static final class UnknownServerName extends SNIServerName { UnknownServerName(int code, byte[] encoded) { super(code, encoded); } } static final class ExtensionInfo { final List sni; final List alpn; ExtensionInfo(final List sni, final List alpn) { this.sni = sni; this.alpn = alpn; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ssl/SslConduit.java000066400000000000000000001453311420065311100301220ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.ssl; import java.io.IOException; import java.io.InterruptedIOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSession; import io.undertow.UndertowLogger; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.DefaultByteBufferPool; import org.xnio.Buffers; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.StreamConnection; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.ConduitReadableByteChannel; import org.xnio.conduits.ConduitStreamSinkChannel; import org.xnio.conduits.ConduitStreamSourceChannel; import org.xnio.conduits.ConduitWritableByteChannel; import org.xnio.conduits.Conduits; import org.xnio.conduits.ReadReadyHandler; import org.xnio.conduits.StreamSinkConduit; import org.xnio.conduits.StreamSourceConduit; import org.xnio.conduits.WriteReadyHandler; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.allAreSet; import static org.xnio.Bits.anyAreSet; /** * Conduit for SSL connections. * * @author Stuart Douglas * @author Flavia Rainone */ public class SslConduit implements StreamSourceConduit, StreamSinkConduit { public static final int MAX_READ_LISTENER_INVOCATIONS = Integer.getInteger("io.undertow.ssl.max-read-listener-invocations", 100); /** * If this is set we are in the middle of a handshake, and we cannot * read any more data until we have written out our wrap result */ private static final int FLAG_READ_REQUIRES_WRITE = 1; /** * If this is set we are in the process of handshaking and we cannot write any * more data until we have read unwrapped data from the remote peer */ private static final int FLAG_WRITE_REQUIRES_READ = 1 << 1; /** * If reads are resumed. The underlying delegate may not be resumed if a write is required * to make progress. */ private static final int FLAG_READS_RESUMED = 1 << 2; /** * If writes are resumed, the underlying delegate may not be resumed if a read is required */ private static final int FLAG_WRITES_RESUMED = 1 << 3; /** * If there is data in the {@link #dataToUnwrap} buffer, and the last unwrap attempt did not result * in a buffer underflow */ private static final int FLAG_DATA_TO_UNWRAP = 1 << 4; /** * If the user has shutdown reads */ private static final int FLAG_READ_SHUTDOWN = 1 << 5; /** * If the user has shutdown writes */ private static final int FLAG_WRITE_SHUTDOWN = 1 << 6; /** * If the engine has been shut down */ private static final int FLAG_ENGINE_INBOUND_SHUTDOWN = 1 << 7; /** * If the engine has been shut down */ private static final int FLAG_ENGINE_OUTBOUND_SHUTDOWN = 1 << 8; private static final int FLAG_DELEGATE_SINK_SHUTDOWN = 1 << 9; private static final int FLAG_DELEGATE_SOURCE_SHUTDOWN = 1 << 10; private static final int FLAG_IN_HANDSHAKE = 1 << 11; private static final int FLAG_CLOSED = 1 << 12; private static final int FLAG_WRITE_CLOSED = 1 << 13; private static final int FLAG_READ_CLOSED = 1 << 14; public static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0); /** * Buffer pool created and used only when large fragments handling is * enabled in the underlying SSL Engine. When this happens, we need * a specific buffer with expanded capacity. */ private static volatile ByteBufferPool expandedBufferPool; private final UndertowSslConnection connection; private final StreamConnection delegate; private final Executor delegatedTaskExecutor; private SSLEngine engine; private final StreamSinkConduit sink; private final StreamSourceConduit source; private final ByteBufferPool bufferPool; private final Runnable handshakeCallback; private volatile int state = 0; private volatile int outstandingTasks = 0; /** * Data that has been wrapped and is ready to be sent to the underlying channel. * * This will be null if there is no data */ private volatile PooledByteBuffer wrappedData; /** * Data that has been read from the underlying channel, and needs to be unwrapped. * * This will be null if there is no data. If there is data the {@link #FLAG_DATA_TO_UNWRAP} * flag must still be checked, otherwise there may be situations where even though some data * has been read there is not enough to unwrap (i.e. the engine returned buffer underflow). */ private volatile PooledByteBuffer dataToUnwrap; /** * Unwrapped data, ready to be delivered to the application. Will be null if there is no data. * * If possible we avoid allocating this buffer, and instead unwrap directly into the end users buffer. */ private volatile PooledByteBuffer unwrappedData; private SslWriteReadyHandler writeReadyHandler; private SslReadReadyHandler readReadyHandler; private int readListenerInvocationCount; private boolean invokingReadListenerHandshake = false; private final Runnable runReadListenerCommand = new Runnable() { @Override public void run() { final int count = readListenerInvocationCount; try { readReadyHandler.readReady(); } finally { if(count == readListenerInvocationCount) { readListenerInvocationCount = 0; } } } }; private final Runnable runReadListenerAndResumeCommand = new Runnable() { @Override public void run() { if (allAreSet(state, FLAG_READS_RESUMED)) { delegate.getSourceChannel().resumeReads(); } runReadListenerCommand.run(); } }; SslConduit(UndertowSslConnection connection, StreamConnection delegate, SSLEngine engine, Executor delegatedTaskExecutor, ByteBufferPool bufferPool, Runnable handshakeCallback) { this.connection = connection; this.delegate = delegate; this.handshakeCallback = handshakeCallback; this.sink = delegate.getSinkChannel().getConduit(); this.source = delegate.getSourceChannel().getConduit(); this.engine = engine; this.delegatedTaskExecutor = delegatedTaskExecutor; this.bufferPool = bufferPool; delegate.getSourceChannel().getConduit().setReadReadyHandler(readReadyHandler = new SslReadReadyHandler(null)); delegate.getSinkChannel().getConduit().setWriteReadyHandler(writeReadyHandler = new SslWriteReadyHandler(null)); if(engine.getUseClientMode()) { state = FLAG_IN_HANDSHAKE | FLAG_READ_REQUIRES_WRITE; } else { state = FLAG_IN_HANDSHAKE | FLAG_WRITE_REQUIRES_READ; } } @Override public void terminateReads() throws IOException { state |= FLAG_READ_SHUTDOWN; notifyReadClosed(); } @Override public boolean isReadShutdown() { return anyAreSet(state, FLAG_READ_SHUTDOWN); } @Override public void resumeReads() { if(anyAreSet(state, FLAG_READS_RESUMED)) { //already resumed return; } resumeReads(false); } @Override public void suspendReads() { state &= ~FLAG_READS_RESUMED; if(!allAreSet(state, FLAG_WRITES_RESUMED | FLAG_WRITE_REQUIRES_READ)) { delegate.getSourceChannel().suspendReads(); } } @Override public void wakeupReads() { resumeReads(true); } private void resumeReads(boolean wakeup) { state |= FLAG_READS_RESUMED; if(anyAreSet(state, FLAG_READ_REQUIRES_WRITE)) { delegate.getSinkChannel().resumeWrites(); } else { if(anyAreSet(state, FLAG_DATA_TO_UNWRAP) || wakeup || unwrappedData != null) { runReadListener(true); } else { delegate.getSourceChannel().resumeReads(); } } } private void runReadListener(final boolean resumeInListener) { try { if(readListenerInvocationCount++ == MAX_READ_LISTENER_INVOCATIONS) { UndertowLogger.REQUEST_LOGGER.sslReadLoopDetected(this); IoUtils.safeClose(connection, delegate); close(); return; } if(resumeInListener) { delegate.getIoThread().execute(runReadListenerAndResumeCommand); } else { delegate.getIoThread().execute(runReadListenerCommand); } } catch (Throwable e) { //will only happen on shutdown IoUtils.safeClose(connection, delegate); UndertowLogger.REQUEST_IO_LOGGER.debugf(e, "Failed to queue read listener invocation"); } } private void runWriteListener() { try { delegate.getIoThread().execute(new Runnable() { @Override public void run() { writeReadyHandler.writeReady(); } }); } catch (Throwable e) { //will only happen on shutdown IoUtils.safeClose(connection, delegate); UndertowLogger.REQUEST_IO_LOGGER.debugf(e, "Failed to queue read listener invocation"); } } @Override public boolean isReadResumed() { return anyAreSet(state, FLAG_READS_RESUMED); } @Override public void awaitReadable() throws IOException { synchronized (this) { if(outstandingTasks > 0) { try { wait(); return; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InterruptedIOException(); } } } if(unwrappedData != null) { return; } if(anyAreSet(state, FLAG_DATA_TO_UNWRAP)) { return; } if(anyAreSet(state, FLAG_READ_REQUIRES_WRITE)) { awaitWritable(); return; } source.awaitReadable(); } @Override public void awaitReadable(long time, TimeUnit timeUnit) throws IOException { synchronized (this) { if(outstandingTasks > 0) { try { wait(timeUnit.toMillis(time)); return; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InterruptedIOException(); } } } if(unwrappedData != null) { return; } if(anyAreSet(state, FLAG_DATA_TO_UNWRAP)) { return; } if(anyAreSet(state, FLAG_READ_REQUIRES_WRITE)) { awaitWritable(time, timeUnit); return; } source.awaitReadable(time, timeUnit); } @Override public XnioIoThread getReadThread() { return delegate.getIoThread(); } @Override public void setReadReadyHandler(ReadReadyHandler handler) { delegate.getSourceChannel().getConduit().setReadReadyHandler(readReadyHandler = new SslReadReadyHandler(handler)); } @Override public long transferFrom(FileChannel src, long position, long count) throws IOException { if(anyAreSet(state, FLAG_WRITE_SHUTDOWN)) { throw new ClosedChannelException(); } return src.transferTo(position, count, new ConduitWritableByteChannel(this)); } @Override public long transferFrom(StreamSourceChannel source, long count, ByteBuffer throughBuffer) throws IOException { if(anyAreSet(state, FLAG_WRITE_SHUTDOWN)) { throw new ClosedChannelException(); } return IoUtils.transfer(source, count, throughBuffer, new ConduitWritableByteChannel(this)); } @Override public int write(ByteBuffer src) throws IOException { if(anyAreSet(state, FLAG_WRITE_SHUTDOWN)) { throw new ClosedChannelException(); } return (int) doWrap(new ByteBuffer[]{src}, 0, 1); } @Override public long write(ByteBuffer[] srcs, int offs, int len) throws IOException { if(anyAreSet(state, FLAG_WRITE_SHUTDOWN)) { throw new ClosedChannelException(); } return doWrap(srcs, offs, len); } @Override public int writeFinal(ByteBuffer src) throws IOException { if(anyAreSet(state, FLAG_WRITE_SHUTDOWN)) { throw new ClosedChannelException(); } return Conduits.writeFinalBasic(this, src); } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { return Conduits.writeFinalBasic(this, srcs, offset, length); } @Override public void terminateWrites() throws IOException { state |= FLAG_WRITE_SHUTDOWN; } @Override public boolean isWriteShutdown() { return false; //todo } @Override public void resumeWrites() { state |= FLAG_WRITES_RESUMED; if(anyAreSet(state, FLAG_WRITE_REQUIRES_READ)) { delegate.getSourceChannel().resumeReads(); } else { delegate.getSinkChannel().resumeWrites(); } } @Override public void suspendWrites() { state &= ~FLAG_WRITES_RESUMED; if(!allAreSet(state, FLAG_READS_RESUMED | FLAG_READ_REQUIRES_WRITE)) { delegate.getSinkChannel().suspendWrites(); } } @Override public void wakeupWrites() { state |= FLAG_WRITES_RESUMED; getWriteThread().execute(new Runnable() { @Override public void run() { resumeWrites(); writeReadyHandler.writeReady(); } }); } @Override public boolean isWriteResumed() { return anyAreSet(state, FLAG_WRITES_RESUMED); } @Override public void awaitWritable() throws IOException { if(anyAreSet(state, FLAG_WRITE_SHUTDOWN)) { return; } if(outstandingTasks > 0) { synchronized (this) { if(outstandingTasks > 0) { try { this.wait(); return; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InterruptedIOException(); } } } } if(anyAreSet(state, FLAG_WRITE_REQUIRES_READ)) { awaitReadable(); return; } sink.awaitWritable(); } @Override public void awaitWritable(long time, TimeUnit timeUnit) throws IOException { if(anyAreSet(state, FLAG_WRITE_SHUTDOWN)) { return; } if(outstandingTasks > 0) { synchronized (this) { if(outstandingTasks > 0) { try { this.wait(timeUnit.toMillis(time)); return; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InterruptedIOException(); } } } } if(anyAreSet(state, FLAG_WRITE_REQUIRES_READ)) { awaitReadable(time, timeUnit); return; } sink.awaitWritable(); } @Override public XnioIoThread getWriteThread() { return delegate.getIoThread(); } @Override public void setWriteReadyHandler(WriteReadyHandler handler) { delegate.getSinkChannel().getConduit().setWriteReadyHandler(writeReadyHandler = new SslWriteReadyHandler(handler)); } @Override public void truncateWrites() throws IOException { try { notifyWriteClosed(); } finally { delegate.getSinkChannel().close(); } } @Override public boolean flush() throws IOException { if(anyAreSet(state, FLAG_DELEGATE_SINK_SHUTDOWN)) { return sink.flush(); } if(wrappedData != null) { doWrap(null, 0, 0); if(wrappedData != null) { return false; } } if(allAreSet(state, FLAG_WRITE_SHUTDOWN)) { if(allAreClear(state, FLAG_ENGINE_OUTBOUND_SHUTDOWN)) { state |= FLAG_ENGINE_OUTBOUND_SHUTDOWN; engine.closeOutbound(); doWrap(null, 0, 0); if(wrappedData != null) { return false; } } else if(wrappedData != null && allAreClear(state, FLAG_DELEGATE_SINK_SHUTDOWN)) { doWrap(null, 0, 0); if(wrappedData != null) { return false; } } if(allAreClear(state, FLAG_DELEGATE_SINK_SHUTDOWN)) { sink.terminateWrites(); state |= FLAG_DELEGATE_SINK_SHUTDOWN; notifyWriteClosed(); } boolean result = sink.flush(); if(result && anyAreSet(state, FLAG_READ_CLOSED)) { closed(); } return result; } return sink.flush(); } @Override public long transferTo(long position, long count, FileChannel target) throws IOException { if(anyAreSet(state, FLAG_READ_SHUTDOWN)) { return -1; } return target.transferFrom(new ConduitReadableByteChannel(this), position, count); } @Override public long transferTo(long count, ByteBuffer throughBuffer, StreamSinkChannel target) throws IOException { if(anyAreSet(state, FLAG_READ_SHUTDOWN)) { return -1; } return IoUtils.transfer(new ConduitReadableByteChannel(this), count, throughBuffer, target); } @Override public int read(ByteBuffer dst) throws IOException { if(anyAreSet(state, FLAG_READ_SHUTDOWN)) { return -1; } return (int) doUnwrap(new ByteBuffer[]{dst}, 0, 1); } @Override public long read(ByteBuffer[] dsts, int offs, int len) throws IOException { if(anyAreSet(state, FLAG_READ_SHUTDOWN)) { return -1; } return doUnwrap(dsts, offs, len); } @Override public XnioWorker getWorker() { return delegate.getWorker(); } private Executor getDelegatedTaskExecutor() { return delegatedTaskExecutor == null ? getWorker() : delegatedTaskExecutor; } void notifyWriteClosed() { if(anyAreSet(state, FLAG_WRITE_CLOSED)) { return; } boolean runListener = isWriteResumed() && anyAreSet(state, FLAG_CLOSED); connection.writeClosed(); engine.closeOutbound(); state |= FLAG_WRITE_CLOSED | FLAG_ENGINE_OUTBOUND_SHUTDOWN; if(anyAreSet(state, FLAG_READ_CLOSED)) { closed(); } if(anyAreSet(state, FLAG_READ_REQUIRES_WRITE)) { notifyReadClosed(); } state &= ~FLAG_WRITE_REQUIRES_READ; //unclean shutdown, run the listener if(runListener) { runWriteListener(); } } void notifyReadClosed() { if(anyAreSet(state, FLAG_READ_CLOSED)) { return; } boolean runListener = isReadResumed() && anyAreSet(state, FLAG_CLOSED); connection.readClosed(); try { engine.closeInbound(); } catch (SSLException e) { UndertowLogger.REQUEST_IO_LOGGER.trace("Exception closing read side of SSL channel", e); if(allAreClear(state, FLAG_WRITE_CLOSED) && isWriteResumed()) { runWriteListener(); } } state |= FLAG_READ_CLOSED | FLAG_ENGINE_INBOUND_SHUTDOWN | FLAG_READ_SHUTDOWN; if(anyAreSet(state, FLAG_WRITE_CLOSED)) { closed(); } if(anyAreSet(state, FLAG_WRITE_REQUIRES_READ)) { notifyWriteClosed(); } if(runListener) { runReadListener(false); } } public void startHandshake() throws SSLException { state |= FLAG_READ_REQUIRES_WRITE; engine.beginHandshake(); } public SSLSession getSslSession() { return engine.getSession(); } /** * Force the handshake to continue * * @throws IOException */ private void doHandshake() throws IOException { doUnwrap(null, 0, 0); doWrap(null, 0, 0); } /** * Unwrap channel data into the user buffers. If no user buffer is supplied (e.g. during handshaking) then the * unwrap will happen into the channels unwrap buffer. * * If some data has already been unwrapped it will simply be copied into the user buffers * and no unwrap will actually take place. * * @return true if the unwrap operation made progress, false otherwise * @throws SSLException */ private synchronized long doUnwrap(ByteBuffer[] userBuffers, int off, int len) throws IOException { if(anyAreSet(state, FLAG_CLOSED)) { throw new ClosedChannelException(); } if(outstandingTasks > 0) { return 0; } if(anyAreSet(state, FLAG_READ_REQUIRES_WRITE)) { doWrap(null, 0, 0); if(allAreClear(state, FLAG_WRITE_REQUIRES_READ)) { //unless a wrap is immediately required we just return return 0; } } boolean bytesProduced = false; PooledByteBuffer unwrappedData = this.unwrappedData; //copy any exiting data if(unwrappedData != null) { if(userBuffers != null) { long copied = Buffers.copy(userBuffers, off, len, unwrappedData.getBuffer()); if (!unwrappedData.getBuffer().hasRemaining()) { unwrappedData.close(); this.unwrappedData = null; } if(copied > 0) { readListenerInvocationCount = 0; } return copied; } } try { //we need to store how much data is in the unwrap buffer. If no progress can be made then we unset //the data to unwrap flag int dataToUnwrapLength; //try and read some data if we don't already have some if (allAreClear(state, FLAG_DATA_TO_UNWRAP)) { if (dataToUnwrap == null) { dataToUnwrap = bufferPool.allocate(); } int res; try { res = source.read(dataToUnwrap.getBuffer()); } catch (IOException | RuntimeException | Error e) { dataToUnwrap.close(); dataToUnwrap = null; throw e; } dataToUnwrap.getBuffer().flip(); if (res == -1) { dataToUnwrap.close(); dataToUnwrap = null; notifyReadClosed(); return -1; } else if (res == 0 && engine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.FINISHED) { //its possible there was some data in the buffer from a previous unwrap that had a buffer underflow //if not we just close the buffer so it does not hang around if(!dataToUnwrap.getBuffer().hasRemaining()) { dataToUnwrap.close(); dataToUnwrap = null; } return 0; } } dataToUnwrapLength = dataToUnwrap.getBuffer().remaining(); long original = 0; if (userBuffers != null) { original = Buffers.remaining(userBuffers); } //perform the actual unwrap operation //if possible this is done into the the user buffers, however //if none are supplied or this results in a buffer overflow then we allocate our own SSLEngineResult result; boolean unwrapBufferUsed = false; try { if (userBuffers != null) { result = engine.unwrap(this.dataToUnwrap.getBuffer(), userBuffers, off, len); if (result.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW) { //not enough space in the user buffers //we use our own unwrappedData = bufferPool.allocate(); ByteBuffer[] d = new ByteBuffer[len + 1]; System.arraycopy(userBuffers, off, d, 0, len); d[len] = unwrappedData.getBuffer(); result = engine.unwrap(this.dataToUnwrap.getBuffer(), d); unwrapBufferUsed = true; } bytesProduced = result.bytesProduced() > 0; } else { unwrapBufferUsed = true; if (unwrappedData == null) { unwrappedData = bufferPool.allocate(); } else { unwrappedData.getBuffer().compact(); } result = engine.unwrap(this.dataToUnwrap.getBuffer(), unwrappedData.getBuffer()); bytesProduced = result.bytesProduced() > 0; } } finally { if (unwrapBufferUsed) { unwrappedData.getBuffer().flip(); if (!unwrappedData.getBuffer().hasRemaining()) { unwrappedData.close(); unwrappedData = null; } } this.unwrappedData = unwrappedData; } if (result.getStatus() == SSLEngineResult.Status.CLOSED) { if(dataToUnwrap != null) { dataToUnwrap.close(); dataToUnwrap = null; } notifyReadClosed(); return -1; } if (!handleHandshakeResult(result)) { if (this.dataToUnwrap.getBuffer().hasRemaining() && result.getStatus() != SSLEngineResult.Status.BUFFER_UNDERFLOW && dataToUnwrap.getBuffer().remaining() != dataToUnwrapLength) { state |= FLAG_DATA_TO_UNWRAP; } else { state &= ~FLAG_DATA_TO_UNWRAP; } return 0; } if (result.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW) { state &= ~FLAG_DATA_TO_UNWRAP; } else if (result.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW) { UndertowLogger.REQUEST_LOGGER.sslBufferOverflow(this); IoUtils.safeClose(delegate); } else if (this.dataToUnwrap.getBuffer().hasRemaining() && dataToUnwrap.getBuffer().remaining() != dataToUnwrapLength) { state |= FLAG_DATA_TO_UNWRAP; } else { state &= ~FLAG_DATA_TO_UNWRAP; } if (userBuffers == null) { return 0; } else { long res = original - Buffers.remaining(userBuffers); if(res > 0) { //if data has been successfully returned this is not a read loop readListenerInvocationCount = 0; } return res; } } catch (SSLException e) { try { try { //we make an effort to write out the final record //this is best effort, there are no guarantees clearWriteRequiresRead(); doWrap(null, 0, 0); flush(); } catch (Exception e2) { UndertowLogger.REQUEST_LOGGER.debug("Failed to write out final SSL record", e2); } close(); } catch (Throwable ex) { //we ignore this UndertowLogger.REQUEST_LOGGER.debug("Exception closing SSLConduit after exception in doUnwrap", ex); } throw e; } catch (RuntimeException|IOException|Error e) { try { close(); } catch (Throwable ex) { //we ignore this UndertowLogger.REQUEST_LOGGER.debug("Exception closing SSLConduit after exception in doUnwrap", ex); } throw e; } finally { boolean requiresListenerInvocation = false; //if there is data in the buffer and reads are resumed we should re-run the listener //we always need to re-invoke if bytes have been produced, as the engine may have buffered some data if (bytesProduced || (unwrappedData != null && unwrappedData.isOpen() && unwrappedData.getBuffer().hasRemaining())) { requiresListenerInvocation = true; } if (dataToUnwrap != null) { //if there is no data in the buffer we just free it if (!dataToUnwrap.getBuffer().hasRemaining()) { dataToUnwrap.close(); dataToUnwrap = null; state &= ~FLAG_DATA_TO_UNWRAP; } else if (allAreClear(state, FLAG_DATA_TO_UNWRAP)) { //if there is not enough data in the buffer we compact it to make room for more dataToUnwrap.getBuffer().compact(); } else { //there is more data, make sure we trigger a read listener invocation requiresListenerInvocation = true; } } //if we are in the read listener handshake we don't need to invoke //as it is about to be invoked anyway if (requiresListenerInvocation && (anyAreSet(state, FLAG_READS_RESUMED) || allAreSet(state, FLAG_WRITE_REQUIRES_READ | FLAG_WRITES_RESUMED)) && !invokingReadListenerHandshake) { runReadListener(false); } } } /** * Wraps the user data and attempts to send it to the remote client. If data has already been buffered then * this is attempted to be sent first. * * If the supplied buffers are null then a wrap operation is still attempted, which will happen during the * handshaking process. * @param userBuffers The buffers * @param off The offset * @param len The length * @return The amount of data consumed * @throws IOException */ private synchronized long doWrap(ByteBuffer[] userBuffers, int off, int len) throws IOException { if(anyAreSet(state, FLAG_CLOSED)) { throw new ClosedChannelException(); } if(outstandingTasks > 0) { return 0; } if(anyAreSet(state, FLAG_WRITE_REQUIRES_READ)) { doUnwrap(null, 0, 0); if(allAreClear(state, FLAG_READ_REQUIRES_WRITE)) { //unless a wrap is immediately required we just return return 0; } } if(wrappedData != null) { int res = sink.write(wrappedData.getBuffer()); if(res == 0 || wrappedData.getBuffer().hasRemaining()) { return 0; } wrappedData.getBuffer().clear(); } else { wrappedData = bufferPool.allocate(); } try { SSLEngineResult result = wrapAndFlip(userBuffers, off, len); if (result.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW) { throw new IOException("underflow"); // unexpected result } else if (result.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW) { //if an earlier wrap succeeded we ignore this if (!wrappedData.getBuffer().hasRemaining()) { if (wrappedData.getBuffer().capacity() < engine.getSession().getPacketBufferSize()) { wrappedData.close(); final int bufferSize = engine.getSession().getPacketBufferSize(); UndertowLogger.REQUEST_IO_LOGGER.tracev( "Expanded buffer enabled due to overflow with empty buffer, buffer size is %s", bufferSize); if (expandedBufferPool == null || expandedBufferPool.getBufferSize() < bufferSize) expandedBufferPool = new DefaultByteBufferPool(false, bufferSize, -1, 12); wrappedData = expandedBufferPool.allocate(); result = wrapAndFlip(userBuffers, off, len); if (result.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW && !wrappedData.getBuffer().hasRemaining()) throw new IOException("overflow"); // unexpected result } else throw new IOException("overflow"); // unexpected result } } //attempt to write it out, if we fail we just return //we ignore the handshake status, as wrap will get called again if (wrappedData.getBuffer().hasRemaining()) { sink.write(wrappedData.getBuffer()); } //if it was not a complete write we just return if (wrappedData.getBuffer().hasRemaining()) { return result.bytesConsumed(); } if (!handleHandshakeResult(result)) { return 0; } if (result.getStatus() == SSLEngineResult.Status.CLOSED && userBuffers != null) { notifyWriteClosed(); throw new ClosedChannelException(); } return result.bytesConsumed(); } catch (RuntimeException|IOException|Error e) { try { close(); } catch (Throwable ex) { UndertowLogger.REQUEST_LOGGER.debug("Exception closing SSLConduit after exception in doWrap()", ex); } throw e; } finally { //this can be cleared if the channel is fully closed if(wrappedData != null) { if (!wrappedData.getBuffer().hasRemaining()) { wrappedData.close(); wrappedData = null; } } } } private SSLEngineResult wrapAndFlip(ByteBuffer[] userBuffers, int off, int len) throws IOException { SSLEngineResult result = null; while (result == null || (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP && result.getStatus() != SSLEngineResult.Status.BUFFER_OVERFLOW)) { if (userBuffers == null) { result = engine.wrap(EMPTY_BUFFER, wrappedData.getBuffer()); } else { result = engine.wrap(userBuffers, off, len, wrappedData.getBuffer()); } } wrappedData.getBuffer().flip(); return result; } private boolean handleHandshakeResult(SSLEngineResult result) throws IOException { switch (result.getHandshakeStatus()) { case NEED_TASK: { state |= FLAG_IN_HANDSHAKE; clearReadRequiresWrite(); clearWriteRequiresRead(); runTasks(); return false; } case NEED_UNWRAP: { clearReadRequiresWrite(); state |= FLAG_WRITE_REQUIRES_READ | FLAG_IN_HANDSHAKE; sink.suspendWrites(); if(anyAreSet(state, FLAG_WRITES_RESUMED)) { source.resumeReads(); } return false; } case NEED_WRAP: { clearWriteRequiresRead(); state |= FLAG_READ_REQUIRES_WRITE | FLAG_IN_HANDSHAKE; source.suspendReads(); if(anyAreSet(state, FLAG_READS_RESUMED)) { sink.resumeWrites(); } return false; } case FINISHED: { if(anyAreSet(state, FLAG_IN_HANDSHAKE)) { state &= ~FLAG_IN_HANDSHAKE; handshakeCallback.run(); } } } clearReadRequiresWrite(); clearWriteRequiresRead(); return true; } private void clearReadRequiresWrite() { if(anyAreSet(state, FLAG_READ_REQUIRES_WRITE)) { state &= ~FLAG_READ_REQUIRES_WRITE; if(anyAreSet(state, FLAG_READS_RESUMED)) { resumeReads(false); } if(allAreClear(state, FLAG_WRITES_RESUMED)) { sink.suspendWrites(); } } } private void clearWriteRequiresRead() { if(anyAreSet(state, FLAG_WRITE_REQUIRES_READ)) { state &= ~FLAG_WRITE_REQUIRES_READ; if(anyAreSet(state, FLAG_WRITES_RESUMED)) { wakeupWrites(); } if(allAreClear(state, FLAG_READS_RESUMED)) { source.suspendReads(); } } } private void closed() { if(anyAreSet(state, FLAG_CLOSED)) { return; } synchronized (this) { state |= FLAG_CLOSED | FLAG_DELEGATE_SINK_SHUTDOWN | FLAG_DELEGATE_SOURCE_SHUTDOWN | FLAG_WRITE_SHUTDOWN | FLAG_READ_SHUTDOWN; notifyReadClosed(); notifyWriteClosed(); if (dataToUnwrap != null) { dataToUnwrap.close(); dataToUnwrap = null; } if (unwrappedData != null) { unwrappedData.close(); unwrappedData = null; } if (wrappedData != null) { wrappedData.close(); wrappedData = null; } if (allAreClear(state, FLAG_ENGINE_OUTBOUND_SHUTDOWN)) { engine.closeOutbound(); } if (allAreClear(state, FLAG_ENGINE_INBOUND_SHUTDOWN)) { try { engine.closeInbound(); } catch (SSLException e) { UndertowLogger.REQUEST_LOGGER.ioException(e); } catch (Throwable t) { UndertowLogger.REQUEST_LOGGER.handleUnexpectedFailure(t); } } } IoUtils.safeClose(delegate); } /** * Execute all the delegated tasks on an executor which allows blocking, the worker executor by default. * * Once they are complete we notify any waiting threads and wakeup reads/writes as appropriate */ private void runTasks() throws IOException { //don't run anything in the IO thread till the tasks are done delegate.getSinkChannel().suspendWrites(); delegate.getSourceChannel().suspendReads(); List tasks = new ArrayList<>(); Runnable t = engine.getDelegatedTask(); while (t != null) { tasks.add(t); t = engine.getDelegatedTask(); } synchronized (this) { outstandingTasks += tasks.size(); for (final Runnable task : tasks) { Runnable wrappedTask = new Runnable() { @Override public void run() { try { task.run(); } finally { synchronized (SslConduit.this) { if (outstandingTasks == 1) { getWriteThread().execute(new Runnable() { @Override public void run() { synchronized (SslConduit.this) { SslConduit.this.notifyAll(); --outstandingTasks; try { doHandshake(); } catch (IOException | RuntimeException | Error e) { UndertowLogger.REQUEST_LOGGER.debug("Closing SSLConduit after exception on handshake", e); IoUtils.safeClose(connection); } if (anyAreSet(state, FLAG_READS_RESUMED)) { wakeupReads(); //wakeup, because we need to run an unwrap even if there is no data to be read } if (anyAreSet(state, FLAG_WRITES_RESUMED)) { resumeWrites(); //we don't need to wakeup, as the channel should be writable } } } }); } else { outstandingTasks--; } } } } }; try { getDelegatedTaskExecutor().execute(wrappedTask); } catch (RejectedExecutionException e) { UndertowLogger.REQUEST_IO_LOGGER.sslEngineDelegatedTaskRejected(e); IoUtils.safeClose(connection); throw DelegatedTaskRejectedClosedChannelException.INSTANCE; } } } } /** * A specialized {@link ClosedChannelException} which does not provide a stack trace. Tasks may be rejected * when the server is overloaded, so it's important not to create more work than necessary. */ private static final class DelegatedTaskRejectedClosedChannelException extends ClosedChannelException { private static final DelegatedTaskRejectedClosedChannelException INSTANCE = new DelegatedTaskRejectedClosedChannelException(); @Override public Throwable fillInStackTrace() { // Avoid the most expensive part of exception creation. return this; } // Ignore mutations @Override public Throwable initCause(Throwable ignored) { return this; } @Override public void setStackTrace(StackTraceElement[] ignored) { // no-op } } public SSLEngine getSSLEngine() { return engine; } /** * forcibly closes the connection */ public void close() { closed(); } /** * Read ready handler that deals with read-requires-write semantics */ private class SslReadReadyHandler implements ReadReadyHandler { private final ReadReadyHandler delegateHandler; private SslReadReadyHandler(ReadReadyHandler delegateHandler) { this.delegateHandler = delegateHandler; } @Override public void readReady() { if(anyAreSet(state, FLAG_WRITE_REQUIRES_READ) && anyAreSet(state, FLAG_WRITES_RESUMED | FLAG_READS_RESUMED) && !anyAreSet(state, FLAG_ENGINE_INBOUND_SHUTDOWN)) { try { invokingReadListenerHandshake = true; doHandshake(); } catch (IOException e) { UndertowLogger.REQUEST_LOGGER.ioException(e); IoUtils.safeClose(delegate); } catch (Throwable t) { UndertowLogger.REQUEST_IO_LOGGER.handleUnexpectedFailure(t); IoUtils.safeClose(delegate); } finally { invokingReadListenerHandshake = false; } if(!anyAreSet(state, FLAG_READS_RESUMED) && !allAreSet(state, FLAG_WRITE_REQUIRES_READ | FLAG_WRITES_RESUMED)) { delegate.getSourceChannel().suspendReads(); } } boolean noProgress = false; int initialDataToUnwrap = -1; int initialUnwrapped = -1; if (anyAreSet(state, FLAG_READS_RESUMED)) { if (delegateHandler == null) { final ChannelListener readListener = connection.getSourceChannel().getReadListener(); if (readListener == null) { suspendReads(); } else { if(anyAreSet(state, FLAG_DATA_TO_UNWRAP)) { initialDataToUnwrap = dataToUnwrap.getBuffer().remaining(); } if(unwrappedData != null) { initialUnwrapped = unwrappedData.getBuffer().remaining(); } ChannelListeners.invokeChannelListener(connection.getSourceChannel(), readListener); if(anyAreSet(state, FLAG_DATA_TO_UNWRAP) && initialDataToUnwrap == dataToUnwrap.getBuffer().remaining()) { noProgress = true; } else if(unwrappedData != null && unwrappedData.getBuffer().remaining() == initialUnwrapped) { noProgress = true; } } } else { delegateHandler.readReady(); } } if(anyAreSet(state, FLAG_READS_RESUMED) && (unwrappedData != null || anyAreSet(state, FLAG_DATA_TO_UNWRAP))) { if(anyAreSet(state, FLAG_READ_CLOSED)) { if(unwrappedData != null) { unwrappedData.close(); } if(dataToUnwrap != null) { dataToUnwrap.close(); } unwrappedData = null; dataToUnwrap = null; } else { //there is data in the buffers so we do a wakeup //as we may not get an actual read notification //if we need to write for the SSL engine to progress we don't invoke the read listener //otherwise it will run in a busy loop till the channel becomes writable //we also don't re-run if we have outstanding tasks if(!(anyAreSet(state, FLAG_READ_REQUIRES_WRITE) && wrappedData != null) && outstandingTasks == 0 && !noProgress) { runReadListener(false); } } } } @Override public void forceTermination() { try { if (delegateHandler != null) { delegateHandler.forceTermination(); } } finally { IoUtils.safeClose(delegate); } } @Override public void terminated() { ChannelListeners.invokeChannelListener(connection.getSourceChannel(), connection.getSourceChannel().getCloseListener()); } } /** * write read handler that deals with write-requires-read semantics */ private class SslWriteReadyHandler implements WriteReadyHandler { private final WriteReadyHandler delegateHandler; private SslWriteReadyHandler(WriteReadyHandler delegateHandler) { this.delegateHandler = delegateHandler; } @Override public void forceTermination() { try { if (delegateHandler != null) { delegateHandler.forceTermination(); } } finally { IoUtils.safeClose(delegate); } } @Override public void terminated() { ChannelListeners.invokeChannelListener(connection.getSinkChannel(), connection.getSinkChannel().getCloseListener()); } @Override public void writeReady() { if(anyAreSet(state, FLAG_READ_REQUIRES_WRITE)) { if(anyAreSet(state, FLAG_READS_RESUMED)) { readReadyHandler.readReady(); } else { try { doHandshake(); } catch (IOException e) { UndertowLogger.REQUEST_LOGGER.ioException(e); IoUtils.safeClose(delegate); } catch (Throwable t) { UndertowLogger.REQUEST_LOGGER.handleUnexpectedFailure(t); IoUtils.safeClose(delegate); } } } if (anyAreSet(state, FLAG_WRITES_RESUMED)) { if(delegateHandler == null) { final ChannelListener writeListener = connection.getSinkChannel().getWriteListener(); if (writeListener == null) { suspendWrites(); } else { ChannelListeners.invokeChannelListener(connection.getSinkChannel(), writeListener); } } else { delegateHandler.writeReady(); } } if(!anyAreSet(state, FLAG_WRITES_RESUMED | FLAG_READ_REQUIRES_WRITE)) { delegate.getSinkChannel().suspendWrites(); } } } public void setSslEngine(SSLEngine engine) { this.engine = engine; } @Override public String toString() { return "SslConduit{" + "state=" + state + ", outstandingTasks=" + outstandingTasks + ", wrappedData=" + wrappedData + ", dataToUnwrap=" + dataToUnwrap + ", unwrappedData=" + unwrappedData + '}'; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ssl/UndertowAcceptingSslChannel.java000066400000000000000000000333541420065311100334340ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.ssl; import io.undertow.UndertowLogger; import io.undertow.UndertowOptions; import io.undertow.connector.ByteBufferPool; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.Option; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.Sequence; import org.xnio.SslClientAuthMode; import org.xnio.StreamConnection; import org.xnio.Xnio; import org.xnio.XnioExecutor; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.AcceptingChannel; import org.xnio.ssl.SslConnection; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLParameters; /** * @author Stuart Douglas */ class UndertowAcceptingSslChannel implements AcceptingChannel { private final UndertowXnioSsl ssl; private final AcceptingChannel tcpServer; private volatile SslClientAuthMode clientAuthMode; private volatile int useClientMode; private volatile int enableSessionCreation; private volatile String[] cipherSuites; private volatile String[] protocols; @SuppressWarnings("rawtypes") private static final AtomicReferenceFieldUpdater clientAuthModeUpdater = AtomicReferenceFieldUpdater.newUpdater(UndertowAcceptingSslChannel.class, SslClientAuthMode.class, "clientAuthMode"); @SuppressWarnings("rawtypes") private static final AtomicIntegerFieldUpdater useClientModeUpdater = AtomicIntegerFieldUpdater.newUpdater(UndertowAcceptingSslChannel.class, "useClientMode"); @SuppressWarnings("rawtypes") private static final AtomicIntegerFieldUpdater enableSessionCreationUpdater = AtomicIntegerFieldUpdater.newUpdater(UndertowAcceptingSslChannel.class, "enableSessionCreation"); private final ChannelListener.Setter> closeSetter; private final ChannelListener.Setter> acceptSetter; protected final boolean startTls; protected final ByteBufferPool applicationBufferPool; private final boolean useCipherSuitesOrder; UndertowAcceptingSslChannel(final UndertowXnioSsl ssl, final AcceptingChannel tcpServer, final OptionMap optionMap, final ByteBufferPool applicationBufferPool, final boolean startTls) { this.tcpServer = tcpServer; this.ssl = ssl; this.applicationBufferPool = applicationBufferPool; this.startTls = startTls; clientAuthMode = optionMap.get(Options.SSL_CLIENT_AUTH_MODE); useClientMode = optionMap.get(Options.SSL_USE_CLIENT_MODE, false) ? 1 : 0; enableSessionCreation = optionMap.get(Options.SSL_ENABLE_SESSION_CREATION, true) ? 1 : 0; final Sequence enabledCipherSuites = optionMap.get(Options.SSL_ENABLED_CIPHER_SUITES); cipherSuites = enabledCipherSuites != null ? enabledCipherSuites.toArray(new String[enabledCipherSuites.size()]) : null; final Sequence enabledProtocols = optionMap.get(Options.SSL_ENABLED_PROTOCOLS); protocols = enabledProtocols != null ? enabledProtocols.toArray(new String[enabledProtocols.size()]) : null; //noinspection ThisEscapedInObjectConstruction closeSetter = ChannelListeners.>getDelegatingSetter(tcpServer.getCloseSetter(), this); //noinspection ThisEscapedInObjectConstruction acceptSetter = ChannelListeners.>getDelegatingSetter(tcpServer.getAcceptSetter(), this); useCipherSuitesOrder = optionMap.get(UndertowOptions.SSL_USER_CIPHER_SUITES_ORDER, false); } private static final Set> SUPPORTED_OPTIONS = Option.setBuilder() .add(Options.SSL_CLIENT_AUTH_MODE) .add(Options.SSL_USE_CLIENT_MODE) .add(Options.SSL_ENABLE_SESSION_CREATION) .add(Options.SSL_ENABLED_CIPHER_SUITES) .add(Options.SSL_ENABLED_PROTOCOLS) .create(); public T setOption(final Option option, final T value) throws IllegalArgumentException, IOException { if (option == Options.SSL_CLIENT_AUTH_MODE) { return option.cast(clientAuthModeUpdater.getAndSet(this, Options.SSL_CLIENT_AUTH_MODE.cast(value))); } else if (option == Options.SSL_USE_CLIENT_MODE) { final Boolean valueObject = Options.SSL_USE_CLIENT_MODE.cast(value); if (valueObject != null) return option.cast(Boolean.valueOf(useClientModeUpdater.getAndSet(this, valueObject.booleanValue() ? 1 : 0) != 0)); } else if (option == Options.SSL_ENABLE_SESSION_CREATION) { final Boolean valueObject = Options.SSL_ENABLE_SESSION_CREATION.cast(value); if (valueObject != null) return option.cast(Boolean.valueOf(enableSessionCreationUpdater.getAndSet(this, valueObject.booleanValue() ? 1 : 0) != 0)); } else if (option == Options.SSL_ENABLED_CIPHER_SUITES) { final Sequence seq = Options.SSL_ENABLED_CIPHER_SUITES.cast(value); String[] old = this.cipherSuites; this.cipherSuites = seq == null ? null : seq.toArray(new String[seq.size()]); return option.cast(old); } else if (option == Options.SSL_ENABLED_PROTOCOLS) { final Sequence seq = Options.SSL_ENABLED_PROTOCOLS.cast(value); String[] old = this.protocols; this.protocols = seq == null ? null : seq.toArray(new String[seq.size()]); return option.cast(old); } else { return tcpServer.setOption(option, value); } throw UndertowLogger.ROOT_LOGGER.nullParameter("value"); } public XnioWorker getWorker() { return tcpServer.getWorker(); } public UndertowSslConnection accept() throws IOException { final StreamConnection tcpConnection = tcpServer.accept(); if (tcpConnection == null) { return null; } try { final InetSocketAddress peerAddress = tcpConnection.getPeerAddress(InetSocketAddress.class); final SSLEngine engine = ssl.getSslContext().createSSLEngine(getHostNameNoResolve(peerAddress), peerAddress.getPort()); if(useCipherSuitesOrder) { SSLParameters sslParameters = engine.getSSLParameters(); sslParameters.setUseCipherSuitesOrder(true); engine.setSSLParameters(sslParameters); } final boolean clientMode = useClientMode != 0; engine.setUseClientMode(clientMode); if (!clientMode) { final SslClientAuthMode clientAuthMode = UndertowAcceptingSslChannel.this.clientAuthMode; if (clientAuthMode != null) switch (clientAuthMode) { case NOT_REQUESTED: engine.setNeedClientAuth(false); engine.setWantClientAuth(false); break; case REQUESTED: engine.setWantClientAuth(true); break; case REQUIRED: engine.setNeedClientAuth(true); break; default: throw new IllegalStateException(); } } engine.setEnableSessionCreation(enableSessionCreation != 0); final String[] cipherSuites = UndertowAcceptingSslChannel.this.cipherSuites; if (cipherSuites != null) { final Set supported = new HashSet<>(Arrays.asList(engine.getSupportedCipherSuites())); if(supported.isEmpty()) { engine.setEnabledCipherSuites(cipherSuites); } else { final List finalList = new ArrayList<>(); for (String name : cipherSuites) { if (supported.contains(name)) { finalList.add(name); } } engine.setEnabledCipherSuites(finalList.toArray(new String[finalList.size()])); } } final String[] protocols = UndertowAcceptingSslChannel.this.protocols; if (protocols != null) { final Set supported = new HashSet<>(Arrays.asList(engine.getSupportedProtocols())); if(supported.isEmpty()) { engine.setEnabledProtocols(protocols); } else { final List finalList = new ArrayList<>(); for (String name : protocols) { if (supported.contains(name)) { finalList.add(name); } } engine.setEnabledProtocols(finalList.toArray(new String[finalList.size()])); } } return accept(tcpConnection, engine); } catch (IOException | RuntimeException e) { IoUtils.safeClose(tcpConnection); UndertowLogger.REQUEST_LOGGER.failedToAcceptSSLRequest(e); return null; } } protected UndertowSslConnection accept(StreamConnection tcpServer, SSLEngine sslEngine) throws IOException { return new UndertowSslConnection(tcpServer, sslEngine, applicationBufferPool, ssl.getDelegatedTaskExecutor()); } public ChannelListener.Setter> getCloseSetter() { return closeSetter; } public boolean isOpen() { return tcpServer.isOpen(); } public void close() throws IOException { tcpServer.close(); } public boolean supportsOption(final Option option) { return SUPPORTED_OPTIONS.contains(option) || tcpServer.supportsOption(option); } public T getOption(final Option option) throws IOException { if (option == Options.SSL_CLIENT_AUTH_MODE) { return option.cast(clientAuthMode); } else if (option == Options.SSL_USE_CLIENT_MODE) { return option.cast(Boolean.valueOf(useClientMode != 0)); } else if (option == Options.SSL_ENABLE_SESSION_CREATION) { return option.cast(Boolean.valueOf(enableSessionCreation != 0)); } else if (option == Options.SSL_ENABLED_CIPHER_SUITES) { final String[] cipherSuites = this.cipherSuites; return cipherSuites == null ? null : option.cast(Sequence.of(cipherSuites)); } else if (option == Options.SSL_ENABLED_PROTOCOLS) { final String[] protocols = this.protocols; return protocols == null ? null : option.cast(Sequence.of(protocols)); } else { return tcpServer.getOption(option); } } public ChannelListener.Setter> getAcceptSetter() { return acceptSetter; } public SocketAddress getLocalAddress() { return tcpServer.getLocalAddress(); } public A getLocalAddress(final Class type) { return tcpServer.getLocalAddress(type); } public void suspendAccepts() { tcpServer.suspendAccepts(); } public void resumeAccepts() { tcpServer.resumeAccepts(); } @Override public boolean isAcceptResumed() { return tcpServer.isAcceptResumed(); } public void wakeupAccepts() { tcpServer.wakeupAccepts(); } public void awaitAcceptable() throws IOException { tcpServer.awaitAcceptable(); } public void awaitAcceptable(final long time, final TimeUnit timeUnit) throws IOException { tcpServer.awaitAcceptable(time, timeUnit); } @Deprecated public XnioExecutor getAcceptThread() { return tcpServer.getAcceptThread(); } public XnioIoThread getIoThread() { return tcpServer.getIoThread(); } private static String getHostNameNoResolve(InetSocketAddress socketAddress) { if (Xnio.NIO2) { return socketAddress.getHostString(); } else { String hostName; if (socketAddress.isUnresolved()) { hostName = socketAddress.getHostName(); } else { final InetAddress address = socketAddress.getAddress(); final String string = address.toString(); final int slash = string.indexOf('/'); if (slash == -1 || slash == 0) { // unresolved both ways hostName = string.substring(slash + 1); } else { // has a cached host name hostName = string.substring(0, slash); } } return hostName; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ssl/UndertowSslConnection.java000066400000000000000000000127501420065311100323420ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.ssl; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.Option; import org.xnio.Options; import io.undertow.connector.ByteBufferPool; import org.xnio.SslClientAuthMode; import org.xnio.StreamConnection; import org.xnio.ssl.SslConnection; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLSession; import java.io.IOException; import java.net.SocketAddress; import java.util.Set; import java.util.concurrent.Executor; /** * @author Stuart Douglas */ class UndertowSslConnection extends SslConnection { private static final Set> SUPPORTED_OPTIONS = Option.setBuilder().add(Options.SECURE, Options.SSL_CLIENT_AUTH_MODE).create(); private final StreamConnection delegate; private final SslConduit sslConduit; private final ChannelListener.SimpleSetter handshakeSetter = new ChannelListener.SimpleSetter<>(); private final SSLEngine engine; /** * Construct a new instance. * * @param delegate the underlying connection */ UndertowSslConnection(StreamConnection delegate, SSLEngine engine, ByteBufferPool bufferPool, Executor delegatedTaskExecutor) { super(delegate.getIoThread()); this.delegate = delegate; this.engine = engine; sslConduit = new SslConduit(this, delegate, engine, delegatedTaskExecutor, bufferPool, new HandshakeCallback()); setSourceConduit(sslConduit); setSinkConduit(sslConduit); } @Override public void startHandshake() throws IOException { sslConduit.startHandshake(); } @Override public SSLSession getSslSession() { return sslConduit.getSslSession(); } @Override public ChannelListener.Setter getHandshakeSetter() { return handshakeSetter; } @Override protected void notifyWriteClosed() { try { sslConduit.notifyWriteClosed(); } finally { super.notifyWriteClosed(); } } @Override protected void notifyReadClosed() { try { sslConduit.notifyReadClosed(); } finally { super.notifyReadClosed(); } } @Override public SocketAddress getPeerAddress() { return delegate.getPeerAddress(); } @Override public SocketAddress getLocalAddress() { return delegate.getLocalAddress(); } public SSLEngine getSSLEngine() { return sslConduit.getSSLEngine(); } SslConduit getSslConduit() { return sslConduit; } /** {@inheritDoc} */ @Override public T setOption(final Option option, final T value) throws IllegalArgumentException, IOException { if (option == Options.SSL_CLIENT_AUTH_MODE) { try { return option.cast(engine.getNeedClientAuth() ? SslClientAuthMode.REQUIRED : engine.getWantClientAuth() ? SslClientAuthMode.REQUESTED : SslClientAuthMode.NOT_REQUESTED); } finally { engine.setWantClientAuth(false); engine.setNeedClientAuth(false); if (value == SslClientAuthMode.REQUESTED) { engine.setWantClientAuth(true); } else if (value == SslClientAuthMode.REQUIRED) { engine.setNeedClientAuth(true); } } } else if (option == Options.SECURE) { throw new IllegalArgumentException(); } else { return delegate.setOption(option, value); } } /** {@inheritDoc} */ @Override public T getOption(final Option option) throws IOException { if (option == Options.SSL_CLIENT_AUTH_MODE) { return option.cast(engine.getNeedClientAuth() ? SslClientAuthMode.REQUIRED : engine.getWantClientAuth() ? SslClientAuthMode.REQUESTED : SslClientAuthMode.NOT_REQUESTED); } else { return option == Options.SECURE ? (T)Boolean.TRUE : delegate.getOption(option); } } /** {@inheritDoc} */ @Override public boolean supportsOption(final Option option) { return SUPPORTED_OPTIONS.contains(option) || delegate.supportsOption(option); } @Override protected boolean readClosed() { return super.readClosed(); } @Override protected boolean writeClosed() { return super.writeClosed(); } protected void closeAction() { sslConduit.close(); } private final class HandshakeCallback implements Runnable { @Override public void run() { final ChannelListener listener = handshakeSetter.get(); if (listener == null) { return; } ChannelListeners.invokeChannelListener(UndertowSslConnection.this, listener); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/protocols/ssl/UndertowXnioSsl.java000066400000000000000000000554671420065311100311740ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.ssl; import java.io.IOException; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.URI; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import javax.net.ssl.SNIHostName; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLParameters; import io.undertow.UndertowOptions; import io.undertow.connector.ByteBufferPool; import io.undertow.server.DefaultByteBufferPool; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.FutureResult; import org.xnio.IoFuture; import org.xnio.IoUtils; import org.xnio.Option; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.Sequence; import org.xnio.SslClientAuthMode; import org.xnio.StreamConnection; import org.xnio.Xnio; import org.xnio.XnioExecutor; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.AcceptingChannel; import org.xnio.channels.AssembledConnectedSslStreamChannel; import org.xnio.channels.BoundChannel; import org.xnio.channels.ConnectedSslStreamChannel; import org.xnio.channels.ConnectedStreamChannel; import org.xnio.ssl.JsseSslUtils; import org.xnio.ssl.JsseXnioSsl; import org.xnio.ssl.SslConnection; import org.xnio.ssl.XnioSsl; import static org.xnio.IoUtils.safeClose; /** * @author Stuart Douglas */ public class UndertowXnioSsl extends XnioSsl { private static final ByteBufferPool DEFAULT_BUFFER_POOL = new DefaultByteBufferPool(true, 17 * 1024, -1, 12); private final ByteBufferPool bufferPool; private final Executor delegatedTaskExecutor; private volatile SSLContext sslContext; /** * Construct a new instance. * * @param xnio the XNIO instance to associate with * @param optionMap the options for this provider * @throws java.security.NoSuchProviderException if the given SSL provider is not found * @throws java.security.NoSuchAlgorithmException if the given SSL algorithm is not supported * @throws java.security.KeyManagementException if the SSL context could not be initialized */ public UndertowXnioSsl(final Xnio xnio, final OptionMap optionMap) throws NoSuchProviderException, NoSuchAlgorithmException, KeyManagementException { this(xnio, optionMap, DEFAULT_BUFFER_POOL, JsseSslUtils.createSSLContext(optionMap)); } /** * Construct a new instance. * @param xnio the XNIO instance to associate with * @param optionMap the options for this provider * @param sslContext the SSL context to use for this instance */ public UndertowXnioSsl(final Xnio xnio, final OptionMap optionMap, final SSLContext sslContext) { this(xnio, optionMap, DEFAULT_BUFFER_POOL, sslContext); } /** * Construct a new instance. * @param xnio the XNIO instance to associate with * @param optionMap the options for this provider * @param sslContext the SSL context to use for this instance * @param delegatedTaskExecutor Executor instance used to run {@link SSLEngine#getDelegatedTask() delegated tasks}. */ public UndertowXnioSsl(final Xnio xnio, final OptionMap optionMap, final SSLContext sslContext, final Executor delegatedTaskExecutor) { this(xnio, optionMap, DEFAULT_BUFFER_POOL, sslContext, delegatedTaskExecutor); } /** * Construct a new instance. * * @param xnio the XNIO instance to associate with * @param optionMap the options for this provider * @param bufferPool * @throws java.security.NoSuchProviderException if the given SSL provider is not found * @throws java.security.NoSuchAlgorithmException if the given SSL algorithm is not supported * @throws java.security.KeyManagementException if the SSL context could not be initialized */ public UndertowXnioSsl(final Xnio xnio, final OptionMap optionMap, ByteBufferPool bufferPool) throws NoSuchProviderException, NoSuchAlgorithmException, KeyManagementException { this(xnio, optionMap, bufferPool, JsseSslUtils.createSSLContext(optionMap)); } /** * Construct a new instance. * @param xnio the XNIO instance to associate with * @param optionMap the options for this provider * @param bufferPool * @param sslContext the SSL context to use for this instance */ public UndertowXnioSsl(final Xnio xnio, final OptionMap optionMap, ByteBufferPool bufferPool, final SSLContext sslContext) { this(xnio, optionMap, bufferPool, sslContext, null); } /** * Construct a new instance. * @param xnio the XNIO instance to associate with * @param optionMap the options for this provider * @param bufferPool * @param sslContext the SSL context to use for this instance * @param delegatedTaskExecutor Executor instance used to run {@link SSLEngine#getDelegatedTask() delegated tasks}. */ public UndertowXnioSsl(final Xnio xnio, final OptionMap optionMap, ByteBufferPool bufferPool, final SSLContext sslContext, final Executor delegatedTaskExecutor) { super(xnio, sslContext, optionMap); this.bufferPool = bufferPool; this.sslContext = sslContext; this.delegatedTaskExecutor = delegatedTaskExecutor; } /** * Get the JSSE SSL context for this provider instance. * * @return the SSL context */ @SuppressWarnings("unused") public SSLContext getSslContext() { return sslContext; } /** * Get the {@link Executor} used to run delegated tasks or {@code null} if no executor is configured. * * @return the delegated task executor or null */ Executor getDelegatedTaskExecutor() { return delegatedTaskExecutor; } /** * Get the SSL engine for a given connection. * * @return the SSL engine */ public static SSLEngine getSslEngine(SslConnection connection) { if (connection instanceof UndertowSslConnection) { return ((UndertowSslConnection) connection).getSSLEngine(); } else { return JsseXnioSsl.getSslEngine(connection); } } public static SslConduit getSslConduit(SslConnection connection) { return ((UndertowSslConnection) connection).getSslConduit(); } @SuppressWarnings("deprecation") public IoFuture connectSsl(final XnioWorker worker, final InetSocketAddress bindAddress, final InetSocketAddress destination, final ChannelListener openListener, final ChannelListener bindListener, final OptionMap optionMap) { final FutureResult futureResult = new FutureResult<>(IoUtils.directExecutor()); final IoFuture futureSslConnection = openSslConnection(worker, bindAddress, destination, new ChannelListener() { public void handleEvent(final SslConnection sslConnection) { final ConnectedSslStreamChannel assembledChannel = new AssembledConnectedSslStreamChannel(sslConnection, sslConnection.getSourceChannel(), sslConnection.getSinkChannel()); if (!futureResult.setResult(assembledChannel)) { safeClose(assembledChannel); } else { ChannelListeners.invokeChannelListener(assembledChannel, openListener); } } }, bindListener, optionMap).addNotifier(new IoFuture.HandlingNotifier>() { public void handleCancelled(final FutureResult result) { result.setCancelled(); } public void handleFailed(final IOException exception, final FutureResult result) { result.setException(exception); } }, futureResult); futureResult.getIoFuture().addNotifier(new IoFuture.HandlingNotifier>() { public void handleCancelled(final IoFuture result) { result.cancel(); } }, futureSslConnection); futureResult.addCancelHandler(futureSslConnection); return futureResult.getIoFuture(); } public IoFuture openSslConnection(final XnioWorker worker, final InetSocketAddress bindAddress, final InetSocketAddress destination, final ChannelListener openListener, final ChannelListener bindListener, final OptionMap optionMap) { final FutureResult futureResult = new FutureResult<>(worker); final IoFuture connection = worker.openStreamConnection(bindAddress, destination, new StreamConnectionChannelListener(optionMap, destination, futureResult, openListener), bindListener, optionMap); return setupSslConnection(futureResult, connection); } @Override public IoFuture openSslConnection(final XnioIoThread ioThread, final InetSocketAddress bindAddress, final InetSocketAddress destination, final ChannelListener openListener, final ChannelListener bindListener, final OptionMap optionMap) { final FutureResult futureResult = new FutureResult<>(ioThread); final IoFuture connection = ioThread.openStreamConnection(bindAddress, destination, new StreamConnectionChannelListener(optionMap, destination, futureResult, openListener), bindListener, optionMap); return setupSslConnection(futureResult, connection); } public SslConnection wrapExistingConnection(StreamConnection connection, OptionMap optionMap) { return new UndertowSslConnection(connection, createSSLEngine(sslContext, optionMap, (InetSocketAddress) connection.getPeerAddress(), true), bufferPool, delegatedTaskExecutor); } public SslConnection wrapExistingConnection(StreamConnection connection, OptionMap optionMap, boolean clientMode) { return new UndertowSslConnection(connection, createSSLEngine(sslContext, optionMap, (InetSocketAddress) connection.getPeerAddress(), clientMode), bufferPool, delegatedTaskExecutor); } public SslConnection wrapExistingConnection(StreamConnection connection, OptionMap optionMap, URI destinationURI) { SSLEngine sslEngine = createSSLEngine(sslContext, optionMap, getPeerAddress(destinationURI), true); SSLParameters sslParameters = sslEngine.getSSLParameters(); if (sslParameters.getServerNames() == null || sslParameters.getServerNames().isEmpty()) { sslParameters.setServerNames(Collections.singletonList(new SNIHostName(destinationURI.getHost()))); sslEngine.setSSLParameters(sslParameters); } return new UndertowSslConnection(connection, sslEngine, bufferPool, delegatedTaskExecutor); } private InetSocketAddress getPeerAddress(URI destinationURI) { String hostname = destinationURI.getHost(); int port = destinationURI.getPort(); if (port == -1) { port = destinationURI.getScheme().equals("wss") ? 443 : 80; } return new InetSocketAddress(hostname, port); } /** * Create a new SSL engine, configured from an option map. * * @param sslContext the SSL context * @param optionMap the SSL options * @param peerAddress the peer address of the connection * @param client whether this SSL connection is run in client mode * @return the configured SSL engine */ private static SSLEngine createSSLEngine(SSLContext sslContext, OptionMap optionMap, InetSocketAddress peerAddress, boolean client) { final SSLEngine engine = sslContext.createSSLEngine( optionMap.get(Options.SSL_PEER_HOST_NAME, peerAddress.getHostString()), optionMap.get(Options.SSL_PEER_PORT, peerAddress.getPort()) ); engine.setUseClientMode(client); engine.setEnableSessionCreation(optionMap.get(Options.SSL_ENABLE_SESSION_CREATION, true)); final Sequence cipherSuites = optionMap.get(Options.SSL_ENABLED_CIPHER_SUITES); if (cipherSuites != null) { final Set supported = new HashSet(Arrays.asList(engine.getSupportedCipherSuites())); final List finalList = new ArrayList(); for (String name : cipherSuites) { if (supported.contains(name)) { finalList.add(name); } } engine.setEnabledCipherSuites(finalList.toArray(new String[finalList.size()])); } final Sequence protocols = optionMap.get(Options.SSL_ENABLED_PROTOCOLS); if (protocols != null) { final Set supported = new HashSet(Arrays.asList(engine.getSupportedProtocols())); final List finalList = new ArrayList(); for (String name : protocols) { if (supported.contains(name)) { finalList.add(name); } } engine.setEnabledProtocols(finalList.toArray(new String[finalList.size()])); } if (!client) { final SslClientAuthMode clientAuthMode = optionMap.get(Options.SSL_CLIENT_AUTH_MODE); if (clientAuthMode != null) { switch (clientAuthMode) { case NOT_REQUESTED: engine.setNeedClientAuth(false); engine.setWantClientAuth(false); break; case REQUESTED: engine.setWantClientAuth(true); break; case REQUIRED: engine.setNeedClientAuth(true); break; default: throw new IllegalStateException(); } } } boolean useCipherSuitesOrder = optionMap.get(UndertowOptions.SSL_USER_CIPHER_SUITES_ORDER, false); if (useCipherSuitesOrder) { SSLParameters sslParameters = engine.getSSLParameters(); sslParameters.setUseCipherSuitesOrder(true); engine.setSSLParameters(sslParameters); } final String endpointIdentificationAlgorithm = optionMap.get(UndertowOptions.ENDPOINT_IDENTIFICATION_ALGORITHM, null); if (endpointIdentificationAlgorithm != null) { SSLParameters sslParameters = engine.getSSLParameters(); sslParameters.setEndpointIdentificationAlgorithm(endpointIdentificationAlgorithm); engine.setSSLParameters(sslParameters); } return engine; } private IoFuture setupSslConnection(FutureResult futureResult, IoFuture connection) { connection.addNotifier(new IoFuture.HandlingNotifier>() { public void handleCancelled(final FutureResult attachment) { attachment.setCancelled(); } public void handleFailed(final IOException exception, final FutureResult attachment) { attachment.setException(exception); } }, futureResult); futureResult.addCancelHandler(connection); return futureResult.getIoFuture(); } @SuppressWarnings("deprecation") public AcceptingChannel createSslTcpServer(final XnioWorker worker, final InetSocketAddress bindAddress, final ChannelListener> acceptListener, final OptionMap optionMap) throws IOException { final AcceptingChannel server = createSslConnectionServer(worker, bindAddress, null, optionMap); final AcceptingChannel acceptingChannel = new AcceptingChannel() { public ConnectedSslStreamChannel accept() throws IOException { final SslConnection connection = server.accept(); return connection == null ? null : new AssembledConnectedSslStreamChannel(connection, connection.getSourceChannel(), connection.getSinkChannel()); } public ChannelListener.Setter> getAcceptSetter() { return ChannelListeners.getDelegatingSetter(server.getAcceptSetter(), this); } public ChannelListener.Setter> getCloseSetter() { return ChannelListeners.getDelegatingSetter(server.getCloseSetter(), this); } public SocketAddress getLocalAddress() { return server.getLocalAddress(); } public A getLocalAddress(final Class type) { return server.getLocalAddress(type); } public void suspendAccepts() { server.suspendAccepts(); } public void resumeAccepts() { server.resumeAccepts(); } public boolean isAcceptResumed() { return server.isAcceptResumed(); } public void wakeupAccepts() { server.wakeupAccepts(); } public void awaitAcceptable() throws IOException { server.awaitAcceptable(); } public void awaitAcceptable(final long time, final TimeUnit timeUnit) throws IOException { server.awaitAcceptable(time, timeUnit); } public XnioWorker getWorker() { return server.getWorker(); } @Deprecated public XnioExecutor getAcceptThread() { return server.getAcceptThread(); } public XnioIoThread getIoThread() { return server.getIoThread(); } public void close() throws IOException { server.close(); } public boolean isOpen() { return server.isOpen(); } public boolean supportsOption(final Option option) { return server.supportsOption(option); } public T getOption(final Option option) throws IOException { return server.getOption(option); } public T setOption(final Option option, final T value) throws IllegalArgumentException, IOException { return server.setOption(option, value); } }; acceptingChannel.getAcceptSetter().set(acceptListener); return acceptingChannel; } /** * Updates the SSLContext that is in use. All new connections will use this new context, however established connections * will not be affected. * * @param context The new context */ public void updateSSLContext(SSLContext context) { this.sslContext = context; } public AcceptingChannel createSslConnectionServer(final XnioWorker worker, final InetSocketAddress bindAddress, final ChannelListener> acceptListener, final OptionMap optionMap) throws IOException { final UndertowAcceptingSslChannel server = new UndertowAcceptingSslChannel(this, worker.createStreamConnectionServer(bindAddress, null, optionMap), optionMap, bufferPool, false); if (acceptListener != null) server.getAcceptSetter().set(acceptListener); return server; } private class StreamConnectionChannelListener implements ChannelListener { private final OptionMap optionMap; private final InetSocketAddress destination; private final FutureResult futureResult; private final ChannelListener openListener; StreamConnectionChannelListener(OptionMap optionMap, InetSocketAddress destination, FutureResult futureResult, ChannelListener openListener) { this.optionMap = optionMap; this.destination = destination; this.futureResult = futureResult; this.openListener = openListener; } public void handleEvent(final StreamConnection connection) { try { SSLEngine sslEngine = JsseSslUtils.createSSLEngine(sslContext, optionMap, destination); SSLParameters params = sslEngine.getSSLParameters(); InetAddress address = destination.getAddress(); String hostnameValue = destination.getHostString(); if (address instanceof Inet6Address && hostnameValue.contains(":")) { // WFLY-13748 get hostname value instead of IPV6adress if it's ipv6 // SNIHostname throw exception if adress contains : hostnameValue = address.getHostName(); } params.setServerNames(Collections.singletonList(new SNIHostName(hostnameValue))); final String endpointIdentificationAlgorithm = optionMap.get(UndertowOptions.ENDPOINT_IDENTIFICATION_ALGORITHM, null); if (endpointIdentificationAlgorithm != null) { params.setEndpointIdentificationAlgorithm(endpointIdentificationAlgorithm); } sslEngine.setSSLParameters(params); final SslConnection wrappedConnection = new UndertowSslConnection(connection, sslEngine, bufferPool, delegatedTaskExecutor); if (!futureResult.setResult(wrappedConnection)) { IoUtils.safeClose(connection); } else { ChannelListeners.invokeChannelListener(wrappedConnection, openListener); } } catch (Throwable e) { futureResult.setException(new IOException(e)); } } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/000077500000000000000000000000001420065311100242035ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/security/api/000077500000000000000000000000001420065311100247545ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/security/api/AuthenticatedSessionManager.java000066400000000000000000000036361420065311100332500ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.api; import io.undertow.security.idm.Account; import io.undertow.server.HttpServerExchange; import io.undertow.util.AttachmentKey; import java.io.Serializable; /** * Interface that represents a persistent authenticated session. * * @author Stuart Douglas * @author Darran Lofthouse */ public interface AuthenticatedSessionManager { /** * The attachment key that is used to attach the manager to the exchange */ AttachmentKey ATTACHMENT_KEY = AttachmentKey.create(AuthenticatedSessionManager.class); AuthenticatedSession lookupSession(final HttpServerExchange exchange); void clearSession(HttpServerExchange exchange); class AuthenticatedSession implements Serializable { private final Account account; private final String mechanism; public AuthenticatedSession(final Account account, final String mechanism) { this.account = account; this.mechanism = mechanism; } public Account getAccount() { return account; } public String getMechanism() { return mechanism; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/api/AuthenticationMechanism.java000066400000000000000000000155141420065311100324310ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.api; import io.undertow.server.HttpServerExchange; /** * The interface to be implemented by a single authentication mechanism. *

* The implementation of this interface are assumed to be stateless, if there is a need to share state between the authenticate * and handleComplete calls then it should be held in the HttpServerExchange. *

* As an in-bound request is received the authenticate method is called on each mechanism in turn until one of the following * occurs: - - A mechanism successfully authenticates the incoming request. - A mechanism attempts but fails to authenticate the * request. - The list of mechanisms is exhausted. *

* This means that if the authenticate method is called on a mechanism it should assume it is required to check if it can * actually authenticate the incoming request, anything that would prevent it from performing the check would have already * stopped the authenticate method from being called. *

* Authentication is allowed to proceed if either authentication was required AND one handler authenticated the request or it is * allowed to proceed if it is not required AND no handler failed to authenticate the request. *

* The handleComplete methods are used as the request processing is returning up the chain, primarily these are used to * challenge the client to authenticate but where supported by the mechanism they could also be used to send mechanism specific * updates back with a request. *

* If a mechanism successfully authenticated the incoming request then only the handleComplete method on that mechanism is * called. *

* If any mechanism failed or if authentication was required and no mechanism succeeded in authenticating the request then * handleComplete will be called for all mechanisms. *

* Finally if authentication was not required handleComplete will not be called for any of the mechanisms. *

* The mechanisms will need to double check why handleComplete is being called, if the request was authenticated then they * should do nothing unless the mechanism has intermediate state to send back. If the request was not authenticated then a * challenge should be sent. * * @author Stuart Douglas * @author Darran Lofthouse */ public interface AuthenticationMechanism { /** * Perform authentication of the request. Any potentially blocking work should be performed in the handoff executor provided * * @param exchange The exchange * @return */ AuthenticationMechanismOutcome authenticate(final HttpServerExchange exchange, final SecurityContext securityContext); /** * Send an authentication challenge to the remote client. *

* The individual mechanisms should update the response headers and body of the message as appropriate however they should * not set the response code, instead that should be indicated in the {@link ChallengeResult} and the most appropriate * overall response code will be selected. * * This method should not return null. * * @param exchange The exchange * @param securityContext The security context * @return A {@link ChallengeResult} indicating if a challenge was sent and the desired response code. */ ChallengeResult sendChallenge(final HttpServerExchange exchange, final SecurityContext securityContext); /** * The AuthenticationOutcome is used by an AuthenticationMechanism to indicate the outcome of the call to authenticate, the * overall authentication process will then used this along with the current AuthenticationState to decide how to proceed * with the current request. */ enum AuthenticationMechanismOutcome { /** * Based on the current request the mechanism has successfully performed authentication. */ AUTHENTICATED, /** * The mechanism did not attempt authentication on this request, most likely due to not discovering any applicable * security tokens for this mechanisms in the request. */ NOT_ATTEMPTED, /** * The mechanism attempted authentication but it did not complete, this could either be due to a failure validating the * tokens from the client or it could be due to the mechanism requiring at least one additional round trip with the * client - either way the request will return challenges to the client. */ NOT_AUTHENTICATED; } /** * Simple class to wrap the result of requesting a mechanism sends it's challenge. */ class ChallengeResult { public static final ChallengeResult NOT_SENT = new ChallengeResult(false); private final boolean challengeSent; private final Integer statusCode; public ChallengeResult(final boolean challengeSent, final Integer statusCode) { this.statusCode = statusCode; this.challengeSent = challengeSent; } public ChallengeResult(final boolean challengeSent) { this(challengeSent, null); } /** * Obtain the response code desired by this mechanism for the challenge. *

* Where multiple mechanisms are in use concurrently all of the requested response codes will be checked and the most * suitable one selected. If no specific response code is required any value less than 0 can be set. * * @return The desired response code or null if no code specified. */ public Integer getDesiredResponseCode() { return statusCode; } /** * Check if the mechanism did send a challenge. *

* Some mechanisms do not send a challenge and just rely on the correct information to authenticate a user being * available in the request, in that case it would be normal for the mechanism to set this to false. * * @return true if a challenge was sent, false otherwise. */ public boolean isChallengeSent() { return challengeSent; } } } AuthenticationMechanismContext.java000066400000000000000000000025631420065311100337170ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/security/api/* * JBoss, Home of Professional Open Source. * Copyright 2015 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.api; /** * An Undertow {@link SecurityContext} that uses Undertow {@link AuthenticationMechanism} * instances for authentication. * * @author Darran Lofthouse */ public interface AuthenticationMechanismContext extends SecurityContext { /** * Adds an authentication mechanism to this context. When {@link #authenticate()} is * called mechanisms will be iterated over in the order they are added, and given a chance to authenticate the user. * * @param mechanism The mechanism to add */ @Override void addAuthenticationMechanism(AuthenticationMechanism mechanism); } AuthenticationMechanismFactory.java000066400000000000000000000047021420065311100336770ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/security/api/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.api; import io.undertow.security.idm.IdentityManager; import io.undertow.server.handlers.form.FormParserFactory; import java.util.Map; /** * * Factory for authentication mechanisms. * * * * @author Stuart Douglas */ public interface AuthenticationMechanismFactory { String REALM = "realm"; String LOGIN_PAGE = "login_page"; String ERROR_PAGE = "error_page"; String CONTEXT_PATH = "context_path"; String DEFAULT_PAGE = "default_page"; String OVERRIDE_INITIAL = "override_initial"; /** * Creates an authentication mechanism using the specified properties * * @param mechanismName The name under which this factory was registered * @param properties The properties * @param formParserFactory Parser to create a form data parser for a given request. * @return The mechanism */ @Deprecated default AuthenticationMechanism create(String mechanismName, FormParserFactory formParserFactory, final Map properties) { return null; } /** * Creates an authentication mechanism that needs access to the deployment IdentityManager and specified properties * * @param mechanismName The name under which this factory was registered * @param identityManager the IdentityManager instance asscociated with the deployment * @param formParserFactory Parser to create a form data parser for a given request. * @param properties The properties * @return The mechanism */ default AuthenticationMechanism create(String mechanismName, IdentityManager identityManager, FormParserFactory formParserFactory, final Map properties) { return create(mechanismName, formParserFactory, properties); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/api/AuthenticationMode.java000066400000000000000000000045331420065311100314100ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.api; /** * Enumeration to indicate the authentication mode in use. * * @author Darran Lofthouse */ public enum AuthenticationMode { /** * Where the authentication mode is set to pro-active each request on arrival will be passed to the defined authentication * mechanisms to eagerly perform authentication if there is sufficient information available in order to do so. * * A pro-active authentication could be possible for a number of reasons such as already having a SSL connection * established, an identity being cached against the current session or even a browser sending in authentication headers. * * Running in pro-active mode the sending of the challenge to the client is still driven by the constraints defined so this * is not the same as mandating security for all paths. For some mechanisms such as Digest this is a recommended mode as * without it there is a risk that clients are sending in headers with unique nonce counts that go unverified risking that a * malicious client could make use of them. This is also useful for applications that wish to make use of the current * authenticated user if one exists without mandating that authentication occurs. */ PRO_ACTIVE, /** * When running in constraint driven mode the authentication mechanisms are only executed where the constraint that mandates * authentication is triggered, for all other requests no authentication occurs unless requested by the internal APIs which * may be exposed using the Servlet APIs. */ CONSTRAINT_DRIVEN; } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/api/GSSAPIServerSubjectFactory.java000066400000000000000000000044251420065311100326510ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.api; import java.security.GeneralSecurityException; import javax.security.auth.Subject; /** * The GSSAPIServerSubjectFactory is a factory responsible for returning the {@link Subject} that should be used for handing the * GSSAPI based authentication for a specific request. * * The authentication handlers will not perform any caching of the returned Subject, the factory implementation can either * return a new Subject for each request or can cache them maybe based on the expiration time of tickets contained within the * Subject. * * @author Darran Lofthouse */ public interface GSSAPIServerSubjectFactory { // TODO - Does this need to be supplying some kind of wrapper that allows a try/finally approach to being and end using the Subject? /** * Obtain the Subject to use for the specified host. * * All virtual hosts on a server could use the same Subject or each virtual host could have a different Subject, the * implementation of the factory will make that decision. The factory implementation will also decide if there should be a * default fallback Subject or if a Subject should only be provided for recognised hosts. * * @param hostName - The host name used for this request. * @return The Subject to use for the specified host name or null if no match possible. * @throws GeneralSecurityException if there is a security failure obtaining the {@link Subject} */ Subject getSubjectForHost(final String hostName) throws GeneralSecurityException; } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/api/NonceManager.java000066400000000000000000000054421420065311100301610ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.api; import io.undertow.server.HttpServerExchange; /** * A NonceManager is used by the HTTP Digest authentication mechanism to request nonces and to validate the nonces sent from the * client. * * @author Darran Lofthouse */ public interface NonceManager { // TODO - Should a nonce manager be able to tie these to a connection or session, or any other piece of info we have about // the client? // Also different rules depending on HTTP method or the resource being accessed? /** * Select the next nonce to be sent from the server taking into account the last valid nonce. * * It is both possible and likely that the nonce last used by the client will still be valid, in that case the same nonce * will be returned. * * @param lastNonce - The last valid nonce received from the client or null if we don't already have a nonce. * @return The next nonce to be sent in a challenge to the client. */ String nextNonce(final String lastNonce, final HttpServerExchange exchange); /** * Validate that a nonce can be used. * * If the nonce can not be used but the related digest was correct then a new nonce should be returned to the client * indicating that the nonce was stale. * * For implementations of this interface this method is not expected by be idempotent, i.e. once a nonce is validated with a * specific nonceCount it is not expected that this method will return true again if the same combination is presented. * * This method is expected to ONLY be called if the users credentials are valid as a storage overhead could be incurred * this overhead must not be accessible to unauthenticated clients. * * @param nonce - The nonce received from the client. * @param nonceCount - The nonce count from the client or -1 of none specified. * @return true if the nonce can be used otherwise return false. */ boolean validateNonce(final String nonce, final int nonceCount, final HttpServerExchange exchange); } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/api/NotificationReceiver.java000066400000000000000000000034411420065311100317340ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.api; /** * The interface to be interested by classes interested in processing security related notifications. * * @author Darran Lofthouse */ public interface NotificationReceiver { /** * Handle a security related notification. * * The {@link SecurityNotification} that is sent to be handled is a security event that has occurred, this is not an * opportunity for that event to be validated - any Exception thrown by the handler will be logged but will not affect the * result of the security event. * * The notifications are sent on the same thread that is currently processing the request that triggered the notification, * if the handling of the notification is likely to be blocking then it should be dispatched to it's own worker thread. The * one exception to this may be where the notification must be sure to have been handled before the response continues. * * @param notification */ void handleNotification(final SecurityNotification notification); } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/api/SecurityContext.java000066400000000000000000000171201420065311100307740ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.api; import io.undertow.security.idm.Account; import io.undertow.security.idm.IdentityManager; import java.util.List; /** * The security context. * * This context is attached to the exchange and holds all security related information. * * @author Stuart Douglas * @author Darran Lofthouse * @see io.undertow.security.impl.SecurityContextImpl */ public interface SecurityContext { /* * Methods Used To Run Authentication Process */ /** * Performs authentication on the request. * * If authentication is REQUIRED then setAuthenticationRequired() should be called before calling this method. * * If the result indicates that a response has been sent to the client then no further attempts should be made to modify the * response. The caller of this method is responsible for ending the exchange. * * If this method returns true it can still have committed the response (e.g. form auth redirects back to the original * page). Callers should check that the exchange has not been ended before proceeding. * * @return true if either the request is successfully authenticated or if there is no failure validating the * current request so that the request should continue to be processed, false if authentication was not * completed and challenge has been prepared for the client. */ boolean authenticate(); /* * API for Direct Control of Authentication */ /** * Attempts to log the user in using the provided credentials. This result will be stored in the current * {@link AuthenticatedSessionManager} (if any), so subsequent requests will automatically be authenticated * as this user. *

* This operation may block * * @param username The username * @param password The password * @return true if the login succeeded, false otherwise */ boolean login(String username, String password); /** * de-authenticates the current exchange. * */ void logout(); /* * Methods Used To Control/Configure The Authentication Process. */ /** * Marks this request as requiring authentication. Authentication challenge headers will only be sent if this * method has been called. If {@link #authenticate()} * is called without first calling this method then the request will continue as normal even if the authentication * was not successful. */ void setAuthenticationRequired(); /** * Returns true if authentication is required * * @return true If authentication is required */ boolean isAuthenticationRequired(); /** * Adds an authentication mechanism to this context. When {@link #authenticate()} is * called mechanisms will be iterated over in the order they are added, and given a chance to authenticate the user. * * @param mechanism The mechanism to add * @deprecated This method is now only applicable to {@code SecurityContext} implementations that also implement the {@link AuthenticationMechanismContext} interface. */ @Deprecated void addAuthenticationMechanism(AuthenticationMechanism mechanism); /** * * @return A list of all authentication mechanisms in this context * @deprecated Obtaining lists of mechanisms is discouraged, however there should not be a need to call this anyway. */ @Deprecated List getAuthenticationMechanisms(); /* * Methods to access information about the current authentication status. */ /** * * @return true if a user has been authenticated for this request, false otherwise. */ boolean isAuthenticated(); /** * Obtain the {@link Account} for the currently authenticated identity. * * @return The {@link Account} for the currently authenticated identity or null if no account is currently authenticated. */ Account getAuthenticatedAccount(); /** * * @return The name of the mechanism that was used to authenticate */ String getMechanismName(); /* * Methods Used by AuthenticationMechanism implementations. */ /** * Obtain the associated {@link IdentityManager} to use to make account verification decisions. * * @return The associated {@link IdentityManager} * @deprecated Authentication mechanisms that rely on the {@link IdentityManager} should instead hold their own reference to it. */ @Deprecated IdentityManager getIdentityManager(); /** * Called by the {@link AuthenticationMechanism} to indicate that an account has been successfully authenticated. * * Note: A successful verification of an account using the {@link IdentityManager} is not the same as a successful * authentication decision, other factors could be taken into account to make the final decision. * * @param account - The authenticated {@link Account} * @param mechanismName - The name of the mechanism used to authenticate the account. * @param cachingRequired - If this mechanism requires caching */ void authenticationComplete(final Account account, final String mechanismName, final boolean cachingRequired); /** * Called by the {@link AuthenticationMechanism} to indicate that an authentication attempt has failed. * * This should only be called where an authentication attempt has truly failed, for authentication mechanisms where an * additional round trip with the client is expected this should not be called. * * Where possible the failure message should contain the name of the identity that authentication was being attempted for, * however as this is not always possible to identify in advance a generic message may be all that can be reported. * * @param message - The message describing the failure. * @param mechanismName - The name of the mechanism reporting the failure. */ void authenticationFailed(final String message, final String mechanismName); /* * Methods for the management of NotificationHandler registrations. */ /** * Register a {@link NotificationReceiver} interested in receiving notifications for security events that happen on this SecurityContext. * * @param receiver - The {@link NotificationReceiver} to register. */ void registerNotificationReceiver(final NotificationReceiver receiver); /** * Remove a previously registered {@link NotificationReceiver} from this SecurityContext. * * If the supplied receiver has not been previously registered this method will fail silently. * * @param receiver - The {@link NotificationReceiver} to remove. */ void removeNotificationReceiver(final NotificationReceiver receiver); } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/api/SecurityContextFactory.java000066400000000000000000000035541420065311100323320ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.api; import io.undertow.security.idm.IdentityManager; import io.undertow.server.HttpServerExchange; /** *

* Interface that must be implemented by factories of {@link io.undertow.security.api.SecurityContext} instances. *

* * @author Stefan Guilhen * @deprecated Instead extend AbstractSecurityContextAssociationHandler to provide alternative contexts. */ @Deprecated() public interface SecurityContextFactory { /** *

* Instantiates and returns a {@code SecurityContext} using the specified parameters. *

* * @param exchange the {@code HttpServerExchange} instance. * @param mode the {@code AuthenticationMode}. * @param identityManager the {@code IdentityManager} instance. * @param programmaticMechName a {@code String} representing the programmatic mechanism name. Can be null. * @return the constructed {@code SecurityContext} instance. */ SecurityContext createSecurityContext(final HttpServerExchange exchange, final AuthenticationMode mode, final IdentityManager identityManager, final String programmaticMechName); } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/api/SecurityNotification.java000066400000000000000000000045411420065311100320010ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.api; import io.undertow.security.idm.Account; import io.undertow.server.HttpServerExchange; /** * Notification representing a security event such as a successful or failed authentication. * * @author Darran Lofthouse */ public class SecurityNotification { private final HttpServerExchange exchange; private final EventType eventType; private final Account account; private final String mechanism; private final boolean programatic; private final String message; private final boolean cachingRequired; public SecurityNotification(final HttpServerExchange exchange, final EventType eventType, final Account account, final String mechanism, final boolean programatic, final String message, boolean cachingRequired) { this.exchange = exchange; this.eventType = eventType; this.account = account; this.mechanism = mechanism; this.programatic = programatic; this.message = message; this.cachingRequired = cachingRequired; } public HttpServerExchange getExchange() { return exchange; } public EventType getEventType() { return eventType; } public Account getAccount() { return account; } public String getMechanism() { return mechanism; } public boolean isProgramatic() { return programatic; } public String getMessage() { return message; } public boolean isCachingRequired() { return cachingRequired; } public enum EventType { AUTHENTICATED, FAILED_AUTHENTICATION, LOGGED_OUT; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/api/SessionNonceManager.java000066400000000000000000000034221420065311100315210ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.api; /** * An extension to the {@link NonceManager} interface for Nonce managers that also support the association of a pre-prepared * hash against a currently valid nonce. * * If the nonce manager replaces in-use nonces as old ones expire then the associated session hash should be migrated to the * replacement nonce. * * @author Darran Lofthouse */ public interface SessionNonceManager extends NonceManager { /** * Associate the supplied hash with the nonce specified. * * @param nonce - The nonce the hash is to be associated with. * @param hash - The hash to associate. */ void associateHash(final String nonce, final byte[] hash); /** * Retrieve the existing hash associated with the nonce specified. * * If there is no association then null should be returned. * * @param nonce - The nonce the hash is required for. * @return The associated hash or null if there is no association. */ byte[] lookupHash(final String nonce); } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/handlers/000077500000000000000000000000001420065311100260035ustar00rootroot00000000000000AbstractConfidentialityHandler.java000066400000000000000000000077201420065311100347040ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/security/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.handlers; import java.net.URI; import java.net.URISyntaxException; import io.undertow.UndertowLogger; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; /** * Handler responsible for checking of confidentiality is required for the requested resource and if so rejecting the request * and redirecting to a secure address. * * @author Darran Lofthouse */ public abstract class AbstractConfidentialityHandler implements HttpHandler { private final HttpHandler next; protected AbstractConfidentialityHandler(final HttpHandler next) { this.next = next; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if (isConfidential(exchange) || !confidentialityRequired(exchange)) { next.handleRequest(exchange); } else { try { URI redirectUri = getRedirectURI(exchange); UndertowLogger.SECURITY_LOGGER.debugf("Redirecting request %s to %s to meet confidentiality requirements", exchange, redirectUri); exchange.setStatusCode(StatusCodes.FOUND); exchange.getResponseHeaders().put(Headers.LOCATION, redirectUri.toString()); } catch (Exception e) { UndertowLogger.REQUEST_LOGGER.exceptionProcessingRequest(e); exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); } exchange.endExchange(); } } /** * Use the HttpServerExchange supplied to check if this request is already 'sufficiently' confidential. * * Here we say 'sufficiently' as sub-classes can override this and maybe even go so far as querying the actual SSLSession. * * @param exchange - The {@link HttpServerExchange} for the request being processed. * @return true if the request is 'sufficiently' confidential, false otherwise. */ protected boolean isConfidential(final HttpServerExchange exchange) { return exchange.getRequestScheme().equals("https"); } /** * Use the HttpServerExchange to identify if confidentiality is required. * * This method currently returns true for all requests, sub-classes can override this to provide a custom check. * * TODO: we should deprecate this and just use a predicate to decide to execute the handler instead * * @param exchange - The {@link HttpServerExchange} for the request being processed. * @return true if the request requires confidentiality, false otherwise. */ protected boolean confidentialityRequired(final HttpServerExchange exchange) { return true; } /** * All sub-classes are required to provide an implementation of this method, using the HttpServerExchange for the current * request return the address to use for a redirect should confidentiality be required and the request not be confidential. * * @param exchange - The {@link HttpServerExchange} for the request being processed. * @return The {@link URI} to redirect to. */ protected abstract URI getRedirectURI(final HttpServerExchange exchange) throws URISyntaxException; } AbstractSecurityContextAssociationHandler.java000066400000000000000000000033331420065311100371240ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/security/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2015 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.handlers; import io.undertow.security.api.SecurityContext; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; /** * Base class responsible for associating the {@link SecurityContext} instance with the current request. * * @author Darran Lofthouse */ public abstract class AbstractSecurityContextAssociationHandler implements HttpHandler { private final HttpHandler next; protected AbstractSecurityContextAssociationHandler(final HttpHandler next) { this.next = next; } /** * @see io.undertow.server.HttpHandler#handleRequest(io.undertow.server.HttpServerExchange) */ @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { SecurityActions.setSecurityContext(exchange, createSecurityContext(exchange)); next.handleRequest(exchange); } public abstract SecurityContext createSecurityContext(final HttpServerExchange exchange); } AuthenticationCallHandler.java000066400000000000000000000041121420065311100336360ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/security/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.handlers; import io.undertow.security.api.SecurityContext; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; /** * This is the final {@link HttpHandler} in the security chain, it's purpose is to act as a barrier at the end of the chain to * ensure authenticate is called after the mechanisms have been associated with the context and the constraint checked. * * @author Darran Lofthouse */ public class AuthenticationCallHandler implements HttpHandler { private final HttpHandler next; public AuthenticationCallHandler(final HttpHandler next) { this.next = next; } /** * Only allow the request through if successfully authenticated or if authentication is not required. * * @see io.undertow.server.HttpHandler#handleRequest(io.undertow.server.HttpServerExchange) */ @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { if(exchange.isInIoThread()) { exchange.dispatch(this); return; } SecurityContext context = exchange.getSecurityContext(); if (context.authenticate()) { if(!exchange.isComplete()) { next.handleRequest(exchange); } } else { exchange.endExchange(); } } } AuthenticationConstraintHandler.java000066400000000000000000000051531420065311100351150ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/security/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.handlers; import io.undertow.UndertowLogger; import io.undertow.security.api.SecurityContext; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; /** * Handler responsible for checking the constraints for the current request and marking authentication as required if * applicable. * * Sub classes can override isAuthenticationRequired to provide a constraint check, by default this handler will set * authentication as always required, authentication will be optional if this handler is omitted. * * @author Darran Lofthouse */ public class AuthenticationConstraintHandler implements HttpHandler { private final HttpHandler next; public AuthenticationConstraintHandler(final HttpHandler next) { this.next = next; } /** * @see io.undertow.server.HttpHandler#handleRequest(io.undertow.server.HttpServerExchange) */ @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if (isAuthenticationRequired(exchange)) { SecurityContext context = exchange.getSecurityContext(); UndertowLogger.SECURITY_LOGGER.debugf("Setting authentication required for exchange %s", exchange); context.setAuthenticationRequired(); } next.handleRequest(exchange); } /** * Evaluate the current request and indicate if authentication is required for the current request. * * By default this will always return true, sub-classes will override this method to provide a more specific check. * * @param exchange - the {@link HttpServerExchange} for the current request to decide if authentication is required. * @return true if authentication is required, false otherwise. */ protected boolean isAuthenticationRequired(final HttpServerExchange exchange) { return true; } } AuthenticationMechanismsHandler.java000066400000000000000000000053451420065311100350630ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/security/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.handlers; import io.undertow.Handlers; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.AuthenticationMechanismContext; import io.undertow.security.api.SecurityContext; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.ResponseCodeHandler; import java.util.List; /** * Authentication handler that adds one or more authentication * mechanisms to the security context * * @author Stuart Douglas */ public class AuthenticationMechanismsHandler implements HttpHandler { private volatile HttpHandler next = ResponseCodeHandler.HANDLE_404; private final AuthenticationMechanism[] authenticationMechanisms; public AuthenticationMechanismsHandler(final HttpHandler next, final List authenticationMechanisms) { this.next = next; this.authenticationMechanisms = authenticationMechanisms.toArray(new AuthenticationMechanism[authenticationMechanisms.size()]); } public AuthenticationMechanismsHandler(final List authenticationHandlers) { this.authenticationMechanisms = authenticationHandlers.toArray(new AuthenticationMechanism[authenticationHandlers.size()]); } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final SecurityContext sc = exchange.getSecurityContext(); if(sc != null && sc instanceof AuthenticationMechanismContext) { AuthenticationMechanismContext amc = (AuthenticationMechanismContext) sc; for(int i = 0; i < authenticationMechanisms.length; ++i) { amc.addAuthenticationMechanism(authenticationMechanisms[i]); } } next.handleRequest(exchange); } public HttpHandler getNext() { return next; } public AuthenticationMechanismsHandler setNext(final HttpHandler next) { Handlers.handlerNotNull(next); this.next = next; return this; } } CachedAuthenticatedSessionHandler.java000066400000000000000000000152001420065311100353010ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/security/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.handlers; import io.undertow.security.api.AuthenticatedSessionManager; import io.undertow.security.api.AuthenticatedSessionManager.AuthenticatedSession; import io.undertow.security.api.NotificationReceiver; import io.undertow.security.api.SecurityContext; import io.undertow.security.api.SecurityNotification; import io.undertow.security.api.SecurityNotification.EventType; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.session.Session; import io.undertow.server.session.SessionConfig; import io.undertow.server.session.SessionManager; /** * {@link HttpHandler} responsible for setting up the {@link AuthenticatedSessionManager} for cached authentications and * registering a {@link NotificationReceiver} to receive the security notifications. *

* This handler also forces the session to change its session ID on sucessful authentication. * * @author Darran Lofthouse */ public class CachedAuthenticatedSessionHandler implements HttpHandler { public static final String ATTRIBUTE_NAME = CachedAuthenticatedSessionHandler.class.getName() + ".AuthenticatedSession"; public static final String NO_ID_CHANGE_REQUIRED = CachedAuthenticatedSessionHandler.class.getName() + ".NoIdChangeRequired"; private final NotificationReceiver NOTIFICATION_RECEIVER = new SecurityNotificationReceiver(); private final AuthenticatedSessionManager SESSION_MANAGER = new ServletAuthenticatedSessionManager(); private final HttpHandler next; public CachedAuthenticatedSessionHandler(final HttpHandler next) { this.next = next; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { SecurityContext securityContext = exchange.getSecurityContext(); securityContext.registerNotificationReceiver(NOTIFICATION_RECEIVER); SessionManager sessionManager = exchange.getAttachment(SessionManager.ATTACHMENT_KEY); SessionConfig sessionConfig = exchange.getAttachment(SessionConfig.ATTACHMENT_KEY); if (sessionManager == null || sessionConfig == null) { next.handleRequest(exchange); return; } Session session = sessionManager.getSession(exchange, sessionConfig); // If there was no existing HttpSession then there could not be a cached AuthenticatedSession so don't bother setting // the AuthenticatedSessionManager. if (session != null) { exchange.putAttachment(AuthenticatedSessionManager.ATTACHMENT_KEY, SESSION_MANAGER); } next.handleRequest(exchange); } private class SecurityNotificationReceiver implements NotificationReceiver { @Override public void handleNotification(SecurityNotification notification) { EventType eventType = notification.getEventType(); HttpServerExchange exchange = notification.getExchange(); SessionManager sessionManager = exchange.getAttachment(SessionManager.ATTACHMENT_KEY); SessionConfig sessionConfig = exchange.getAttachment(SessionConfig.ATTACHMENT_KEY); if (sessionManager == null || sessionConfig == null) { return; } Session httpSession = sessionManager.getSession(exchange, sessionConfig); switch (eventType) { case AUTHENTICATED: if (isCacheable(notification)) { if (httpSession == null) { httpSession = sessionManager.createSession(exchange, sessionConfig); } // It is normal for this notification to be received when using a previously cached session - in that // case the IDM would have been given an opportunity to re-load the Account so updating here ready for // the next request is desired. httpSession.setAttribute(ATTRIBUTE_NAME, new AuthenticatedSession(notification.getAccount(), notification.getMechanism())); } break; case LOGGED_OUT: if (httpSession != null) { httpSession.removeAttribute(ATTRIBUTE_NAME); httpSession.removeAttribute(NO_ID_CHANGE_REQUIRED); } break; } } } private static class ServletAuthenticatedSessionManager implements AuthenticatedSessionManager { @Override public AuthenticatedSession lookupSession(HttpServerExchange exchange) { SessionManager sessionManager = exchange.getAttachment(SessionManager.ATTACHMENT_KEY); SessionConfig sessionConfig = exchange.getAttachment(SessionConfig.ATTACHMENT_KEY); if (sessionManager == null || sessionConfig == null) { return null; } Session httpSession = sessionManager.getSession(exchange, sessionConfig); if (httpSession != null) { return (AuthenticatedSession) httpSession.getAttribute(ATTRIBUTE_NAME); } return null; } @Override public void clearSession(HttpServerExchange exchange) { SessionManager sessionManager = exchange.getAttachment(SessionManager.ATTACHMENT_KEY); SessionConfig sessionConfig = exchange.getAttachment(SessionConfig.ATTACHMENT_KEY); if (sessionManager == null || sessionConfig == null) { return; } Session httpSession = sessionManager.getSession(exchange, sessionConfig); if (httpSession != null) { httpSession.removeAttribute(ATTRIBUTE_NAME); } } } private boolean isCacheable(final SecurityNotification notification) { return notification.isProgramatic() || notification.isCachingRequired(); } } NotificationReceiverHandler.java000066400000000000000000000035551420065311100342100ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/security/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.handlers; import java.util.Collection; import io.undertow.security.api.NotificationReceiver; import io.undertow.security.api.SecurityContext; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; /** * A {@link HttpHandler} to register a list of {@link NotificationReceiver} instances with the current {@link SecurityContext}. * * @author Darran Lofthouse */ public class NotificationReceiverHandler implements HttpHandler { private final HttpHandler next; private final NotificationReceiver[] receivers; public NotificationReceiverHandler(final HttpHandler next, final Collection receivers) { this.next = next; this.receivers = receivers.toArray(new NotificationReceiver[receivers.size()]); } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { SecurityContext sc = exchange.getSecurityContext(); for (int i = 0; i < receivers.length; ++i) { sc.registerNotificationReceiver(receivers[i]); } next.handleRequest(exchange); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/handlers/SecurityActions.java000066400000000000000000000027071420065311100320040ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.handlers; import java.security.AccessController; import java.security.PrivilegedAction; import io.undertow.security.api.SecurityContext; import io.undertow.server.HttpServerExchange; class SecurityActions { static void setSecurityContext(final HttpServerExchange exchange, final SecurityContext securityContext) { if (System.getSecurityManager() == null) { exchange.setSecurityContext(securityContext); } else { AccessController.doPrivileged(new PrivilegedAction() { @Override public Object run() { exchange.setSecurityContext(securityContext); return null; } }); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/handlers/SecurityInitialHandler.java000066400000000000000000000071041420065311100332670ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.handlers; import io.undertow.security.api.AuthenticationMode; import io.undertow.security.api.SecurityContext; import io.undertow.security.api.SecurityContextFactory; import io.undertow.security.idm.IdentityManager; import io.undertow.security.impl.SecurityContextFactoryImpl; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; /** * The security handler responsible for attaching the SecurityContext to the current {@link HttpServerExchange}. * * This handler is called early in the processing of the incoming request, subsequently supported authentication mechanisms will * be added to the context, a decision will then be made if authentication is required or optional and the associated mechanisms * will be called. * * In addition to the HTTPExchange authentication state can also be associated with the * {@link io.undertow.server.protocol.http.HttpServerConnection} and with the {@link io.undertow.server.session.Session} however this is * mechanism specific so it is down to the actual mechanisms to decide if there is state that can be re-used. * * @author Darran Lofthouse */ @SuppressWarnings("deprecation") public class SecurityInitialHandler extends AbstractSecurityContextAssociationHandler { private final AuthenticationMode authenticationMode; private final IdentityManager identityManager; private final String programaticMechName; private final SecurityContextFactory contextFactory; public SecurityInitialHandler(final AuthenticationMode authenticationMode, final IdentityManager identityManager, final String programaticMechName, final SecurityContextFactory contextFactory, final HttpHandler next) { super(next); this.authenticationMode = authenticationMode; this.identityManager = identityManager; this.programaticMechName = programaticMechName; this.contextFactory = contextFactory; } public SecurityInitialHandler(final AuthenticationMode authenticationMode, final IdentityManager identityManager, final String programaticMechName, final HttpHandler next) { this(authenticationMode, identityManager, programaticMechName, SecurityContextFactoryImpl.INSTANCE, next); } public SecurityInitialHandler(final AuthenticationMode authenticationMode, final IdentityManager identityManager, final HttpHandler next) { this(authenticationMode, identityManager, null, SecurityContextFactoryImpl.INSTANCE, next); } /** * @see io.undertow.security.handlers.AbstractSecurityContextAssociationHandler#createSecurityContext */ @Override public SecurityContext createSecurityContext(final HttpServerExchange exchange) { return contextFactory.createSecurityContext(exchange, authenticationMode, identityManager, programaticMechName); } } SinglePortConfidentialityHandler.java000066400000000000000000000053501420065311100352240ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/security/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.NetworkUtils; import java.net.URI; import java.net.URISyntaxException; /** * An extension to {@link AbstractConfidentialityHandler} that uses the Host header from the incoming message and specifies the * confidential address by just switching the port. * * @author Darran Lofthouse */ public class SinglePortConfidentialityHandler extends AbstractConfidentialityHandler { private final int redirectPort; public SinglePortConfidentialityHandler(final HttpHandler next, final int redirectPort) { super(next); this.redirectPort = redirectPort == 443 ? -1 : redirectPort; } @Override protected URI getRedirectURI(HttpServerExchange exchange) throws URISyntaxException { return getRedirectURI(exchange, redirectPort); } protected URI getRedirectURI(final HttpServerExchange exchange, final int port) throws URISyntaxException { final StringBuilder uriBuilder = new StringBuilder(); uriBuilder.append("https://"); uriBuilder.append(NetworkUtils.formatPossibleIpv6Address(exchange.getHostName())); if (port > 0) { uriBuilder.append(":").append(port); } String uri = exchange.getRequestURI(); if(exchange.isHostIncludedInRequestURI()) { int slashCount = 0; for(int i = 0; i < uri.length(); ++i) { if(uri.charAt(i) == '/') { slashCount++; if(slashCount == 3) { uri = uri.substring(i); break; } } } } uriBuilder.append(uri); final String queryString = exchange.getQueryString(); if (queryString != null && !queryString.isEmpty()) { uriBuilder.append("?").append(queryString); } return new URI(uriBuilder.toString()); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/idm/000077500000000000000000000000001420065311100247545ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/security/idm/Account.java000066400000000000000000000027361420065311100272230ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.idm; import java.io.Serializable; import java.security.Principal; import java.util.Set; /** * Representation of an account, most likely a user account. * * @author Darran Lofthouse */ public interface Account extends Serializable { Principal getPrincipal(); /** * Returns the users roles. * * @return A set of the users roles */ Set getRoles(); // TODO - Do we need a way to pass back to IDM that account is logging out? A few scenarios: - // 1 - Session expiration so cached account known to be logging out. // 2 - API call to logout. // 3 - End of HTTP request where account not cached, not strictly logging out but then again no real log-in. } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/idm/Credential.java000066400000000000000000000016141420065311100276730ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.idm; /** * Representation of a users Credential. * * @author Darran Lofthouse */ public interface Credential { } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/idm/DigestAlgorithm.java000066400000000000000000000044131420065311100307070ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.idm; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * Enumeration of the supported digest algorithms. * * @author Darran Lofthouse */ public enum DigestAlgorithm { MD5("MD5", "MD5", false), MD5_SESS("MD5-sess", "MD5", true); private static final Map BY_TOKEN; static { DigestAlgorithm[] algorithms = DigestAlgorithm.values(); Map byToken = new HashMap<>(algorithms.length); for (DigestAlgorithm current : algorithms) { byToken.put(current.token, current); } BY_TOKEN = Collections.unmodifiableMap(byToken); } private final String token; private final String digestAlgorithm; private final boolean session; DigestAlgorithm(final String token, final String digestAlgorithm, final boolean session) { this.token = token; this.digestAlgorithm = digestAlgorithm; this.session = session; } public String getToken() { return token; } public String getAlgorithm() { return digestAlgorithm; } public boolean isSession() { return session; } public MessageDigest getMessageDigest() throws NoSuchAlgorithmException { return MessageDigest.getInstance(digestAlgorithm); } public static DigestAlgorithm forName(final String name) { return BY_TOKEN.get(name); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/idm/DigestCredential.java000066400000000000000000000043731420065311100310400ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.idm; /** * An extension of {@link Credential} to provide some additional methods needed to enable verification of a request where * {@link io.undertow.security.impl.DigestAuthenticationMechanism} is in use. * * @author Darran Lofthouse */ public interface DigestCredential extends Credential { /** * Obtain the selected {@link DigestAlgorithm} for the request being authenticated. * * @return The {@link DigestAlgorithm} for the request being authenticated. */ DigestAlgorithm getAlgorithm(); /** * Called by the {@link IdentityManager} implementation to pass in the hex encoded a1 representation for validation against * the current request. * * The {@link Credential} is self validating based on the information passed in here, if verification is successful then the * {@link IdentityManager} can return the appropriate {@link Account} representation. * * @param ha1 - The hex encoded a1 value. * @return true if verification was successful, false otherwise. */ boolean verifyHA1(final byte[] ha1); /** * Get the realm name the credential is being validated against. * * @return The realm name. */ String getRealm(); /** * If the algorithm is session based return the session data to be included when generating the ha1. * * @return The session data. * @throws IllegalStateException where the algorithm is not session based. */ byte[] getSessionData(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/idm/ExternalCredential.java000066400000000000000000000022161420065311100313750ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.idm; import java.io.Serializable; /** * Representation of an external credential. This basically represents a trusted * 3rd party, e.g. a front end server that has performed authentication. * * @author Stuart Douglas */ public class ExternalCredential implements Serializable, Credential { public static final ExternalCredential INSTANCE = new ExternalCredential(); private ExternalCredential() { } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/idm/GSSContextCredential.java000066400000000000000000000022741420065311100316200ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.idm; import org.ietf.jgss.GSSContext; /** * A {@link Credential} to wrap an established GSSContext. * * @author Darran Lofthouse */ public class GSSContextCredential implements Credential { private final GSSContext gssContext; public GSSContextCredential(final GSSContext gssContext) { this.gssContext = gssContext; } public GSSContext getGssContext() { return gssContext; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/idm/IdentityManager.java000066400000000000000000000047021420065311100307060ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.idm; /** * The IdentityManager interface to be implemented by an identity manager implementation providing user verification and * identity loading to Undertow. * * Note: The IdentityManager interface is very much work in progress, methods are added to cover use cases as identified and * then simplified as common cases are defined. * * @author Darran Lofthouse */ public interface IdentityManager { /** * Verify a previously authenticated account. * * Typical checks could be along the lines of verifying that the account is not now locked or that the password has not been * reset since last verified, also this provides an opportunity for roles to be re-loaded if membership information has * changed. * * @param account - The {@link Account} to verify. * @return An updates {@link Account} if verification is successful, null otherwise. */ Account verify(final Account account); /** * Verify a supplied {@link Credential} against a requested ID. * * @param id - The requested ID for the account. * @param credential - The {@link Credential} to verify. * @return The {@link Account} for the user if verification was successful, null otherwise. */ Account verify(final String id, final Credential credential); /** * Perform verification when all we have is the Credential, in this case the IdentityManager is also responsible for mapping the Credential to an account. * * The most common scenario for this would be mapping an X509Certificate to the user it is associated with. * * @param credential * @return */ Account verify(final Credential credential); } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/idm/PasswordCredential.java000066400000000000000000000022021420065311100314100ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.idm; /** * A Credential representing the password of an Account. * * @author Darran Lofthouse */ public final class PasswordCredential implements Credential { private final char[] password; public PasswordCredential(final char[] password) { this.password = password; } public char[] getPassword() { return password; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/idm/X509CertificateCredential.java000066400000000000000000000023711420065311100324250ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.idm; import java.security.cert.X509Certificate; /** * A {@link Credential} implementation which wraps an X.509 certificate. * * @author Darran Lofthouse */ public final class X509CertificateCredential implements Credential { private final X509Certificate certificate; public X509CertificateCredential(final X509Certificate certificate) { this.certificate = certificate; } public X509Certificate getCertificate() { return certificate; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/000077500000000000000000000000001420065311100251445ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/AbstractSecurityContext.java000066400000000000000000000126161420065311100326550ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2015 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.impl; import static io.undertow.UndertowMessages.MESSAGES; import io.undertow.UndertowLogger; import io.undertow.security.api.NotificationReceiver; import io.undertow.security.api.SecurityContext; import io.undertow.security.api.SecurityNotification; import io.undertow.security.api.SecurityNotification.EventType; import io.undertow.security.idm.Account; import io.undertow.server.HttpServerExchange; /** * A base class for {@link SecurityContext} implementations predominantly focusing on the notification handling allowing the * specific implementation for focus on authentication. * * @author Darran Lofthouse */ public abstract class AbstractSecurityContext implements SecurityContext { private boolean authenticationRequired; protected final HttpServerExchange exchange; private Node notificationReceivers = null; private Account account; private String mechanismName; protected AbstractSecurityContext(final HttpServerExchange exchange) { this.exchange = exchange; } @Override public void setAuthenticationRequired() { authenticationRequired = true; } @Override public boolean isAuthenticationRequired() { return authenticationRequired; } @Override public boolean isAuthenticated() { return account != null; } @Override public Account getAuthenticatedAccount() { return account; } /** * @return The name of the mechanism used to authenticate the request. */ @Override public String getMechanismName() { return mechanismName; } @Override public void authenticationComplete(Account account, String mechanism, final boolean cachingRequired) { authenticationComplete(account, mechanism, false, cachingRequired); } protected void authenticationComplete(Account account, String mechanism, boolean programatic, final boolean cachingRequired) { this.account = account; this.mechanismName = mechanism; UndertowLogger.SECURITY_LOGGER.debugf("Authenticated as %s, roles %s", account.getPrincipal().getName(), account.getRoles()); sendNoticiation(new SecurityNotification(exchange, EventType.AUTHENTICATED, account, mechanism, programatic, MESSAGES.userAuthenticated(account.getPrincipal().getName()), cachingRequired)); } @Override public void authenticationFailed(String message, String mechanism) { UndertowLogger.SECURITY_LOGGER.debugf("Authentication failed with message %s and mechanism %s for %s", message, mechanism, exchange); sendNoticiation(new SecurityNotification(exchange, EventType.FAILED_AUTHENTICATION, null, mechanism, false, message, true)); } @Override public void registerNotificationReceiver(NotificationReceiver receiver) { if(notificationReceivers == null) { notificationReceivers = new Node<>(receiver); } else { Node cur = notificationReceivers; while (cur.next != null) { cur = cur.next; } cur.next = new Node<>(receiver); } } @Override public void removeNotificationReceiver(NotificationReceiver receiver) { Node cur = notificationReceivers; if(receiver.equals(cur.item)) { notificationReceivers = cur.next; } else { Node old = cur; while (cur.next != null) { cur = cur.next; if(receiver.equals(cur.item)) { old.next = cur.next; } old = cur; } } } private void sendNoticiation(final SecurityNotification notification) { Node cur = notificationReceivers; while (cur != null) { cur.item.handleNotification(notification); cur = cur.next; } } @Override public void logout() { if (!isAuthenticated()) { return; } UndertowLogger.SECURITY_LOGGER.debugf("Logged out %s", exchange); sendNoticiation(new SecurityNotification(exchange, SecurityNotification.EventType.LOGGED_OUT, account, mechanismName, true, MESSAGES.userLoggedOut(account.getPrincipal().getName()), true)); this.account = null; this.mechanismName = null; } /** * To reduce allocations we use a custom linked list data structure * @param */ protected static final class Node { final T item; Node next; private Node(T item) { this.item = item; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/AuthenticationInfoToken.java000066400000000000000000000045131420065311100326060ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.impl; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import io.undertow.util.HeaderToken; import io.undertow.util.HeaderTokenParser; import io.undertow.util.Headers; import io.undertow.util.HttpString; /** * Enumeration of tokens expected in a HTTP Digest 'Authentication-Info' header. * * @author Darran Lofthouse */ public enum AuthenticationInfoToken implements HeaderToken { NEXT_NONCE(Headers.NEXT_NONCE, true), MESSAGE_QOP(Headers.QOP, true), RESPONSE_AUTH(Headers.RESPONSE_AUTH, true), CNONCE(Headers.CNONCE, true), NONCE_COUNT(Headers.NONCE_COUNT, false); private static final HeaderTokenParser TOKEN_PARSER; static { Map expected = new LinkedHashMap<>( AuthenticationInfoToken.values().length); for (AuthenticationInfoToken current : AuthenticationInfoToken.values()) { expected.put(current.getName(), current); } TOKEN_PARSER = new HeaderTokenParser<>(Collections.unmodifiableMap(expected)); } private final String name; private final boolean quoted; AuthenticationInfoToken(final HttpString name, final boolean quoted) { this.name = name.toString(); this.quoted = quoted; } public String getName() { return name; } public boolean isAllowQuoted() { return quoted; } public static Map parseHeader(final String header) { return TOKEN_PARSER.parseHeader(header); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/BasicAuthenticationMechanism.java000066400000000000000000000261011420065311100335550ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.impl; import static io.undertow.UndertowMessages.MESSAGES; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.regex.Pattern; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.AuthenticationMechanismFactory; import io.undertow.security.api.SecurityContext; import io.undertow.security.idm.Account; import io.undertow.security.idm.IdentityManager; import io.undertow.security.idm.PasswordCredential; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.form.FormParserFactory; import io.undertow.util.FlexBase64; import io.undertow.util.Headers; import static io.undertow.util.Headers.AUTHORIZATION; import static io.undertow.util.Headers.BASIC; import static io.undertow.util.Headers.WWW_AUTHENTICATE; import static io.undertow.util.StatusCodes.UNAUTHORIZED; /** * The authentication handler responsible for BASIC authentication as described by RFC2617 * * @author Darran Lofthouse */ public class BasicAuthenticationMechanism implements AuthenticationMechanism { public static final AuthenticationMechanismFactory FACTORY = new Factory(); public static final String SILENT = "silent"; public static final String CHARSET = "charset"; /** * A comma separated list of patterns and charsets. The pattern is a regular expression. * * Because different browsers user different encodings this allows for the correct encoding to be selected based * on the current browser. In general though it is recommended that BASIC auth not be used when passwords contain * characters outside ASCII, as some browsers use the current locate to determine encoding. * * This list must have an even number of elements, as it is interpreted as pattern,charset,pattern,charset,... */ public static final String USER_AGENT_CHARSETS = "user-agent-charsets"; private final String name; private final String challenge; private static final String BASIC_PREFIX = BASIC + " "; private static final String LOWERCASE_BASIC_PREFIX = BASIC_PREFIX.toLowerCase(Locale.ENGLISH); private static final int PREFIX_LENGTH = BASIC_PREFIX.length(); private static final String COLON = ":"; /** * If silent is true then this mechanism will only take effect if there is an Authorization header. * * This allows you to combine basic auth with form auth, so human users will use form based auth, but allows * programmatic clients to login using basic auth. */ private final boolean silent; private final IdentityManager identityManager; private final Charset charset; private final Map userAgentCharsets; public BasicAuthenticationMechanism(final String realmName) { this(realmName, "BASIC"); } public BasicAuthenticationMechanism(final String realmName, final String mechanismName) { this(realmName, mechanismName, false); } public BasicAuthenticationMechanism(final String realmName, final String mechanismName, final boolean silent) { this(realmName, mechanismName, silent, null); } public BasicAuthenticationMechanism(final String realmName, final String mechanismName, final boolean silent, final IdentityManager identityManager) { this(realmName, mechanismName, silent, identityManager, StandardCharsets.UTF_8, Collections.emptyMap()); } public BasicAuthenticationMechanism(final String realmName, final String mechanismName, final boolean silent, final IdentityManager identityManager, Charset charset, Map userAgentCharsets) { this.challenge = BASIC_PREFIX + "realm=\"" + realmName + "\""; this.name = mechanismName; this.silent = silent; this.identityManager = identityManager; this.charset = charset; this.userAgentCharsets = Collections.unmodifiableMap(new LinkedHashMap<>(userAgentCharsets)); } @SuppressWarnings("deprecation") private IdentityManager getIdentityManager(SecurityContext securityContext) { return identityManager != null ? identityManager : securityContext.getIdentityManager(); } /** * @see io.undertow.server.HttpHandler#handleRequest(io.undertow.server.HttpServerExchange) */ @Override public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) { List authHeaders = exchange.getRequestHeaders().get(AUTHORIZATION); if (authHeaders != null) { for (String current : authHeaders) { if (current.toLowerCase(Locale.ENGLISH).startsWith(LOWERCASE_BASIC_PREFIX)) { String base64Challenge = current.substring(PREFIX_LENGTH); String plainChallenge = null; try { ByteBuffer decode = FlexBase64.decode(base64Challenge); Charset charset = this.charset; if(!userAgentCharsets.isEmpty()) { String ua = exchange.getRequestHeaders().getFirst(Headers.USER_AGENT); if(ua != null) { for (Map.Entry entry : userAgentCharsets.entrySet()) { if(entry.getKey().matcher(ua).find()) { charset = entry.getValue(); break; } } } } plainChallenge = new String(decode.array(), decode.arrayOffset(), decode.limit(), charset); UndertowLogger.SECURITY_LOGGER.debugf("Found basic auth header (decoded using charset %s) in %s", charset, exchange); } catch (IOException e) { UndertowLogger.SECURITY_LOGGER.debugf(e, "Failed to decode basic auth header in %s", exchange); } int colonPos; if (plainChallenge != null && (colonPos = plainChallenge.indexOf(COLON)) > -1) { String userName = plainChallenge.substring(0, colonPos); char[] password = plainChallenge.substring(colonPos + 1).toCharArray(); IdentityManager idm = getIdentityManager(securityContext); PasswordCredential credential = new PasswordCredential(password); try { final AuthenticationMechanismOutcome result; Account account = idm.verify(userName, credential); if (account != null) { securityContext.authenticationComplete(account, name, false); result = AuthenticationMechanismOutcome.AUTHENTICATED; } else { securityContext.authenticationFailed(MESSAGES.authenticationFailed(userName), name); result = AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } return result; } finally { clear(password); } } // By this point we had a header we should have been able to verify but for some reason // it was not correctly structured. return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } } } // No suitable header has been found in this request, return AuthenticationMechanismOutcome.NOT_ATTEMPTED; } @Override public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) { if(silent) { //if this is silent we only send a challenge if the request contained auth headers //otherwise we assume another method will send the challenge String authHeader = exchange.getRequestHeaders().getFirst(AUTHORIZATION); if(authHeader == null) { return ChallengeResult.NOT_SENT; } } exchange.getResponseHeaders().add(WWW_AUTHENTICATE, challenge); UndertowLogger.SECURITY_LOGGER.debugf("Sending basic auth challenge %s for %s", challenge, exchange); return new ChallengeResult(true, UNAUTHORIZED); } private static void clear(final char[] array) { for (int i = 0; i < array.length; i++) { array[i] = 0x00; } } public static class Factory implements AuthenticationMechanismFactory { @Deprecated public Factory(IdentityManager identityManager) {} public Factory() {} @Override public AuthenticationMechanism create(String mechanismName,IdentityManager identityManager, FormParserFactory formParserFactory, Map properties) { String realm = properties.get(REALM); String silent = properties.get(SILENT); String charsetString = properties.get(CHARSET); Charset charset = charsetString == null ? StandardCharsets.UTF_8 : Charset.forName(charsetString); Map userAgentCharsets = new HashMap<>(); String userAgentString = properties.get(USER_AGENT_CHARSETS); if(userAgentString != null) { String[] parts = userAgentString.split(","); if(parts.length % 2 != 0) { throw UndertowMessages.MESSAGES.userAgentCharsetMustHaveEvenNumberOfItems(userAgentString); } for(int i = 0; i < parts.length; i += 2) { Pattern pattern = Pattern.compile(parts[i]); Charset c = Charset.forName(parts[i + 1]); userAgentCharsets.put(pattern, c); } } return new BasicAuthenticationMechanism(realm, mechanismName, silent != null && silent.equals("true"), identityManager, charset, userAgentCharsets); } } } CachedAuthenticatedSessionMechanism.java000066400000000000000000000073431420065311100350020ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.impl; import io.undertow.security.api.AuthenticatedSessionManager; import io.undertow.security.api.AuthenticatedSessionManager.AuthenticatedSession; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.SecurityContext; import io.undertow.security.idm.Account; import io.undertow.security.idm.IdentityManager; import io.undertow.server.HttpServerExchange; /** * An {@link AuthenticationMechanism} which uses any cached {@link AuthenticatedSession}s. * * @author Darran Lofthouse */ public class CachedAuthenticatedSessionMechanism implements AuthenticationMechanism { private final IdentityManager identityManager; public CachedAuthenticatedSessionMechanism() { this(null); } public CachedAuthenticatedSessionMechanism(final IdentityManager identityManager) { this.identityManager = identityManager; } @SuppressWarnings("deprecation") private IdentityManager getIdentityManager(SecurityContext securityContext) { return identityManager != null ? identityManager : securityContext.getIdentityManager(); } @Override public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) { AuthenticatedSessionManager sessionManager = exchange.getAttachment(AuthenticatedSessionManager.ATTACHMENT_KEY); if (sessionManager != null) { return runCached(exchange, securityContext, sessionManager); } else { return AuthenticationMechanismOutcome.NOT_ATTEMPTED; } } public AuthenticationMechanismOutcome runCached(final HttpServerExchange exchange, final SecurityContext securityContext, final AuthenticatedSessionManager sessionManager) { AuthenticatedSession authSession = sessionManager.lookupSession(exchange); if (authSession != null) { Account account = getIdentityManager(securityContext).verify(authSession.getAccount()); if (account != null) { securityContext.authenticationComplete(account, authSession.getMechanism(), false); return AuthenticationMechanismOutcome.AUTHENTICATED; } else { sessionManager.clearSession(exchange); // We know we had a previously authenticated account but for some reason the IdentityManager is no longer // accepting it, we now return AuthenticationMechanismOutcome.NOT_ATTEMPTED; } } else { // It is possible an AuthenticatedSessionManager could have been available even if there was no chance of it // loading a session. return AuthenticationMechanismOutcome.NOT_ATTEMPTED; } } @Override public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) { // This mechanism can only use what is already available and can not send a challenge of it's own. return ChallengeResult.NOT_SENT; } } ClientCertAuthenticationMechanism.java000066400000000000000000000146671420065311100345270ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.impl; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.AuthenticationMechanismFactory; import io.undertow.security.api.SecurityContext; import io.undertow.security.idm.Account; import io.undertow.security.idm.Credential; import io.undertow.security.idm.IdentityManager; import io.undertow.security.idm.X509CertificateCredential; import io.undertow.server.HttpServerExchange; import io.undertow.server.RenegotiationRequiredException; import io.undertow.server.SSLSessionInfo; import io.undertow.server.handlers.form.FormParserFactory; import org.xnio.SslClientAuthMode; import javax.net.ssl.SSLPeerUnverifiedException; import java.io.IOException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.Map; /** * The Client Cert based authentication mechanism. *

* When authenticate is called the current request is checked to see if it a SSL request, this is further checked to identify if * the client has been verified at the SSL level. * * @author Darran Lofthouse */ public class ClientCertAuthenticationMechanism implements AuthenticationMechanism { public static final AuthenticationMechanismFactory FACTORY = new Factory(); public static final String FORCE_RENEGOTIATION = "force_renegotiation"; private final String name; private final IdentityManager identityManager; /** * If we should force a renegotiation if client certs were not supplied. true by default */ private final boolean forceRenegotiation; public ClientCertAuthenticationMechanism() { this(true); } public ClientCertAuthenticationMechanism(boolean forceRenegotiation) { this("CLIENT_CERT", forceRenegotiation); } public ClientCertAuthenticationMechanism(final String mechanismName) { this(mechanismName, true); } public ClientCertAuthenticationMechanism(final String mechanismName, boolean forceRenegotiation) { this(mechanismName, forceRenegotiation, null); } public ClientCertAuthenticationMechanism(final String mechanismName, boolean forceRenegotiation, IdentityManager identityManager) { this.name = mechanismName; this.forceRenegotiation = forceRenegotiation; this.identityManager = identityManager; } @SuppressWarnings("deprecation") private IdentityManager getIdentityManager(SecurityContext securityContext) { return identityManager != null ? identityManager : securityContext.getIdentityManager(); } public AuthenticationMechanismOutcome authenticate(final HttpServerExchange exchange, final SecurityContext securityContext) { SSLSessionInfo sslSession = exchange.getConnection().getSslSessionInfo(); if (sslSession != null) { try { Certificate[] clientCerts = getPeerCertificates(exchange, sslSession, securityContext); if (clientCerts[0] instanceof X509Certificate) { Credential credential = new X509CertificateCredential((X509Certificate) clientCerts[0]); IdentityManager idm = getIdentityManager(securityContext); Account account = idm.verify(credential); if (account != null) { securityContext.authenticationComplete(account, name, false); return AuthenticationMechanismOutcome.AUTHENTICATED; } } } catch (SSLPeerUnverifiedException e) { // No action - this mechanism can not attempt authentication without peer certificates so allow it to drop out // to NOT_ATTEMPTED. } } /* * For ClientCert we do not have a concept of a failed authentication, if the client did use a key then it was deemed * acceptable for the connection to be established, this mechanism then just 'attempts' to use it for authentication but * does not mandate success. */ return AuthenticationMechanismOutcome.NOT_ATTEMPTED; } private Certificate[] getPeerCertificates(final HttpServerExchange exchange, SSLSessionInfo sslSession, SecurityContext securityContext) throws SSLPeerUnverifiedException { try { return sslSession.getPeerCertificates(); } catch (RenegotiationRequiredException e) { //we only renegotiate if authentication is required if (forceRenegotiation && securityContext.isAuthenticationRequired()) { try { sslSession.renegotiate(exchange, SslClientAuthMode.REQUESTED); return sslSession.getPeerCertificates(); } catch (IOException e1) { //ignore } catch (RenegotiationRequiredException e1) { //ignore } } } throw new SSLPeerUnverifiedException(""); } @Override public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) { return ChallengeResult.NOT_SENT; } public static final class Factory implements AuthenticationMechanismFactory { @Deprecated public Factory(IdentityManager identityManager) {} public Factory() {} @Override public AuthenticationMechanism create(String mechanismName,IdentityManager identityManager, FormParserFactory formParserFactory, Map properties) { String forceRenegotiation = properties.get(FORCE_RENEGOTIATION); return new ClientCertAuthenticationMechanism(mechanismName, forceRenegotiation == null ? true : "true".equals(forceRenegotiation), identityManager); } } } DigestAuthenticationMechanism.java000066400000000000000000000714251420065311100337050ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.impl; import static io.undertow.UndertowLogger.REQUEST_LOGGER; import static io.undertow.UndertowMessages.MESSAGES; import static io.undertow.security.impl.DigestAuthorizationToken.parseHeader; import static io.undertow.util.Headers.AUTHENTICATION_INFO; import static io.undertow.util.Headers.AUTHORIZATION; import static io.undertow.util.Headers.DIGEST; import static io.undertow.util.Headers.NEXT_NONCE; import static io.undertow.util.Headers.WWW_AUTHENTICATE; import static io.undertow.util.StatusCodes.UNAUTHORIZED; import io.undertow.UndertowLogger; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.AuthenticationMechanismFactory; import io.undertow.security.api.NonceManager; import io.undertow.security.api.SecurityContext; import io.undertow.security.idm.Account; import io.undertow.security.idm.DigestAlgorithm; import io.undertow.security.idm.DigestCredential; import io.undertow.security.idm.IdentityManager; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.form.FormParserFactory; import io.undertow.util.AttachmentKey; import io.undertow.util.HeaderMap; import io.undertow.util.Headers; import io.undertow.util.HexConverter; import io.undertow.util.StatusCodes; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Collections; import java.util.EnumSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * {@link io.undertow.server.HttpHandler} to handle HTTP Digest authentication, both according to RFC-2617 and draft update to allow additional * algorithms to be used. * * @author Darran Lofthouse */ public class DigestAuthenticationMechanism implements AuthenticationMechanism { public static final AuthenticationMechanismFactory FACTORY = new Factory(); private static final String DEFAULT_NAME = "DIGEST"; private static final String DIGEST_PREFIX = DIGEST + " "; private static final int PREFIX_LENGTH = DIGEST_PREFIX.length(); private static final String OPAQUE_VALUE = "00000000000000000000000000000000"; private static final byte COLON = ':'; private final String mechanismName; private final IdentityManager identityManager; private static final Set MANDATORY_REQUEST_TOKENS; static { Set mandatoryTokens = EnumSet.noneOf(DigestAuthorizationToken.class); mandatoryTokens.add(DigestAuthorizationToken.USERNAME); mandatoryTokens.add(DigestAuthorizationToken.REALM); mandatoryTokens.add(DigestAuthorizationToken.NONCE); mandatoryTokens.add(DigestAuthorizationToken.DIGEST_URI); mandatoryTokens.add(DigestAuthorizationToken.RESPONSE); MANDATORY_REQUEST_TOKENS = Collections.unmodifiableSet(mandatoryTokens); } /** * The {@link List} of supported algorithms, this is assumed to be in priority order. */ private final List supportedAlgorithms; private final List supportedQops; private final String qopString; private final String realmName; // TODO - Will offer choice once backing store API/SPI is in. private final String domain; private final NonceManager nonceManager; // Where do session keys fit? Do we just hang onto a session key or keep visiting the user store to check if the password // has changed? // Maybe even support registration of a session so it can be invalidated? // 2013-05-29 - Session keys will be cached, where a cached key is used the IdentityManager is still given the // opportunity to check the Account is still valid. public DigestAuthenticationMechanism(final List supportedAlgorithms, final List supportedQops, final String realmName, final String domain, final NonceManager nonceManager) { this(supportedAlgorithms, supportedQops, realmName, domain, nonceManager, DEFAULT_NAME); } public DigestAuthenticationMechanism(final List supportedAlgorithms, final List supportedQops, final String realmName, final String domain, final NonceManager nonceManager, final String mechanismName) { this(supportedAlgorithms, supportedQops, realmName, domain, nonceManager, mechanismName, null); } public DigestAuthenticationMechanism(final List supportedAlgorithms, final List supportedQops, final String realmName, final String domain, final NonceManager nonceManager, final String mechanismName, final IdentityManager identityManager) { this.supportedAlgorithms = supportedAlgorithms; this.supportedQops = supportedQops; this.realmName = realmName; this.domain = domain; this.nonceManager = nonceManager; this.mechanismName = mechanismName; this.identityManager = identityManager; if (!supportedQops.isEmpty()) { StringBuilder sb = new StringBuilder(); Iterator it = supportedQops.iterator(); sb.append(it.next().getToken()); while (it.hasNext()) { sb.append(",").append(it.next().getToken()); } qopString = sb.toString(); } else { qopString = null; } } public DigestAuthenticationMechanism(final String realmName, final String domain, final String mechanismName) { this(realmName, domain, mechanismName, null); } public DigestAuthenticationMechanism(final String realmName, final String domain, final String mechanismName, final IdentityManager identityManager) { this(Collections.singletonList(DigestAlgorithm.MD5), Collections.singletonList(DigestQop.AUTH), realmName, domain, new SimpleNonceManager(), DEFAULT_NAME, identityManager); } @SuppressWarnings("deprecation") private IdentityManager getIdentityManager(SecurityContext securityContext) { return identityManager != null ? identityManager : securityContext.getIdentityManager(); } public AuthenticationMechanismOutcome authenticate(final HttpServerExchange exchange, final SecurityContext securityContext) { List authHeaders = exchange.getRequestHeaders().get(AUTHORIZATION); if (authHeaders != null) { for (String current : authHeaders) { if (current.startsWith(DIGEST_PREFIX)) { String digestChallenge = current.substring(PREFIX_LENGTH); try { DigestContext context = new DigestContext(); Map parsedHeader = parseHeader(digestChallenge); context.setMethod(exchange.getRequestMethod().toString()); context.setParsedHeader(parsedHeader); // Some form of Digest authentication is going to occur so get the DigestContext set on the exchange. exchange.putAttachment(DigestContext.ATTACHMENT_KEY, context); UndertowLogger.SECURITY_LOGGER.debugf("Found digest header %s in %s", current, exchange); return handleDigestHeader(exchange, securityContext); } catch (Exception e) { UndertowLogger.SECURITY_LOGGER.authenticationFailedFor(current, exchange, e); } } // By this point we had a header we should have been able to verify but for some reason // it was not correctly structured. return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } } // No suitable header has been found in this request, return AuthenticationMechanismOutcome.NOT_ATTEMPTED; } private AuthenticationMechanismOutcome handleDigestHeader(HttpServerExchange exchange, final SecurityContext securityContext) { DigestContext context = exchange.getAttachment(DigestContext.ATTACHMENT_KEY); Map parsedHeader = context.getParsedHeader(); // Step 1 - Verify the set of tokens received to ensure valid values. Set mandatoryTokens = EnumSet.copyOf(MANDATORY_REQUEST_TOKENS); if (!supportedAlgorithms.contains(DigestAlgorithm.MD5)) { // If we don't support MD5 then the client must choose an algorithm as we can not fall back to MD5. mandatoryTokens.add(DigestAuthorizationToken.ALGORITHM); } if (!supportedQops.isEmpty() && !supportedQops.contains(DigestQop.AUTH)) { // If we do not support auth then we are mandating auth-int so force the client to send a QOP mandatoryTokens.add(DigestAuthorizationToken.MESSAGE_QOP); } DigestQop qop = null; // This check is early as is increases the list of mandatory tokens. if (parsedHeader.containsKey(DigestAuthorizationToken.MESSAGE_QOP)) { qop = DigestQop.forName(parsedHeader.get(DigestAuthorizationToken.MESSAGE_QOP)); if (qop == null || !supportedQops.contains(qop)) { // We are also ensuring the client is not trying to force a qop that has been disabled. REQUEST_LOGGER.invalidTokenReceived(DigestAuthorizationToken.MESSAGE_QOP.getName(), parsedHeader.get(DigestAuthorizationToken.MESSAGE_QOP)); // TODO - This actually needs to result in a HTTP 400 Bad Request response and not a new challenge. return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } context.setQop(qop); mandatoryTokens.add(DigestAuthorizationToken.CNONCE); mandatoryTokens.add(DigestAuthorizationToken.NONCE_COUNT); } // Check all mandatory tokens are present. mandatoryTokens.removeAll(parsedHeader.keySet()); if (mandatoryTokens.size() > 0) { for (DigestAuthorizationToken currentToken : mandatoryTokens) { // TODO - Need a better check and possible concatenate the list of tokens - however // even having one missing token is not something we should routinely expect. REQUEST_LOGGER.missingAuthorizationToken(currentToken.getName()); } // TODO - This actually needs to result in a HTTP 400 Bad Request response and not a new challenge. return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } // Perform some validation of the remaining tokens. if (!realmName.equals(parsedHeader.get(DigestAuthorizationToken.REALM))) { REQUEST_LOGGER.invalidTokenReceived(DigestAuthorizationToken.REALM.getName(), parsedHeader.get(DigestAuthorizationToken.REALM)); // TODO - This actually needs to result in a HTTP 400 Bad Request response and not a new challenge. return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } if(parsedHeader.containsKey(DigestAuthorizationToken.DIGEST_URI)) { String uri = parsedHeader.get(DigestAuthorizationToken.DIGEST_URI); String requestURI = exchange.getRequestURI(); if(!exchange.getQueryString().isEmpty()) { requestURI = requestURI + "?" + exchange.getQueryString(); } if(!uri.equals(requestURI)) { //it is possible we were given an absolute URI //we reconstruct the URI from the host header to make sure they match up //I am not sure if this is overly strict, however I think it is better //to be safe than sorry requestURI = exchange.getRequestURL(); if(!exchange.getQueryString().isEmpty()) { requestURI = requestURI + "?" + exchange.getQueryString(); } if(!uri.equals(requestURI)) { //just end the auth process exchange.setStatusCode(StatusCodes.BAD_REQUEST); exchange.endExchange(); return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } } } else { return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } if (parsedHeader.containsKey(DigestAuthorizationToken.OPAQUE)) { if (!OPAQUE_VALUE.equals(parsedHeader.get(DigestAuthorizationToken.OPAQUE))) { REQUEST_LOGGER.invalidTokenReceived(DigestAuthorizationToken.OPAQUE.getName(), parsedHeader.get(DigestAuthorizationToken.OPAQUE)); return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } } DigestAlgorithm algorithm; if (parsedHeader.containsKey(DigestAuthorizationToken.ALGORITHM)) { algorithm = DigestAlgorithm.forName(parsedHeader.get(DigestAuthorizationToken.ALGORITHM)); if (algorithm == null || !supportedAlgorithms.contains(algorithm)) { // We are also ensuring the client is not trying to force an algorithm that has been disabled. REQUEST_LOGGER.invalidTokenReceived(DigestAuthorizationToken.ALGORITHM.getName(), parsedHeader.get(DigestAuthorizationToken.ALGORITHM)); // TODO - This actually needs to result in a HTTP 400 Bad Request response and not a new challenge. return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } } else { // We know this is safe as the algorithm token was made mandatory // if MD5 is not supported. algorithm = DigestAlgorithm.MD5; } try { context.setAlgorithm(algorithm); } catch (NoSuchAlgorithmException e) { /* * This should not be possible in a properly configured installation. */ REQUEST_LOGGER.exceptionProcessingRequest(e); return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } final String userName = parsedHeader.get(DigestAuthorizationToken.USERNAME); final IdentityManager identityManager = getIdentityManager(securityContext); final Account account; if (algorithm.isSession()) { /* This can follow one of the following: - * 1 - New session so use DigestCredentialImpl with the IdentityManager to * create a new session key. * 2 - Obtain the existing session key from the session store and validate it, just use * IdentityManager to validate account is still active and the current role assignment. */ throw new IllegalStateException("Not yet implemented."); } else { final DigestCredential credential = new DigestCredentialImpl(context); account = identityManager.verify(userName, credential); } if (account == null) { // Authentication has failed, this could either be caused by the user not-existing or it // could be caused due to an invalid hash. securityContext.authenticationFailed(MESSAGES.authenticationFailed(userName), mechanismName); return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } // Step 3 - Verify that the nonce was eligible to be used. if (!validateNonceUse(context, parsedHeader, exchange)) { // TODO - This is the right place to make use of the decision but the check needs to be much much sooner // otherwise a failure server // side could leave a packet that could be 're-played' after the failed auth. // The username and password verification passed but for some reason we do not like the nonce. context.markStale(); // We do not mark as a failure on the security context as this is not quite a failure, a client with a cached nonce // can easily hit this point. return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } // We have authenticated the remote user. sendAuthenticationInfoHeader(exchange); securityContext.authenticationComplete(account, mechanismName, false); return AuthenticationMechanismOutcome.AUTHENTICATED; // Step 4 - Set up any QOP related requirements. // TODO - Do QOP } private boolean validateRequest(final DigestContext context, final byte[] ha1) { byte[] ha2; DigestQop qop = context.getQop(); // Step 2.2 Calculate H(A2) if (qop == null || qop.equals(DigestQop.AUTH)) { ha2 = createHA2Auth(context, context.getParsedHeader()); } else { ha2 = createHA2AuthInt(); } byte[] requestDigest; if (qop == null) { requestDigest = createRFC2069RequestDigest(ha1, ha2, context); } else { requestDigest = createRFC2617RequestDigest(ha1, ha2, context); } byte[] providedResponse = context.getParsedHeader().get(DigestAuthorizationToken.RESPONSE).getBytes(StandardCharsets.UTF_8); return MessageDigest.isEqual(requestDigest, providedResponse); } private boolean validateNonceUse(DigestContext context, Map parsedHeader, final HttpServerExchange exchange) { String suppliedNonce = parsedHeader.get(DigestAuthorizationToken.NONCE); int nonceCount = -1; if (parsedHeader.containsKey(DigestAuthorizationToken.NONCE_COUNT)) { String nonceCountHex = parsedHeader.get(DigestAuthorizationToken.NONCE_COUNT); nonceCount = Integer.parseInt(nonceCountHex, 16); } context.setNonce(suppliedNonce); // TODO - A replay attempt will need an exception. return (nonceManager.validateNonce(suppliedNonce, nonceCount, exchange)); } private byte[] createHA2Auth(final DigestContext context, Map parsedHeader) { byte[] method = context.getMethod().getBytes(StandardCharsets.UTF_8); byte[] digestUri = parsedHeader.get(DigestAuthorizationToken.DIGEST_URI).getBytes(StandardCharsets.UTF_8); MessageDigest digest = context.getDigest(); try { digest.update(method); digest.update(COLON); digest.update(digestUri); return HexConverter.convertToHexBytes(digest.digest()); } finally { digest.reset(); } } private byte[] createHA2AuthInt() { // TODO - Implement method. throw new IllegalStateException("Method not implemented."); } private byte[] createRFC2069RequestDigest(final byte[] ha1, final byte[] ha2, final DigestContext context) { final MessageDigest digest = context.getDigest(); final Map parsedHeader = context.getParsedHeader(); byte[] nonce = parsedHeader.get(DigestAuthorizationToken.NONCE).getBytes(StandardCharsets.UTF_8); try { digest.update(ha1); digest.update(COLON); digest.update(nonce); digest.update(COLON); digest.update(ha2); return HexConverter.convertToHexBytes(digest.digest()); } finally { digest.reset(); } } private byte[] createRFC2617RequestDigest(final byte[] ha1, final byte[] ha2, final DigestContext context) { final MessageDigest digest = context.getDigest(); final Map parsedHeader = context.getParsedHeader(); byte[] nonce = parsedHeader.get(DigestAuthorizationToken.NONCE).getBytes(StandardCharsets.UTF_8); byte[] nonceCount = parsedHeader.get(DigestAuthorizationToken.NONCE_COUNT).getBytes(StandardCharsets.UTF_8); byte[] cnonce = parsedHeader.get(DigestAuthorizationToken.CNONCE).getBytes(StandardCharsets.UTF_8); byte[] qop = parsedHeader.get(DigestAuthorizationToken.MESSAGE_QOP).getBytes(StandardCharsets.UTF_8); try { digest.update(ha1); digest.update(COLON); digest.update(nonce); digest.update(COLON); digest.update(nonceCount); digest.update(COLON); digest.update(cnonce); digest.update(COLON); digest.update(qop); digest.update(COLON); digest.update(ha2); return HexConverter.convertToHexBytes(digest.digest()); } finally { digest.reset(); } } @Override public ChallengeResult sendChallenge(final HttpServerExchange exchange, final SecurityContext securityContext) { DigestContext context = exchange.getAttachment(DigestContext.ATTACHMENT_KEY); boolean stale = context == null ? false : context.isStale(); StringBuilder rb = new StringBuilder(DIGEST_PREFIX); rb.append(Headers.REALM.toString()).append("=\"").append(realmName).append("\","); rb.append(Headers.DOMAIN.toString()).append("=\"").append(domain).append("\","); // based on security constraints. rb.append(Headers.NONCE.toString()).append("=\"").append(nonceManager.nextNonce(null, exchange)).append("\","); // Not currently using OPAQUE as it offers no integrity, used for session data leaves it vulnerable to // session fixation type issues as well. rb.append(Headers.OPAQUE.toString()).append("=\"00000000000000000000000000000000\""); if (stale) { rb.append(",stale=true"); } if (supportedAlgorithms.size() > 0) { // This header will need to be repeated once for each algorithm. rb.append(",").append(Headers.ALGORITHM.toString()).append("=%s"); } if (qopString != null) { rb.append(",").append(Headers.QOP.toString()).append("=\"").append(qopString).append("\""); } String theChallenge = rb.toString(); HeaderMap responseHeader = exchange.getResponseHeaders(); if (supportedAlgorithms.isEmpty()) { responseHeader.add(WWW_AUTHENTICATE, theChallenge); } else { for (DigestAlgorithm current : supportedAlgorithms) { responseHeader.add(WWW_AUTHENTICATE, String.format(theChallenge, current.getToken())); } } return new ChallengeResult(true, UNAUTHORIZED); } public void sendAuthenticationInfoHeader(final HttpServerExchange exchange) { DigestContext context = exchange.getAttachment(DigestContext.ATTACHMENT_KEY); DigestQop qop = context.getQop(); String currentNonce = context.getNonce(); String nextNonce = nonceManager.nextNonce(currentNonce, exchange); if (qop != null || !nextNonce.equals(currentNonce)) { StringBuilder sb = new StringBuilder(); sb.append(NEXT_NONCE).append("=\"").append(nextNonce).append("\""); if (qop != null) { Map parsedHeader = context.getParsedHeader(); sb.append(",").append(Headers.QOP.toString()).append("=\"").append(qop.getToken()).append("\""); byte[] ha1 = context.getHa1(); byte[] ha2; if (qop == DigestQop.AUTH) { ha2 = createHA2Auth(context); } else { ha2 = createHA2AuthInt(); } String rspauth = new String(createRFC2617RequestDigest(ha1, ha2, context), StandardCharsets.UTF_8); sb.append(",").append(Headers.RESPONSE_AUTH.toString()).append("=\"").append(rspauth).append("\""); sb.append(",").append(Headers.CNONCE.toString()).append("=\"").append(parsedHeader.get(DigestAuthorizationToken.CNONCE)).append("\""); sb.append(",").append(Headers.NONCE_COUNT.toString()).append("=").append(parsedHeader.get(DigestAuthorizationToken.NONCE_COUNT)); } HeaderMap responseHeader = exchange.getResponseHeaders(); responseHeader.add(AUTHENTICATION_INFO, sb.toString()); } exchange.removeAttachment(DigestContext.ATTACHMENT_KEY); } private byte[] createHA2Auth(final DigestContext context) { byte[] digestUri = context.getParsedHeader().get(DigestAuthorizationToken.DIGEST_URI).getBytes(StandardCharsets.UTF_8); MessageDigest digest = context.getDigest(); try { digest.update(COLON); digest.update(digestUri); return HexConverter.convertToHexBytes(digest.digest()); } finally { digest.reset(); } } private static class DigestContext { static final AttachmentKey ATTACHMENT_KEY = AttachmentKey.create(DigestContext.class); private String method; private String nonce; private DigestQop qop; private byte[] ha1; private DigestAlgorithm algorithm; private MessageDigest digest; private boolean stale = false; Map parsedHeader; String getMethod() { return method; } void setMethod(String method) { this.method = method; } boolean isStale() { return stale; } void markStale() { this.stale = true; } String getNonce() { return nonce; } void setNonce(String nonce) { this.nonce = nonce; } DigestQop getQop() { return qop; } void setQop(DigestQop qop) { this.qop = qop; } byte[] getHa1() { return ha1; } void setHa1(byte[] ha1) { this.ha1 = ha1; } DigestAlgorithm getAlgorithm() { return algorithm; } void setAlgorithm(DigestAlgorithm algorithm) throws NoSuchAlgorithmException { this.algorithm = algorithm; digest = algorithm.getMessageDigest(); } MessageDigest getDigest() { return digest; } Map getParsedHeader() { return parsedHeader; } void setParsedHeader(Map parsedHeader) { this.parsedHeader = parsedHeader; } } private class DigestCredentialImpl implements DigestCredential { private final DigestContext context; private DigestCredentialImpl(final DigestContext digestContext) { this.context = digestContext; } @Override public DigestAlgorithm getAlgorithm() { return context.getAlgorithm(); } @Override public boolean verifyHA1(byte[] ha1) { context.setHa1(ha1); // Cache for subsequent use. return validateRequest(context, ha1); } @Override public String getRealm() { return realmName; } @Override public byte[] getSessionData() { if (!context.getAlgorithm().isSession()) { throw MESSAGES.noSessionData(); } byte[] nonce = context.getParsedHeader().get(DigestAuthorizationToken.NONCE).getBytes(StandardCharsets.UTF_8); byte[] cnonce = context.getParsedHeader().get(DigestAuthorizationToken.CNONCE).getBytes(StandardCharsets.UTF_8); byte[] response = new byte[nonce.length + cnonce.length + 1]; System.arraycopy(nonce, 0, response, 0, nonce.length); response[nonce.length] = ':'; System.arraycopy(cnonce, 0, response, nonce.length + 1, cnonce.length); return response; } } public static final class Factory implements AuthenticationMechanismFactory { @Deprecated public Factory(IdentityManager identityManager) {} public Factory() {} @Override public AuthenticationMechanism create(String mechanismName,IdentityManager identityManager, FormParserFactory formParserFactory, Map properties) { return new DigestAuthenticationMechanism(properties.get(REALM), properties.get(CONTEXT_PATH), mechanismName, identityManager); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/DigestAuthorizationToken.java000066400000000000000000000050631420065311100330140ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.impl; import io.undertow.util.HeaderToken; import io.undertow.util.HeaderTokenParser; import io.undertow.util.Headers; import io.undertow.util.HttpString; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; /** * Enumeration of tokens expected in a HTTP Digest 'Authorization' header. * * @author Darran Lofthouse */ public enum DigestAuthorizationToken implements HeaderToken { USERNAME(Headers.USERNAME, true), REALM(Headers.REALM, true), NONCE(Headers.NONCE, true), DIGEST_URI(Headers.URI, true), RESPONSE(Headers.RESPONSE, true), ALGORITHM(Headers.ALGORITHM, true), CNONCE(Headers.CNONCE, true), OPAQUE(Headers.OPAQUE, true), MESSAGE_QOP(Headers.QOP, true), NONCE_COUNT(Headers.NONCE_COUNT, false), AUTH_PARAM(Headers.AUTH_PARAM, false); private static final HeaderTokenParser TOKEN_PARSER; static { Map expected = new LinkedHashMap<>( DigestAuthorizationToken.values().length); for (DigestAuthorizationToken current : DigestAuthorizationToken.values()) { expected.put(current.getName(), current); } TOKEN_PARSER = new HeaderTokenParser<>(Collections.unmodifiableMap(expected)); } private final String name; private final boolean quoted; DigestAuthorizationToken(final HttpString name, final boolean quoted) { this.name = name.toString(); this.quoted = quoted; } @Override public String getName() { return name; } @Override public boolean isAllowQuoted() { return quoted; } public static Map parseHeader(final String header) { return TOKEN_PARSER.parseHeader(header); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/DigestQop.java000066400000000000000000000034771420065311100277210ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.impl; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * Enumeration to represent the Digest quality of protection options. * * @author Darran Lofthouse */ public enum DigestQop { AUTH("auth", false), AUTH_INT("auth-int", true); private static final Map BY_TOKEN; static { DigestQop[] qops = DigestQop.values(); Map byToken = new HashMap<>(qops.length); for (DigestQop current : qops) { byToken.put(current.token, current); } BY_TOKEN = Collections.unmodifiableMap(byToken); } private final String token; private final boolean integrity; DigestQop(final String token, final boolean integrity) { this.token = token; this.integrity = integrity; } public String getToken() { return token; } public boolean isMessageIntegrity() { return integrity; } public static DigestQop forName(final String name) { return BY_TOKEN.get(name); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/DigestWWWAuthenticateToken.java000066400000000000000000000046611420065311100332020ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.impl; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import io.undertow.util.HeaderToken; import io.undertow.util.HeaderTokenParser; import io.undertow.util.Headers; import io.undertow.util.HttpString; /** * Enumeration of tokens expected in a HTTP Digest 'WWW_Authenticate' header. * * @author Darran Lofthouse */ public enum DigestWWWAuthenticateToken implements HeaderToken { REALM(Headers.REALM, true), DOMAIN(Headers.DOMAIN, true), NONCE(Headers.NONCE, true), OPAQUE(Headers.OPAQUE, true), STALE(Headers.STALE, false), ALGORITHM(Headers.ALGORITHM, false), MESSAGE_QOP(Headers.QOP, true), AUTH_PARAM(Headers.AUTH_PARAM, false); private static final HeaderTokenParser TOKEN_PARSER; static { Map expected = new LinkedHashMap<>( DigestWWWAuthenticateToken.values().length); for (DigestWWWAuthenticateToken current : DigestWWWAuthenticateToken.values()) { expected.put(current.getName(), current); } TOKEN_PARSER = new HeaderTokenParser<>(Collections.unmodifiableMap(expected)); } private final String name; private final boolean quoted; DigestWWWAuthenticateToken(final HttpString name, final boolean quoted) { this.name = name.toString(); this.quoted = quoted; } public String getName() { return name; } public boolean isAllowQuoted() { return quoted; } public static Map parseHeader(final String header) { return TOKEN_PARSER.parseHeader(header); } } ExternalAuthenticationMechanism.java000066400000000000000000000077511420065311100342510ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.impl; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.AuthenticationMechanismFactory; import io.undertow.security.api.SecurityContext; import io.undertow.security.idm.Account; import io.undertow.security.idm.ExternalCredential; import io.undertow.security.idm.IdentityManager; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.form.FormParserFactory; import io.undertow.util.AttachmentKey; import java.util.Map; /** * * Authentication mechanism that uses an externally provided principal. * * WARNING: This method performs no verification. It must only be used if there is no * way for an end user to modify the principal, for example if Undertow is behind a * front end server that is responsible for authentication. * * @author Stuart Douglas */ public class ExternalAuthenticationMechanism implements AuthenticationMechanism { public static final AuthenticationMechanismFactory FACTORY = new Factory(); public static final String NAME = "EXTERNAL"; private final String name; private final IdentityManager identityManager; public static final AttachmentKey EXTERNAL_PRINCIPAL = AttachmentKey.create(String.class); public static final AttachmentKey EXTERNAL_AUTHENTICATION_TYPE = AttachmentKey.create(String.class); public ExternalAuthenticationMechanism(String name, IdentityManager identityManager) { this.name = name; this.identityManager = identityManager; } public ExternalAuthenticationMechanism(String name) { this(name, null); } public ExternalAuthenticationMechanism() { this(NAME); } @SuppressWarnings("deprecation") private IdentityManager getIdentityManager(SecurityContext securityContext) { return identityManager != null ? identityManager : securityContext.getIdentityManager(); } @Override public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) { String principal = exchange.getAttachment(EXTERNAL_PRINCIPAL); if(principal == null) { return AuthenticationMechanismOutcome.NOT_ATTEMPTED; } Account account = getIdentityManager(securityContext).verify(principal, ExternalCredential.INSTANCE); if(account == null) { return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } String name = exchange.getAttachment(EXTERNAL_AUTHENTICATION_TYPE); securityContext.authenticationComplete(account, name != null ? name: this.name, false); return AuthenticationMechanismOutcome.AUTHENTICATED; } @Override public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) { return ChallengeResult.NOT_SENT; } public static final class Factory implements AuthenticationMechanismFactory { @Deprecated public Factory(IdentityManager identityManager) {} public Factory() {} @Override public AuthenticationMechanism create(String mechanismName,IdentityManager identityManager, FormParserFactory formParserFactory, Map properties) { return new ExternalAuthenticationMechanism(mechanismName, identityManager); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/FormAuthenticationMechanism.java000066400000000000000000000252311420065311100334420ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.impl; import io.undertow.UndertowLogger; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.SecurityContext; import io.undertow.security.idm.Account; import io.undertow.security.idm.IdentityManager; import io.undertow.security.idm.PasswordCredential; import io.undertow.server.DefaultResponseListener; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.form.FormData; import io.undertow.server.handlers.form.FormDataParser; import io.undertow.server.handlers.form.FormParserFactory; import io.undertow.server.session.Session; import io.undertow.util.Headers; import io.undertow.util.Methods; import io.undertow.util.RedirectBuilder; import io.undertow.util.Sessions; import io.undertow.util.StatusCodes; import java.io.IOException; import java.io.UncheckedIOException; import static io.undertow.UndertowMessages.MESSAGES; /** * @author Stuart Douglas */ public class FormAuthenticationMechanism implements AuthenticationMechanism { public static final String LOCATION_ATTRIBUTE = FormAuthenticationMechanism.class.getName() + ".LOCATION"; public static final String DEFAULT_POST_LOCATION = "/j_security_check"; private final String name; private final String loginPage; private final String errorPage; private final String postLocation; private final FormParserFactory formParserFactory; private final IdentityManager identityManager; public FormAuthenticationMechanism(final String name, final String loginPage, final String errorPage) { this(FormParserFactory.builder().build(), name, loginPage, errorPage); } public FormAuthenticationMechanism(final String name, final String loginPage, final String errorPage, final String postLocation) { this(FormParserFactory.builder().build(), name, loginPage, errorPage, postLocation); } public FormAuthenticationMechanism(final FormParserFactory formParserFactory, final String name, final String loginPage, final String errorPage) { this(formParserFactory, name, loginPage, errorPage, DEFAULT_POST_LOCATION); } public FormAuthenticationMechanism(final FormParserFactory formParserFactory, final String name, final String loginPage, final String errorPage, final IdentityManager identityManager) { this(formParserFactory, name, loginPage, errorPage, DEFAULT_POST_LOCATION, identityManager); } public FormAuthenticationMechanism(final FormParserFactory formParserFactory, final String name, final String loginPage, final String errorPage, final String postLocation) { this(formParserFactory, name, loginPage, errorPage, postLocation, null); } public FormAuthenticationMechanism(final FormParserFactory formParserFactory, final String name, final String loginPage, final String errorPage, final String postLocation, final IdentityManager identityManager) { this.name = name; this.loginPage = loginPage; this.errorPage = errorPage; this.postLocation = postLocation; this.formParserFactory = formParserFactory; this.identityManager = identityManager; } @SuppressWarnings("deprecation") private IdentityManager getIdentityManager(SecurityContext securityContext) { return identityManager != null ? identityManager : securityContext.getIdentityManager(); } @Override public AuthenticationMechanismOutcome authenticate(final HttpServerExchange exchange, final SecurityContext securityContext) { if (exchange.getRequestPath().endsWith(postLocation) && exchange.getRequestMethod().equals(Methods.POST)) { return runFormAuth(exchange, securityContext); } else { return AuthenticationMechanismOutcome.NOT_ATTEMPTED; } } public AuthenticationMechanismOutcome runFormAuth(final HttpServerExchange exchange, final SecurityContext securityContext) { final FormDataParser parser = formParserFactory.createParser(exchange); if (parser == null) { UndertowLogger.SECURITY_LOGGER.debug("Could not authenticate as no form parser is present"); // TODO - May need a better error signaling mechanism here to prevent repeated attempts. return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } Throwable original = null; AuthenticationMechanismOutcome retValue = null; try { final FormData data = parser.parseBlocking(); final FormData.FormValue jUsername = data.getFirst("j_username"); final FormData.FormValue jPassword = data.getFirst("j_password"); if (jUsername == null || jPassword == null) { UndertowLogger.SECURITY_LOGGER.debugf("Could not authenticate as username or password was not present in the posted result for %s", exchange); return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } final String userName = jUsername.getValue(); final String password = jPassword.getValue(); AuthenticationMechanismOutcome outcome = null; PasswordCredential credential = new PasswordCredential(password.toCharArray()); try { IdentityManager identityManager = getIdentityManager(securityContext); Account account = identityManager.verify(userName, credential); if (account != null) { securityContext.authenticationComplete(account, name, true); UndertowLogger.SECURITY_LOGGER.debugf("Authenticated user %s using for auth for %s", account.getPrincipal().getName(), exchange); outcome = AuthenticationMechanismOutcome.AUTHENTICATED; } else { securityContext.authenticationFailed(MESSAGES.authenticationFailed(userName), name); } } catch (Throwable t) { original = t; } finally { try { if (outcome == AuthenticationMechanismOutcome.AUTHENTICATED) { handleRedirectBack(exchange); exchange.endExchange(); } retValue = outcome != null ? outcome : AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } catch (Throwable t) { if (original != null) { original.addSuppressed(t); } else { original = t; } } } } catch (IOException e) { original = new UncheckedIOException(e); } if (original != null) { if (original instanceof RuntimeException) { throw (RuntimeException) original; } if (original instanceof Error) { throw (Error) original; } } return retValue; } protected void handleRedirectBack(final HttpServerExchange exchange) { final Session session = Sessions.getSession(exchange); if (session != null) { final String location = (String) session.removeAttribute(LOCATION_ATTRIBUTE); if(location != null) { exchange.addDefaultResponseListener(new DefaultResponseListener() { @Override public boolean handleDefaultResponse(final HttpServerExchange exchange) { exchange.getResponseHeaders().put(Headers.LOCATION, location); exchange.setStatusCode(StatusCodes.FOUND); exchange.endExchange(); return true; } }); } } } public ChallengeResult sendChallenge(final HttpServerExchange exchange, final SecurityContext securityContext) { // make sure a request to root context is handled with trailing slash. Otherwise call to j_security_check will not // be handled correctly if (exchange.getRelativePath().isEmpty()) { exchange.getResponseHeaders().put(Headers.LOCATION, RedirectBuilder.redirect(exchange, exchange.getRelativePath() + "/", true)); return new ChallengeResult(true, StatusCodes.FOUND); } if (exchange.getRequestPath().endsWith(postLocation) && exchange.getRequestMethod().equals(Methods.POST)) { UndertowLogger.SECURITY_LOGGER.debugf("Serving form auth error page %s for %s", loginPage, exchange); // This method would no longer be called if authentication had already occurred. Integer code = servePage(exchange, errorPage); return new ChallengeResult(true, code); } else { UndertowLogger.SECURITY_LOGGER.debugf("Serving login form %s for %s", loginPage, exchange); // we need to store the URL storeInitialLocation(exchange); // TODO - Rather than redirecting, in order to make this mechanism compatible with the other mechanisms we need to // return the actual error page not a redirect. Integer code = servePage(exchange, loginPage); return new ChallengeResult(true, code); } } protected void storeInitialLocation(final HttpServerExchange exchange) { Session session = Sessions.getOrCreateSession(exchange); session.setAttribute(LOCATION_ATTRIBUTE, RedirectBuilder.redirect(exchange, exchange.getRelativePath())); } protected Integer servePage(final HttpServerExchange exchange, final String location) { sendRedirect(exchange, location); return StatusCodes.TEMPORARY_REDIRECT; } static void sendRedirect(final HttpServerExchange exchange, final String location) { // TODO - String concatenation to construct URLS is extremely error prone - switch to a URI which will better handle this. String loc = exchange.getRequestScheme() + "://" + exchange.getHostAndPort() + location; exchange.getResponseHeaders().put(Headers.LOCATION, loc); } } GSSAPIAuthenticationMechanism.java000066400000000000000000000341251420065311100334500ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.impl; import io.undertow.UndertowLogger; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.GSSAPIServerSubjectFactory; import io.undertow.security.api.SecurityContext; import io.undertow.security.idm.Account; import io.undertow.security.idm.GSSContextCredential; import io.undertow.security.idm.IdentityManager; import io.undertow.server.HttpServerExchange; import io.undertow.server.ServerConnection; import io.undertow.server.handlers.proxy.ExclusivityChecker; import io.undertow.util.AttachmentKey; import io.undertow.util.FlexBase64; import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSCredential; import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSManager; import org.ietf.jgss.Oid; import java.io.IOException; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; import java.security.Principal; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.List; import javax.security.auth.Subject; import javax.security.auth.kerberos.KerberosPrincipal; import static io.undertow.util.Headers.AUTHORIZATION; import static io.undertow.util.Headers.HOST; import static io.undertow.util.Headers.NEGOTIATE; import static io.undertow.util.Headers.WWW_AUTHENTICATE; import static io.undertow.util.StatusCodes.UNAUTHORIZED; /** * {@link io.undertow.security.api.AuthenticationMechanism} for GSSAPI / SPNEGO based authentication. *

* GSSAPI authentication is associated with the HTTP connection, as long as a connection is being re-used allow the * authentication state to be re-used. *

* TODO - May consider an option to allow it to also be associated with the underlying session but that has it's own risks so * would need to come with a warning. * * @author Darran Lofthouse */ public class GSSAPIAuthenticationMechanism implements AuthenticationMechanism { public static final ExclusivityChecker EXCLUSIVITY_CHECKER = new ExclusivityChecker() { @Override public boolean isExclusivityRequired(HttpServerExchange exchange) { List authHeaders = exchange.getRequestHeaders().get(AUTHORIZATION); if (authHeaders != null) { for (String current : authHeaders) { if (current.startsWith(NEGOTIATE_PREFIX)) { return true; } } } return false; } }; private static final String NEGOTIATION_PLAIN = NEGOTIATE.toString(); private static final String NEGOTIATE_PREFIX = NEGOTIATE + " "; private static final Oid[] DEFAULT_MECHANISMS; static { try { Oid spnego = new Oid("1.3.6.1.5.5.2"); Oid kerberos = new Oid("1.2.840.113554.1.2.2"); DEFAULT_MECHANISMS = new Oid[] { spnego, kerberos }; } catch (GSSException e) { throw new RuntimeException(e); } } private static final String name = "SPNEGO"; private final IdentityManager identityManager; private final GSSAPIServerSubjectFactory subjectFactory; private final Oid[] mechanisms; public GSSAPIAuthenticationMechanism(final GSSAPIServerSubjectFactory subjectFactory, IdentityManager identityManager, Oid ...supportedMechanisms) { this.subjectFactory = subjectFactory; this.identityManager = identityManager; this.mechanisms = supportedMechanisms; } public GSSAPIAuthenticationMechanism(final GSSAPIServerSubjectFactory subjectFactory, Oid ...supportedMechanisms) { this(subjectFactory, null, supportedMechanisms); } public GSSAPIAuthenticationMechanism(final GSSAPIServerSubjectFactory subjectFactory) { this(subjectFactory, DEFAULT_MECHANISMS); } @SuppressWarnings("deprecation") private IdentityManager getIdentityManager(SecurityContext securityContext) { return identityManager != null ? identityManager : securityContext.getIdentityManager(); } @Override public AuthenticationMechanismOutcome authenticate(final HttpServerExchange exchange, final SecurityContext securityContext) { ServerConnection connection = exchange.getConnection(); NegotiationContext negContext = connection.getAttachment(NegotiationContext.ATTACHMENT_KEY); if (negContext != null) { UndertowLogger.SECURITY_LOGGER.debugf("Existing negotiation context found for %s", exchange); exchange.putAttachment(NegotiationContext.ATTACHMENT_KEY, negContext); if (negContext.isEstablished()) { IdentityManager identityManager = getIdentityManager(securityContext); final Account account = identityManager.verify(new GSSContextCredential(negContext.getGssContext())); if (account != null) { securityContext.authenticationComplete(account, name, false); UndertowLogger.SECURITY_LOGGER.debugf("Authenticated as user %s with existing GSSAPI negotiation context for %s", account.getPrincipal().getName(), exchange); return AuthenticationMechanismOutcome.AUTHENTICATED; } else { UndertowLogger.SECURITY_LOGGER.debugf("Failed to authenticate with existing GSSAPI negotiation context for %s", exchange); return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } } } List authHeaders = exchange.getRequestHeaders().get(AUTHORIZATION); if (authHeaders != null) { for (String current : authHeaders) { if (current.startsWith(NEGOTIATE_PREFIX)) { String base64Challenge = current.substring(NEGOTIATE_PREFIX.length()); try { ByteBuffer challenge = FlexBase64.decode(base64Challenge); return runGSSAPI(exchange, challenge, securityContext); } catch (IOException e) { } // By this point we had a header we should have been able to verify but for some reason // it was not correctly structured. return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } } } // No suitable header was found so authentication was not even attempted. return AuthenticationMechanismOutcome.NOT_ATTEMPTED; } public ChallengeResult sendChallenge(final HttpServerExchange exchange, final SecurityContext securityContext) { NegotiationContext negContext = exchange.getAttachment(NegotiationContext.ATTACHMENT_KEY); String header = NEGOTIATION_PLAIN; if (negContext != null) { byte[] responseChallenge = negContext.useResponseToken(); exchange.putAttachment(NegotiationContext.ATTACHMENT_KEY, null); if (responseChallenge != null) { header = NEGOTIATE_PREFIX + FlexBase64.encodeString(responseChallenge, false); } } else { Subject server = null; try { server = subjectFactory.getSubjectForHost(getHostName(exchange)); } catch (GeneralSecurityException e) { // Deliberately ignore - no Subject so don't offer GSSAPI is our main concern here. } if (server == null) { return ChallengeResult.NOT_SENT; } } exchange.getResponseHeaders().add(WWW_AUTHENTICATE, header); UndertowLogger.SECURITY_LOGGER.debugf("Sending GSSAPI challenge for %s", exchange); return new ChallengeResult(true, UNAUTHORIZED); } public AuthenticationMechanismOutcome runGSSAPI(final HttpServerExchange exchange, final ByteBuffer challenge, final SecurityContext securityContext) { try { Subject server = subjectFactory.getSubjectForHost(getHostName(exchange)); // The AcceptSecurityContext takes over responsibility for setting the result. return Subject.doAs(server, new AcceptSecurityContext(exchange, challenge, securityContext)); } catch (GeneralSecurityException e) { UndertowLogger.SECURITY_LOGGER.failedToObtainSubject(exchange, e); return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } catch (PrivilegedActionException e) { UndertowLogger.SECURITY_LOGGER.failedToNegotiateAtGSSAPI(exchange, e.getCause()); return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } } private String getHostName(final HttpServerExchange exchange) { String hostName = exchange.getRequestHeaders().getFirst(HOST); if (hostName != null) { if (hostName.startsWith("[") && hostName.contains("]")) { hostName = hostName.substring(0, hostName.indexOf(']') + 1); } else if (hostName.contains(":")) { hostName = hostName.substring(0, hostName.indexOf(":")); } return hostName; } return null; } private class AcceptSecurityContext implements PrivilegedExceptionAction { private final HttpServerExchange exchange; private final ByteBuffer challenge; private final SecurityContext securityContext; private AcceptSecurityContext(final HttpServerExchange exchange, final ByteBuffer challenge, final SecurityContext securityContext) { this.exchange = exchange; this.challenge = challenge; this.securityContext = securityContext; } public AuthenticationMechanismOutcome run() throws GSSException { NegotiationContext negContext = exchange.getAttachment(NegotiationContext.ATTACHMENT_KEY); if (negContext == null) { negContext = new NegotiationContext(); exchange.putAttachment(NegotiationContext.ATTACHMENT_KEY, negContext); // Also cache it on the connection for future calls. exchange.getConnection().putAttachment(NegotiationContext.ATTACHMENT_KEY, negContext); } GSSContext gssContext = negContext.getGssContext(); if (gssContext == null) { GSSManager manager = GSSManager.getInstance(); GSSCredential credential = manager.createCredential(null, GSSCredential.INDEFINITE_LIFETIME, mechanisms, GSSCredential.ACCEPT_ONLY); gssContext = manager.createContext(credential); negContext.setGssContext(gssContext); } byte[] respToken = gssContext.acceptSecContext(challenge.array(), challenge.arrayOffset(), challenge.limit()); negContext.setResponseToken(respToken); if (negContext.isEstablished()) { if (respToken != null) { // There will be no further challenge but we do have a token so set it here. exchange.getResponseHeaders().add(WWW_AUTHENTICATE, NEGOTIATE_PREFIX + FlexBase64.encodeString(respToken, false)); } IdentityManager identityManager = securityContext.getIdentityManager(); final Account account = identityManager.verify(new GSSContextCredential(negContext.getGssContext())); if (account != null) { securityContext.authenticationComplete(account, name, false); return AuthenticationMechanismOutcome.AUTHENTICATED; } else { return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } } else { // This isn't a failure but as the context is not established another round trip with the client is needed. return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } } } private static class NegotiationContext { static final AttachmentKey ATTACHMENT_KEY = AttachmentKey.create(NegotiationContext.class); private GSSContext gssContext; private byte[] responseToken; private Principal principal; GSSContext getGssContext() { return gssContext; } void setGssContext(GSSContext gssContext) { this.gssContext = gssContext; } byte[] useResponseToken() { // The token only needs to be returned once so clear it once used. try { return responseToken; } finally { responseToken = null; } } void setResponseToken(byte[] responseToken) { this.responseToken = responseToken; } boolean isEstablished() { return gssContext != null ? gssContext.isEstablished() : false; } Principal getPrincipal() { if (!isEstablished()) { throw new IllegalStateException("No established GSSContext to use for the Principal."); } if (principal == null) { try { principal = new KerberosPrincipal(gssContext.getSrcName().toString()); } catch (GSSException e) { throw new IllegalStateException("Unable to create Principal", e); } } return principal; } } } GenericHeaderAuthenticationMechanism.java000066400000000000000000000132261420065311100351460ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.impl; import io.undertow.UndertowMessages; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.AuthenticationMechanismFactory; import io.undertow.security.api.SecurityContext; import io.undertow.security.idm.Account; import io.undertow.security.idm.IdentityManager; import io.undertow.security.idm.PasswordCredential; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.Cookie; import io.undertow.server.handlers.form.FormParserFactory; import io.undertow.util.HttpString; import java.util.ArrayList; import java.util.List; import java.util.Map; import static io.undertow.security.api.AuthenticationMechanism.AuthenticationMechanismOutcome.AUTHENTICATED; import static io.undertow.security.api.AuthenticationMechanism.AuthenticationMechanismOutcome.NOT_ATTEMPTED; import static io.undertow.security.api.AuthenticationMechanism.AuthenticationMechanismOutcome.NOT_AUTHENTICATED; /** * A authentication mechanism that requires the presence of two headers in the request. One of these will be used as a * principal and the other as a password credential. * * @author Stuart Douglas * @author Richard Opalka */ public class GenericHeaderAuthenticationMechanism implements AuthenticationMechanism { public static final AuthenticationMechanismFactory FACTORY = new Factory(); public static final String NAME = "GENERIC_HEADER"; public static final String IDENTITY_HEADER = "identity-header"; public static final String SESSION_HEADER = "session-header"; private final String mechanismName; private final List identityHeaders; private final List sessionCookieNames; private final IdentityManager identityManager; public GenericHeaderAuthenticationMechanism(String mechanismName, List identityHeaders, List sessionCookieNames, IdentityManager identityManager) { this.mechanismName = mechanismName; this.identityHeaders = identityHeaders; this.sessionCookieNames = sessionCookieNames; this.identityManager = identityManager; } @Override public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) { String principal = getPrincipal(exchange); if(principal == null) { return NOT_ATTEMPTED; } String session = getSession(exchange); if(session == null) { return NOT_ATTEMPTED; } Account account = identityManager.verify(principal, new PasswordCredential(session.toCharArray())); if(account == null) { securityContext.authenticationFailed(UndertowMessages.MESSAGES.authenticationFailed(principal), mechanismName); return NOT_AUTHENTICATED; } securityContext.authenticationComplete(account, mechanismName, false); return AUTHENTICATED; } private String getSession(HttpServerExchange exchange) { for (String header : sessionCookieNames) { for (Cookie cookie : exchange.requestCookies()) { if (header.equals(cookie.getName())) { return cookie.getValue(); } } } return null; } private String getPrincipal(HttpServerExchange exchange) { for(HttpString header : identityHeaders) { String res = exchange.getRequestHeaders().getFirst(header); if(res != null) { return res; } } return null; } @Override public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) { return ChallengeResult.NOT_SENT; } public static class Factory implements AuthenticationMechanismFactory { @Deprecated public Factory(IdentityManager identityManager) { } public Factory() { } @Override public AuthenticationMechanism create(String mechanismName, IdentityManager identityManager, FormParserFactory formParserFactory, Map properties) { String identity = properties.get(IDENTITY_HEADER); if(identity == null) { throw UndertowMessages.MESSAGES.authenticationPropertyNotSet(mechanismName, IDENTITY_HEADER); } String session = properties.get(SESSION_HEADER); if(session == null) { throw UndertowMessages.MESSAGES.authenticationPropertyNotSet(mechanismName, SESSION_HEADER); } List ids = new ArrayList<>(); for(String s : identity.split(",")) { ids.add(new HttpString(s)); } List sessions = new ArrayList<>(); for(String s : session.split(",")) { sessions.add(s); } return new GenericHeaderAuthenticationMechanism(mechanismName, ids, sessions, identityManager); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/InMemorySingleSignOnManager.java000066400000000000000000000077331420065311100333330ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.impl; import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import io.undertow.security.idm.Account; import io.undertow.server.session.SecureRandomSessionIdGenerator; import io.undertow.server.session.Session; import io.undertow.server.session.SessionManager; import io.undertow.util.CopyOnWriteMap; import org.jboss.logging.Logger; /** * @author Stuart Douglas * @author Paul Ferraro */ public class InMemorySingleSignOnManager implements SingleSignOnManager { private static final Logger log = Logger.getLogger(InMemorySingleSignOnManager.class); private static final SecureRandomSessionIdGenerator SECURE_RANDOM_SESSION_ID_GENERATOR = new SecureRandomSessionIdGenerator(); private final Map ssoEntries = new ConcurrentHashMap<>(); @Override public SingleSignOn findSingleSignOn(String ssoId) { return this.ssoEntries.get(ssoId); } @Override public SingleSignOn createSingleSignOn(Account account, String mechanism) { String id = SECURE_RANDOM_SESSION_ID_GENERATOR.createSessionId(); SingleSignOn entry = new SimpleSingleSignOnEntry(id, account, mechanism); this.ssoEntries.put(id, entry); if(log.isTraceEnabled()) { log.tracef("Creating SSO ID %s for Principal %s and Roles %s.", id, account.getPrincipal().getName(), account.getRoles().toString()); } return entry; } @Override public void removeSingleSignOn(SingleSignOn sso) { if(log.isTraceEnabled()) { log.tracef("Removing SSO ID %s.", sso.getId()); } this.ssoEntries.remove(sso.getId()); } private static class SimpleSingleSignOnEntry implements SingleSignOn { private final String id; private final Account account; private final String mechanismName; private final Map sessions = new CopyOnWriteMap<>(); SimpleSingleSignOnEntry(String id, Account account, String mechanismName) { this.id = id; this.account = account; this.mechanismName = mechanismName; } @Override public String getId() { return this.id; } @Override public Account getAccount() { return this.account; } @Override public String getMechanismName() { return this.mechanismName; } @Override public Iterator iterator() { return Collections.unmodifiableCollection(this.sessions.values()).iterator(); } @Override public boolean contains(Session session) { return this.sessions.containsKey(session.getSessionManager()); } @Override public Session getSession(SessionManager manager) { return this.sessions.get(manager); } @Override public void add(Session session) { this.sessions.put(session.getSessionManager(), session); } @Override public void remove(Session session) { this.sessions.remove(session.getSessionManager()); } @Override public void close() { // Do nothing } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/SecurityActions.java000066400000000000000000000031731420065311100311430ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.impl; import java.security.AccessController; import java.security.PrivilegedAction; import io.undertow.security.api.AuthenticationMode; import io.undertow.security.idm.IdentityManager; import io.undertow.server.HttpServerExchange; class SecurityActions { static SecurityContextImpl createSecurityContextImpl(final HttpServerExchange exchange, final AuthenticationMode authenticationMode, final IdentityManager identityManager) { if (System.getSecurityManager() == null) { return new SecurityContextImpl(exchange, authenticationMode, identityManager); } else { return AccessController.doPrivileged(new PrivilegedAction() { @Override public SecurityContextImpl run() { return new SecurityContextImpl(exchange, authenticationMode, identityManager); } }); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/SecurityContextFactoryImpl.java000066400000000000000000000040141420065311100333340ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.impl; import io.undertow.security.api.AuthenticationMode; import io.undertow.security.api.SecurityContext; import io.undertow.security.api.SecurityContextFactory; import io.undertow.security.idm.IdentityManager; import io.undertow.server.HttpServerExchange; /** *

* Default {@link io.undertow.security.api.SecurityContextFactory} implementation. It creates * {@link io.undertow.security.impl.SecurityContextImpl} instances with the specified parameters, setting the * programmatic mechanism name if it is not null. *

* * @author Stefan Guilhen */ public class SecurityContextFactoryImpl implements SecurityContextFactory { public static final SecurityContextFactory INSTANCE = new SecurityContextFactoryImpl(); private SecurityContextFactoryImpl() { } @Override public SecurityContext createSecurityContext(final HttpServerExchange exchange, final AuthenticationMode mode, final IdentityManager identityManager, final String programmaticMechName) { SecurityContextImpl securityContext = SecurityActions.createSecurityContextImpl(exchange, mode, identityManager); if (programmaticMechName != null) securityContext.setProgramaticMechName(programmaticMechName); return securityContext; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/SecurityContextImpl.java000066400000000000000000000347401420065311100320150ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.impl; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.AuthenticationMechanism.AuthenticationMechanismOutcome; import io.undertow.security.api.AuthenticationMechanism.ChallengeResult; import io.undertow.security.api.AuthenticationMechanismContext; import io.undertow.security.api.AuthenticationMode; import io.undertow.security.idm.Account; import io.undertow.security.idm.IdentityManager; import io.undertow.security.idm.PasswordCredential; import io.undertow.server.HttpServerExchange; import io.undertow.util.StatusCodes; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Collections; import java.util.LinkedList; import java.util.List; /** * The internal SecurityContext used to hold the state of security for the current exchange. * * @author Darran Lofthouse * @author Stuart Douglas */ public class SecurityContextImpl extends AbstractSecurityContext implements AuthenticationMechanismContext { private static final RuntimePermission PERMISSION = new RuntimePermission("MODIFY_UNDERTOW_SECURITY_CONTEXT"); private AuthenticationState authenticationState = AuthenticationState.NOT_ATTEMPTED; private final AuthenticationMode authenticationMode; private String programaticMechName = "Programatic"; /** * the authentication mechanisms. Note that in order to reduce the allocation of list and iterator structures * we use a custom linked list structure. */ private Node authMechanisms = null; private final IdentityManager identityManager; public SecurityContextImpl(final HttpServerExchange exchange, final IdentityManager identityManager) { this(exchange, AuthenticationMode.PRO_ACTIVE, identityManager); } public SecurityContextImpl(final HttpServerExchange exchange, final AuthenticationMode authenticationMode, final IdentityManager identityManager) { super(exchange); this.authenticationMode = authenticationMode; this.identityManager = identityManager; if (System.getSecurityManager() != null) { System.getSecurityManager().checkPermission(PERMISSION); } } /* * Authentication can be represented as being at one of many states with different transitions depending on desired outcome. * * NOT_ATTEMPTED * ATTEMPTED * AUTHENTICATED * CHALLENGED_SENT */ @Override public boolean authenticate() { UndertowLogger.SECURITY_LOGGER.debugf("Attempting to authenticate %s, authentication required: %s", exchange.getRequestPath(), isAuthenticationRequired()); if(authenticationState == AuthenticationState.ATTEMPTED || (authenticationState == AuthenticationState.CHALLENGE_SENT && !exchange.isResponseStarted())) { //we are re-attempted, so we just reset the state //see UNDERTOW-263 authenticationState = AuthenticationState.NOT_ATTEMPTED; } return !authTransition(); } private boolean authTransition() { if (authTransitionRequired()) { switch (authenticationState) { case NOT_ATTEMPTED: authenticationState = attemptAuthentication(); break; case ATTEMPTED: authenticationState = sendChallenges(); break; default: throw new IllegalStateException("It should not be possible to reach this."); } return authTransition(); } else { UndertowLogger.SECURITY_LOGGER.debugf("Authentication result was %s for %s", authenticationState, exchange.getRequestPath()); // Keep in mind this switch statement is only called after a call to authTransitionRequired. switch (authenticationState) { case NOT_ATTEMPTED: // No constraint was set that mandated authentication so not reason to hold up the request. case ATTEMPTED: // Attempted based on incoming request but no a failure so allow the request to proceed. case AUTHENTICATED: // Authentication was a success - no responses sent. return false; default: // Remaining option is CHALLENGE_SENT to request processing must end. return true; } } } private AuthenticationState attemptAuthentication() { return new AuthAttempter(authMechanisms,exchange).transition(); } private AuthenticationState sendChallenges() { UndertowLogger.SECURITY_LOGGER.debugf("Sending authentication challenge for %s", exchange); return new ChallengeSender(authMechanisms, exchange).transition(); } private boolean authTransitionRequired() { switch (authenticationState) { case NOT_ATTEMPTED: // There has been no attempt to authenticate the current request so do so either if required or if we are set to // be pro-active. return isAuthenticationRequired() || authenticationMode == AuthenticationMode.PRO_ACTIVE; case ATTEMPTED: // To be ATTEMPTED we know it was not AUTHENTICATED so if it is required we need to transition to send the // challenges. return isAuthenticationRequired(); default: // At this point the state would either be AUTHENTICATED or CHALLENGE_SENT - either of which mean no further // transitions applicable for this request. return false; } } /** * Set the name of the mechanism used for authentication to be reported if authentication was handled programatically. * * @param programaticMechName */ public void setProgramaticMechName(final String programaticMechName) { this.programaticMechName = programaticMechName; } @Override public void addAuthenticationMechanism(final AuthenticationMechanism handler) { // TODO - Do we want to change this so we can ensure the mechanisms are not modifiable mid request? if(authMechanisms == null) { authMechanisms = new Node<>(handler); } else { Node cur = authMechanisms; while (cur.next != null) { cur = cur.next; } cur.next = new Node<>(handler); } } @Override @Deprecated public List getAuthenticationMechanisms() { List ret = new LinkedList<>(); Node cur = authMechanisms; while (cur != null) { ret.add(cur.item); cur = cur.next; } return Collections.unmodifiableList(ret); } @Override @Deprecated public IdentityManager getIdentityManager() { return identityManager; } @Override public boolean login(final String username, final String password) { UndertowLogger.SECURITY_LOGGER.debugf("Attempting programatic login for user %s for request %s", username, exchange); final Account account; if(System.getSecurityManager() == null) { account = identityManager.verify(username, new PasswordCredential(password.toCharArray())); } else { account = AccessController.doPrivileged(new PrivilegedAction() { @Override public Account run() { return identityManager.verify(username, new PasswordCredential(password.toCharArray())); } }); } if (account == null) { return false; } authenticationComplete(account, programaticMechName, true); this.authenticationState = AuthenticationState.AUTHENTICATED; return true; } @Override public void logout() { Account authenticatedAccount = getAuthenticatedAccount(); if(authenticatedAccount != null) { UndertowLogger.SECURITY_LOGGER.debugf("Logging out user %s for %s", authenticatedAccount.getPrincipal().getName(), exchange); } else { UndertowLogger.SECURITY_LOGGER.debugf("Logout called with no authenticated user in exchange %s", exchange); } super.logout(); this.authenticationState = AuthenticationState.NOT_ATTEMPTED; } private class AuthAttempter { private Node currentMethod; private final HttpServerExchange exchange; private AuthAttempter(Node currentMethod, final HttpServerExchange exchange) { this.exchange = exchange; this.currentMethod = currentMethod; } private AuthenticationState transition() { if (currentMethod != null) { final AuthenticationMechanism mechanism = currentMethod.item; currentMethod = currentMethod.next; AuthenticationMechanismOutcome outcome = mechanism.authenticate(exchange, SecurityContextImpl.this); if(UndertowLogger.SECURITY_LOGGER.isDebugEnabled()) { UndertowLogger.SECURITY_LOGGER.debugf("Authentication outcome was %s with method %s for %s", outcome, mechanism, exchange.getRequestURI()); if(UndertowLogger.SECURITY_LOGGER.isTraceEnabled()) { UndertowLogger.SECURITY_LOGGER.tracef("Contents of exchange after authentication attempt is %s", exchange); } } if (outcome == null) { throw UndertowMessages.MESSAGES.authMechanismOutcomeNull(); } switch (outcome) { case AUTHENTICATED: // TODO - Should verify that the mechanism did register an authenticated Account. return AuthenticationState.AUTHENTICATED; case NOT_AUTHENTICATED: // A mechanism attempted to authenticate but could not complete, this now means that // authentication is required and challenges need to be sent. setAuthenticationRequired(); return AuthenticationState.ATTEMPTED; case NOT_ATTEMPTED: // Time to try the next mechanism. return transition(); default: throw new IllegalStateException(); } } else { // Reached the end of the mechanisms and no mechanism authenticated for us to reach this point. return AuthenticationState.ATTEMPTED; } } } /** * Class responsible for sending the authentication challenges. */ private class ChallengeSender { private Node currentMethod; private final HttpServerExchange exchange; private Integer chosenStatusCode = null; private boolean challengeSent = false; private ChallengeSender(Node currentMethod, final HttpServerExchange exchange) { this.exchange = exchange; this.currentMethod = currentMethod; } private AuthenticationState transition() { if (currentMethod != null) { final AuthenticationMechanism mechanism = currentMethod.item; currentMethod = currentMethod.next; ChallengeResult result = mechanism.sendChallenge(exchange, SecurityContextImpl.this); if(result == null) { throw UndertowMessages.MESSAGES.sendChallengeReturnedNull(mechanism); } if (result.isChallengeSent()) { challengeSent = true; Integer desiredCode = result.getDesiredResponseCode(); if (desiredCode != null && (chosenStatusCode == null || chosenStatusCode.equals(StatusCodes.OK))) { chosenStatusCode = desiredCode; if (chosenStatusCode.equals(StatusCodes.OK) == false) { if(!exchange.isResponseStarted()) { exchange.setStatusCode(chosenStatusCode); } } } } // We always transition so we can reach the end of the list and hit the else. return transition(); } else { if(!exchange.isResponseStarted()) { // Iterated all mechanisms, if OK it will not be set yet. if (chosenStatusCode == null) { if (challengeSent == false) { // No mechanism generated a challenge so send a 403 as our challenge - i.e. just rejecting the request. exchange.setStatusCode(StatusCodes.FORBIDDEN); } } else if (chosenStatusCode.equals(StatusCodes.OK)) { exchange.setStatusCode(chosenStatusCode); } } return AuthenticationState.CHALLENGE_SENT; } } } /** * Representation of the current authentication state of the SecurityContext. */ enum AuthenticationState { NOT_ATTEMPTED, ATTEMPTED, AUTHENTICATED, CHALLENGE_SENT; } /** * To reduce allocations we use a custom linked list data structure * @param */ private static final class Node { final T item; Node next; private Node(T item) { this.item = item; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/SimpleNonceManager.java000066400000000000000000000526541420065311100315320ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.impl; import static io.undertow.UndertowMessages.MESSAGES; import io.undertow.security.api.SessionNonceManager; import io.undertow.server.HttpServerExchange; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import org.xnio.XnioExecutor; import org.xnio.XnioExecutor.Key; import org.xnio.XnioIoThread; import io.undertow.util.FlexBase64; import io.undertow.util.WorkerUtils; /** * A default {@link io.undertow.security.api.NonceManager} implementation to provide reasonable single host management of nonces. * * This {@link io.undertow.security.api.NonceManager} manages nonces in two groups, the first is the group that are allocated to new requests, this group * is a problem as we want to be able to limit how many we distribute so we don't have a DOS storing too many but we also don't * a high number of requests to to push the other valid nonces out faster than they can be used. * * The second group is the set of nonces actively in use - these should be maintained as we can also maintain the nonce count * and even track the next nonce once invalid. * * Maybe group one should be a timestamp and private key hashed together, if used with a nonce count they move to be tracked to * ensure the same count is not used again - if successfully used without a nonce count add to a blacklist until expiration? A * nonce used without a nonce count will essentially be single use with each request getting a new nonce. * * @author Darran Lofthouse */ public class SimpleNonceManager implements SessionNonceManager { private static final String DEFAULT_HASH_ALG = "MD5"; /** * List of invalid nonces, this list contains the nonces that have been used without a nonce count. * * In that situation they are considered single use and must not be used again. */ private final Set invalidNonces = Collections.synchronizedSet(new HashSet()); /** * Map of known currently valid nonces, a SortedMap is used to order the nonces by their creation time stamp allowing a * simple iteration over the keys to identify expired nonces. */ private final Map knownNonces = Collections.synchronizedMap(new HashMap()); /** * A WeakHashMap to map expired nonces to their replacement nonce. For an item to be added to this Collection the value will * have been removed from the knownNonces map. * * A replacement nonce will have been added to knownNonces that references the key used here - once the replacement nonce is * removed from knownNonces then the key will be eligible for garbage collection allowing it to be removed from this map as * well. * * The value in this Map is a plain String, this is to avoid inadvertently creating a long term reference to the key we * expect to be garbage collected at some point in the future. */ private final Map forwardMapping = Collections.synchronizedMap(new WeakHashMap()); private final String secret; private final String hashAlg; private final int hashLength; /** * After a nonce is issued the first authentication response MUST be received within 5 minutes. */ private static final long firstUseTimeOut = 5 * 60 * 1000; /** * Overall a nonce is valid from 15 minutes from first being issued, if used after this then a new nonce will be issued. */ private static final long overallTimeOut = 15 * 60 * 1000; /** * A previously used nonce will be allowed to remain in the knownNonces list for up to 5 minutes. * * The nonce will be accepted during this 5 minute window but will immediately be replaced causing any additional requests * to be forced to use the new nonce. * * This is primarily for session based digests where loosing the cached session key would be bad. */ private static final long cacheTimePostExpiry = 5 * 60 * 1000; public SimpleNonceManager() { this(DEFAULT_HASH_ALG); } public SimpleNonceManager(final String hashAlg) { // Verify it is a valid algorithm (at least for now) MessageDigest digest = getDigest(hashAlg); this.hashAlg = hashAlg; this.hashLength = digest.getDigestLength(); // Create a new secret only valid within this NonceManager instance. Random rand = new SecureRandom(); byte[] secretBytes = new byte[32]; rand.nextBytes(secretBytes); secret = FlexBase64.encodeString(digest.digest(secretBytes), false); } private MessageDigest getDigest(final String hashAlg) { try { return MessageDigest.getInstance(hashAlg); } catch (NoSuchAlgorithmException e) { throw MESSAGES.hashAlgorithmNotFound(hashAlg); } } /** * * @see io.undertow.security.api.NonceManager#nextNonce(java.lang.String, io.undertow.server.HttpServerExchange) */ public String nextNonce(String lastNonce, HttpServerExchange exchange) { if (lastNonce == null) { return createNewNonceString(); } if (invalidNonces.contains(lastNonce)) { // The nonce supplied has already been used. return createNewNonceString(); } String nonce = lastNonce; // Loop the forward mappings. synchronized (forwardMapping) { NonceHolder holder = new NonceHolder(lastNonce); while (forwardMapping.containsKey(holder)) { nonce = forwardMapping.get(holder); // The final NonceHolder will then be used if a forwardMapping needs to be set. holder = new NonceHolder(nonce); } synchronized (knownNonces) { Nonce value = knownNonces.get(nonce); if (value == null) { // Not a likely scenario but if this occurs then most likely the nonce mapped to has also expired so we will // just send a new nonce. nonce = createNewNonceString(); } else { long now = System.currentTimeMillis(); // The cacheTimePostExpiry is not included here as this is our opportunity to inform the client to use a // replacement nonce without a stale round trip. long earliestAccepted = now - firstUseTimeOut; if (value.timeStamp < earliestAccepted || value.timeStamp > now) { Nonce replacement = createNewNonce(holder); if (value.executorKey != null) { // The outcome doesn't matter - if we have the value we have all we need. value.executorKey.remove(); } nonce = replacement.nonce; // Create a record of the forward mapping so if any requests do need to be marked stale they can be // pointed towards the correct nonce to use. forwardMapping.put(holder, nonce); // Bring over any existing session key. replacement.setSessionKey(value.getSessionKey()); // At this point we will not accept the nonce again so remove it from the list of known nonces but do // register the replacement. knownNonces.remove(holder.nonce); // There are two reasons for registering the replacement 1 - to preserve any session key, 2 - To keep a // reference to the now invalid key so it // can be used as a key in a weak hash map. knownNonces.put(nonce, replacement); earliestAccepted = now - (overallTimeOut + cacheTimePostExpiry); long timeTillExpiry = replacement.timeStamp - earliestAccepted; replacement.executorKey = WorkerUtils.executeAfter(exchange.getIoThread(), new KnownNonceCleaner(nonce), timeTillExpiry, TimeUnit.MILLISECONDS); } } } } return nonce; } private String createNewNonceString() { return createNewNonce(null).nonce; } private Nonce createNewNonce(NonceHolder previousNonce) { byte[] prefix = new byte[8]; // A pseudo-random generator for creating the nonces, a secure random is not required here as this is used purely to // minimise the chance of collisions should two nonces be generated at exactly the same time. ThreadLocalRandom.current().nextBytes(prefix); long timeStamp = System.currentTimeMillis(); byte[] now = Long.toString(timeStamp).getBytes(StandardCharsets.UTF_8); String nonce = createNonce(prefix, now); return new Nonce(nonce, timeStamp, previousNonce); } /** * * @see io.undertow.security.api.NonceManager#validateNonce(java.lang.String, int, io.undertow.server.HttpServerExchange) */ @Override public boolean validateNonce(String nonce, int nonceCount, HttpServerExchange exchange) { if (nonceCount < 0) { if (invalidNonces.contains(nonce)) { // Without a nonce count the nonce is only usable once. return false; } // Not already known so will drop into first use validation. } else if (knownNonces.containsKey(nonce)) { // At this point we need to validate that the nonce is still within it's time limits, // If a new nonce had been selected then a known nonce would not have been found. // The nonce will also have it's nonce count checked. return validateNonceWithCount(new Nonce(nonce), nonceCount, exchange.getIoThread()); } else if (forwardMapping.containsKey(new NonceHolder(nonce))) { // We could have let this drop through as the next validation would fail anyway but // why waste the time if we already know a replacement nonce has been issued. return false; } // This is not a nonce currently known to us so start the validation process. Nonce value = verifyUnknownNonce(nonce, nonceCount); if (value == null) { return false; } long now = System.currentTimeMillis(); // NOTE - This check is for the first use, overall validity is checked in validateNonceWithCount. long earliestAccepted = now - firstUseTimeOut; if (value.timeStamp < earliestAccepted || value.timeStamp > now) { // The embedded timestamp is either expired or somehow is after now. return false; } if (nonceCount < 0) { // Allow a single use but reject all further uses. return addInvalidNonce(value, exchange.getIoThread()); } else { return validateNonceWithCount(value, nonceCount, exchange.getIoThread()); } } private boolean validateNonceWithCount(Nonce nonce, int nonceCount, final XnioIoThread executor) { // This point could have been reached either because the knownNonces map contained the key or because // it didn't and a count was supplied - either way need to double check the contents of knownNonces once // the lock is in place. synchronized (knownNonces) { Nonce value = knownNonces.get(nonce.nonce); long now = System.currentTimeMillis(); // For the purpose of this validation we also add the cacheTimePostExpiry - when nextNonce is subsequently // called it will decide if we are in the interval to replace the nonce. long earliestAccepted = now - (overallTimeOut + cacheTimePostExpiry); if (value == null) { if (nonce.timeStamp < 0) { // Means it was in there, now it isn't - most likely a timestamp expiration mid check - abandon validation. return false; } if (nonce.timeStamp > earliestAccepted && nonce.timeStamp <= now) { knownNonces.put(nonce.nonce, nonce); long timeTillExpiry = nonce.timeStamp - earliestAccepted; nonce.executorKey = WorkerUtils.executeAfter(executor, new KnownNonceCleaner(nonce.nonce), timeTillExpiry, TimeUnit.MILLISECONDS); return true; } return false; } else { // We have it, just need to verify that it has not expired and that the nonce key is valid. if (value.timeStamp < earliestAccepted || value.timeStamp > now) { // The embedded timestamp is either expired or somehow is after now!! return false; } if (value.getMaxNonceCount() < nonceCount) { value.setMaxNonceCount(nonceCount); return true; } return false; } } } private boolean addInvalidNonce(final Nonce nonce, final XnioExecutor executor) { long now = System.currentTimeMillis(); long invalidBefore = now - firstUseTimeOut; long timeTillInvalid = nonce.timeStamp - invalidBefore; if (timeTillInvalid > 0) { if (invalidNonces.add(nonce.nonce)) { executor.executeAfter(new InvalidNonceCleaner(nonce.nonce), timeTillInvalid, TimeUnit.MILLISECONDS); return true; } else { return false; } } else { // So close to expiring any record of this nonce being used could have been cleared so // don't take a chance and just say no. return false; } } /** * Verify a previously unknown nonce and return the {@link NonceKey} representation for the nonce. * * Later when a nonce is re-used we can match based on the String alone - the information embedded within the nonce will be * cached with it. * * This stage of the verification simply extracts the prefix and the embedded timestamp and recreates a new hashed and * Base64 nonce based on the local secret - if the newly generated nonce matches the supplied one we accept it was created * by this nonce manager. * * This verification does not validate that the timestamp is within a valid time period. * * @param nonce - * @return */ private Nonce verifyUnknownNonce(final String nonce, final int nonceCount) { byte[] complete; int offset; int length; try { ByteBuffer decode = FlexBase64.decode(nonce); complete = decode.array(); offset = decode.arrayOffset(); length = decode.limit() - offset; } catch (IOException e) { throw MESSAGES.invalidBase64Token(e); } int timeStampLength = complete[offset + 8]; // A sanity check to try and verify the sizes we expect from the arrays are correct. if (hashLength > 0) { int expectedLength = 9 + timeStampLength + hashLength; if (length != expectedLength) { throw MESSAGES.invalidNonceReceived(); } else if (timeStampLength + 1 >= length) throw MESSAGES.invalidNonceReceived(); } byte[] prefix = new byte[8]; System.arraycopy(complete, offset, prefix, 0, 8); byte[] timeStampBytes = new byte[timeStampLength]; System.arraycopy(complete, offset + 9, timeStampBytes, 0, timeStampBytes.length); String expectedNonce = createNonce(prefix, timeStampBytes); if (expectedNonce.equals(nonce)) { try { long timeStamp = Long.parseLong(new String(timeStampBytes, StandardCharsets.UTF_8)); return new Nonce(expectedNonce, timeStamp, nonceCount); } catch (NumberFormatException dropped) { } } return null; } private String createNonce(final byte[] prefix, final byte[] timeStamp) { byte[] hashedPart = generateHash(prefix, timeStamp); byte[] complete = new byte[9 + timeStamp.length + hashedPart.length]; System.arraycopy(prefix, 0, complete, 0, 8); complete[8] = (byte) timeStamp.length; System.arraycopy(timeStamp, 0, complete, 9, timeStamp.length); System.arraycopy(hashedPart, 0, complete, 9 + timeStamp.length, hashedPart.length); return FlexBase64.encodeString(complete, false); } private byte[] generateHash(final byte[] prefix, final byte[] timeStamp) { MessageDigest digest = getDigest(hashAlg); digest.update(prefix); digest.update(timeStamp); return digest.digest(secret.getBytes(StandardCharsets.UTF_8)); } public void associateHash(String nonce, byte[] hash) { // TODO Auto-generated method stub } public byte[] lookupHash(String nonce) { // TODO Auto-generated method stub return null; } /** * A simple wrapper around a nonce to allow it to be used as a key in a weak map. */ private static class NonceHolder { private final String nonce; private NonceHolder(final String nonce) { if (nonce == null) { throw new NullPointerException("nonce must not be null."); } this.nonce = nonce; } @Override public int hashCode() { return nonce.hashCode(); } @Override public boolean equals(Object obj) { return (obj instanceof NonceHolder) ? nonce.equals(((NonceHolder) obj).nonce) : false; } } /** * The state associated with a nonce. * * A NonceKey for a preciously valid nonce is also referenced, this is so that a WeakHashMap can be used to maintain a * mapping from the original NonceKey to the new nonce value. */ private static class Nonce { private final String nonce; private final long timeStamp; // TODO we will also add a mechanism to track the gaps as the only restriction is that a NC can only be used one. private int maxNonceCount; // We keep this as it is used in the weak hash map as a forward mapping as long as the nonce to map to is still alive. @SuppressWarnings("unused") private final NonceHolder previousNonce; private byte[] sessionKey; private Key executorKey; private Nonce(final String nonce) { this(nonce, -1, -1); } private Nonce(final String nonce, final long timeStamp, final int initialNC) { this(nonce, timeStamp, initialNC, null); } private Nonce(final String nonce, final long timeStamp, final NonceHolder previousNonce) { this(nonce, timeStamp, -1, previousNonce); } private Nonce(final String nonce, final long timeStamp, final int initialNC, final NonceHolder previousNonce) { this.nonce = nonce; this.timeStamp = timeStamp; this.maxNonceCount = initialNC; this.previousNonce = previousNonce; } byte[] getSessionKey() { return sessionKey; } void setSessionKey(final byte[] sessionKey) { this.sessionKey = sessionKey; } int getMaxNonceCount() { return maxNonceCount; } void setMaxNonceCount(int maxNonceCount) { this.maxNonceCount = maxNonceCount; } } private class InvalidNonceCleaner implements Runnable { private final String nonce; private InvalidNonceCleaner(final String nonce) { if (nonce == null) { throw new NullPointerException("nonce must not be null."); } this.nonce = nonce; } public void run() { invalidNonces.remove(nonce); } } private class KnownNonceCleaner implements Runnable { private final String nonce; private KnownNonceCleaner(final String nonce) { if (nonce == null) { throw new NullPointerException("nonce must not be null."); } this.nonce = nonce; } public void run() { knownNonces.remove(nonce); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/SingleSignOn.java000066400000000000000000000046211420065311100303510ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.impl; import io.undertow.security.idm.Account; import io.undertow.server.session.Session; import io.undertow.server.session.SessionManager; /** * @author Stuart Douglas * @author Paul Ferraro */ public interface SingleSignOn extends Iterable, AutoCloseable { /** * Returns the unique identifier for this SSO. * @return this SSO's unique identifier */ String getId(); /** * Returns the account associated with this SSO. * @return an account */ Account getAccount(); /** * Returns the authentication mechanism used to create the account associated with this SSO. * @return an authentication mechanism */ String getMechanismName(); /** * Indicates whether or not the specified session is contained in the set of sessions to which the user is authenticated * @param session a session manager * @return */ boolean contains(Session session); /** * Adds the specified session to the set of sessions to which the user is authenticated * @param session a session manager */ void add(Session session); /** * Removes the specified session from the set of sessions to which the user is authenticated * @param session a session manager */ void remove(Session session); /** * Returns the session associated with the deployment of the specified session manager * @param manager a session manager * @return a session */ Session getSession(SessionManager manager); /** * Releases any resources acquired by this object. * Must be called after this object is no longer in use. */ @Override void close(); } SingleSignOnAuthenticationMechanism.java000066400000000000000000000256561420065311100350320ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.impl; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.NotificationReceiver; import io.undertow.security.api.SecurityContext; import io.undertow.security.api.SecurityNotification; import io.undertow.security.idm.Account; import io.undertow.security.idm.IdentityManager; import io.undertow.server.ConduitWrapper; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.Cookie; import io.undertow.server.handlers.CookieImpl; import io.undertow.server.session.Session; import io.undertow.server.session.SessionListener; import io.undertow.server.session.SessionManager; import io.undertow.util.ConduitFactory; import io.undertow.util.Sessions; import org.jboss.logging.Logger; import org.xnio.conduits.StreamSinkConduit; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.WeakHashMap; /** * Authenticator that can be used to configure single sign on. * * @author Stuart Douglas * @author Paul Ferraro * @author Richard Opalka */ public class SingleSignOnAuthenticationMechanism implements AuthenticationMechanism { private static final Logger log = Logger.getLogger(SingleSignOnAuthenticationMechanism.class); private static final String SSO_SESSION_ATTRIBUTE = SingleSignOnAuthenticationMechanism.class.getName() + ".SSOID"; // Use weak references to prevent memory leaks following undeployment private final Set seenSessionManagers = Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap())); private String cookieName = "JSESSIONIDSSO"; private boolean httpOnly; private boolean secure; private String domain; private String path; private final SessionInvalidationListener listener = new SessionInvalidationListener(); private final ResponseListener responseListener = new ResponseListener(); private final SingleSignOnManager singleSignOnManager; private final IdentityManager identityManager; public SingleSignOnAuthenticationMechanism(SingleSignOnManager storage) { this(storage, null); } public SingleSignOnAuthenticationMechanism(SingleSignOnManager storage, IdentityManager identityManager) { this.singleSignOnManager = storage; this.identityManager = identityManager; } @SuppressWarnings("deprecation") private IdentityManager getIdentityManager(SecurityContext securityContext) { return identityManager != null ? identityManager : securityContext.getIdentityManager(); } @Override public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) { Cookie cookie = null; for (Cookie c : exchange.requestCookies()) { if (cookieName.equals(c.getName())) { cookie = c; } } if (cookie != null) { final String ssoId = cookie.getValue(); log.tracef("Found SSO cookie %s", ssoId); try (SingleSignOn sso = this.singleSignOnManager.findSingleSignOn(ssoId)) { if (sso != null) { if(log.isTraceEnabled()) { log.tracef("SSO session with ID: %s found.", ssoId); } Account verified = getIdentityManager(securityContext).verify(sso.getAccount()); if (verified == null) { if(log.isTraceEnabled()) { log.tracef("Account not found. Returning 'not attempted' here."); } //we return not attempted here to allow other mechanisms to proceed as normal return AuthenticationMechanismOutcome.NOT_ATTEMPTED; } final Session session = getSession(exchange); registerSessionIfRequired(sso, session); securityContext.authenticationComplete(verified, sso.getMechanismName(), false); securityContext.registerNotificationReceiver(new NotificationReceiver() { @Override public void handleNotification(SecurityNotification notification) { if (notification.getEventType() == SecurityNotification.EventType.LOGGED_OUT) { singleSignOnManager.removeSingleSignOn(sso); } } }); log.tracef("Authenticated account %s using SSO", verified.getPrincipal().getName()); return AuthenticationMechanismOutcome.AUTHENTICATED; } } clearSsoCookie(exchange); } exchange.addResponseWrapper(responseListener); return AuthenticationMechanismOutcome.NOT_ATTEMPTED; } private void registerSessionIfRequired(SingleSignOn sso, Session session) { if (!sso.contains(session)) { if(log.isTraceEnabled()) { log.tracef("Session %s added to SSO %s", session.getId(), sso.getId()); } sso.add(session); } if(session.getAttribute(SSO_SESSION_ATTRIBUTE) == null) { if(log.isTraceEnabled()) { log.tracef("SSO_SESSION_ATTRIBUTE not found. Creating it with SSO ID %s as value.", sso.getId()); } session.setAttribute(SSO_SESSION_ATTRIBUTE, sso.getId()); } SessionManager manager = session.getSessionManager(); if (seenSessionManagers.add(manager)) { manager.registerSessionListener(listener); } } private void clearSsoCookie(HttpServerExchange exchange) { exchange.setResponseCookie(new CookieImpl(cookieName).setMaxAge(0).setHttpOnly(httpOnly).setSecure(secure).setDomain(domain).setPath(path)); } @Override public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) { return ChallengeResult.NOT_SENT; } protected Session getSession(final HttpServerExchange exchange) { return Sessions.getOrCreateSession(exchange); } final class ResponseListener implements ConduitWrapper { @Override public StreamSinkConduit wrap(ConduitFactory factory, HttpServerExchange exchange) { SecurityContext sc = exchange.getSecurityContext(); Account account = sc.getAuthenticatedAccount(); if (account != null) { try (SingleSignOn sso = singleSignOnManager.createSingleSignOn(account, sc.getMechanismName())) { Session session = getSession(exchange); registerSessionIfRequired(sso, session); exchange.setResponseCookie(new CookieImpl(cookieName, sso.getId()).setHttpOnly(httpOnly).setSecure(secure).setDomain(domain).setPath(path)); } } return factory.create(); } } final class SessionInvalidationListener implements SessionListener { @Override public void sessionCreated(Session session, HttpServerExchange exchange) { } @Override public void sessionDestroyed(Session session, HttpServerExchange exchange, SessionDestroyedReason reason) { String ssoId = (String) session.getAttribute(SSO_SESSION_ATTRIBUTE); if (ssoId != null) { if(log.isTraceEnabled()) { log.tracef("Removing SSO ID %s from destroyed session %s.", ssoId, session.getId()); } List sessionsToRemove = new LinkedList<>(); try (SingleSignOn sso = singleSignOnManager.findSingleSignOn(ssoId)) { if (sso != null) { sso.remove(session); if (reason == SessionDestroyedReason.INVALIDATED) { for (Session associatedSession : sso) { sso.remove(associatedSession); sessionsToRemove.add(associatedSession); } } // If there are no more associated sessions, remove the SSO altogether if (!sso.iterator().hasNext()) { singleSignOnManager.removeSingleSignOn(sso); } } } // Any consequential session invalidations will trigger this listener recursively, // so make sure we don't attempt to invalidate session until after the sso is removed. for (Session sessionToRemove : sessionsToRemove) { sessionToRemove.invalidate(null); } } } @Override public void attributeAdded(Session session, String name, Object value) { } @Override public void attributeUpdated(Session session, String name, Object newValue, Object oldValue) { } @Override public void attributeRemoved(Session session, String name, Object oldValue) { } @Override public void sessionIdChanged(Session session, String oldSessionId) { } } public String getCookieName() { return cookieName; } public SingleSignOnAuthenticationMechanism setCookieName(String cookieName) { this.cookieName = cookieName; return this; } public boolean isHttpOnly() { return httpOnly; } public SingleSignOnAuthenticationMechanism setHttpOnly(boolean httpOnly) { this.httpOnly = httpOnly; return this; } public boolean isSecure() { return secure; } public SingleSignOnAuthenticationMechanism setSecure(boolean secure) { this.secure = secure; return this; } public String getDomain() { return domain; } public SingleSignOnAuthenticationMechanism setDomain(String domain) { this.domain = domain; return this; } public String getPath() { return path; } public SingleSignOnAuthenticationMechanism setPath(String path) { this.path = path; return this; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/security/impl/SingleSignOnManager.java000066400000000000000000000020121420065311100316340ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.security.impl; import io.undertow.security.idm.Account; /** * @author Paul Ferraro */ public interface SingleSignOnManager { SingleSignOn createSingleSignOn(Account account, String mechanism); SingleSignOn findSingleSignOn(String ssoId); void removeSingleSignOn(SingleSignOn sso); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/000077500000000000000000000000001420065311100236425ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/AbstractServerConnection.java000066400000000000000000000232561420065311100314670ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.Option; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import org.xnio.Pool; import org.xnio.StreamConnection; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.conduits.ConduitStreamSinkChannel; import org.xnio.conduits.ConduitStreamSourceChannel; import org.xnio.conduits.StreamSinkConduit; import org.xnio.conduits.StreamSourceConduit; import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.util.LinkedList; import java.util.List; public abstract class AbstractServerConnection extends ServerConnection { protected final StreamConnection channel; protected final CloseSetter closeSetter; protected final ByteBufferPool bufferPool; protected final HttpHandler rootHandler; protected final OptionMap undertowOptions; protected final StreamSourceConduit originalSourceConduit; protected final StreamSinkConduit originalSinkConduit; protected final List closeListeners = new LinkedList<>(); protected HttpServerExchange current; private final int bufferSize; private XnioBufferPoolAdaptor poolAdaptor; /** * Any extra bytes that were read from the channel. This could be data for this requests, or the next response. */ protected PooledByteBuffer extraBytes; public AbstractServerConnection(StreamConnection channel, final ByteBufferPool bufferPool, final HttpHandler rootHandler, final OptionMap undertowOptions, final int bufferSize) { this.channel = channel; this.bufferPool = bufferPool; this.rootHandler = rootHandler; this.undertowOptions = undertowOptions; this.bufferSize = bufferSize; closeSetter = new CloseSetter(); if (channel != null) { this.originalSinkConduit = channel.getSinkChannel().getConduit(); this.originalSourceConduit = channel.getSourceChannel().getConduit(); channel.setCloseListener(closeSetter); } else { this.originalSinkConduit = null; this.originalSourceConduit = null; } } @Override public Pool getBufferPool() { if(poolAdaptor == null) { poolAdaptor = new XnioBufferPoolAdaptor(getByteBufferPool()); } return poolAdaptor; } /** * Get the root HTTP handler for this connection. * * @return the root HTTP handler for this connection */ public HttpHandler getRootHandler() { return rootHandler; } /** * Get the buffer pool for this connection. * * @return the buffer pool for this connection */ @Override public ByteBufferPool getByteBufferPool() { return bufferPool; } /** * Get the underlying channel. * * @return the underlying channel */ public StreamConnection getChannel() { return channel; } @Override public ChannelListener.Setter getCloseSetter() { return closeSetter; } @Override public XnioWorker getWorker() { return channel.getWorker(); } @Override public XnioIoThread getIoThread() { if(channel == null) { return null; } return channel.getIoThread(); } @Override public boolean isOpen() { return channel.isOpen(); } @Override public boolean supportsOption(final Option option) { return channel.supportsOption(option); } @Override public T getOption(final Option option) throws IOException { return channel.getOption(option); } @Override public T setOption(final Option option, final T value) throws IllegalArgumentException, IOException { return channel.setOption(option, value); } @Override public void close() throws IOException { channel.close(); } @Override public SocketAddress getPeerAddress() { return channel.getPeerAddress(); } @Override public A getPeerAddress(final Class type) { return channel.getPeerAddress(type); } @Override public SocketAddress getLocalAddress() { return channel.getLocalAddress(); } @Override public A getLocalAddress(final Class type) { return channel.getLocalAddress(type); } @Override public OptionMap getUndertowOptions() { return undertowOptions; } /** * @return The size of the buffers allocated by the buffer pool */ @Override public int getBufferSize() { return bufferSize; } public PooledByteBuffer getExtraBytes() { if(extraBytes != null && !extraBytes.getBuffer().hasRemaining()) { extraBytes.close(); extraBytes = null; return null; } return extraBytes; } public void setExtraBytes(final PooledByteBuffer extraBytes) { this.extraBytes = extraBytes; } /** * @return The original source conduit */ public StreamSourceConduit getOriginalSourceConduit() { return originalSourceConduit; } /** * @return The original underlying sink conduit */ public StreamSinkConduit getOriginalSinkConduit() { return originalSinkConduit; } /** * Resets the channel to its original state, effectively disabling all current conduit * wrappers. The current state is encapsulated inside a {@link ConduitState} object that * can be used the restore the channel. * * @return An opaque representation of the previous channel state */ public ConduitState resetChannel() { ConduitState ret = new ConduitState(channel.getSinkChannel().getConduit(), channel.getSourceChannel().getConduit()); channel.getSinkChannel().setConduit(originalSinkConduit); channel.getSourceChannel().setConduit(originalSourceConduit); return ret; } /** * Resets the channel to its original state, effectively disabling all current conduit * wrappers. The current state is lost. */ public void clearChannel() { channel.getSinkChannel().setConduit(originalSinkConduit); channel.getSourceChannel().setConduit(originalSourceConduit); } /** * Restores the channel conduits to a previous state. * * @param state The original state * @see #resetChannel() */ public void restoreChannel(final ConduitState state) { channel.getSinkChannel().setConduit(state.sink); channel.getSourceChannel().setConduit(state.source); } public static class ConduitState { final StreamSinkConduit sink; final StreamSourceConduit source; private ConduitState(final StreamSinkConduit sink, final StreamSourceConduit source) { this.sink = sink; this.source = source; } } protected static StreamSinkConduit sink(ConduitState state) { return state.sink; } protected static StreamSourceConduit source(ConduitState state) { return state.source; } @Override public void addCloseListener(CloseListener listener) { this.closeListeners.add(listener); } @Override protected ConduitStreamSinkChannel getSinkChannel() { return channel.getSinkChannel(); } @Override protected ConduitStreamSourceChannel getSourceChannel() { return channel.getSourceChannel(); } protected void setUpgradeListener(HttpUpgradeListener upgradeListener) { throw UndertowMessages.MESSAGES.upgradeNotSupported(); } @Override protected void maxEntitySizeUpdated(HttpServerExchange exchange) { } private class CloseSetter implements ChannelListener.Setter, ChannelListener { private ChannelListener listener; @Override public void set(ChannelListener listener) { this.listener = listener; } @Override public void handleEvent(StreamConnection channel) { try { for (CloseListener l : closeListeners) { try { l.closed(AbstractServerConnection.this); } catch (Throwable e) { UndertowLogger.REQUEST_LOGGER.exceptionInvokingCloseListener(l, e); } } if (current != null) { current.endExchange(); } ChannelListeners.invokeChannelListener(AbstractServerConnection.this, listener); } finally { if(extraBytes != null) { extraBytes.close(); extraBytes = null; } } } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/AggregateConnectorStatistics.java000066400000000000000000000066341420065311100323320ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; /** * @author Stuart Douglas */ public class AggregateConnectorStatistics implements ConnectorStatistics { private final ConnectorStatistics[] connectorStatistics; public AggregateConnectorStatistics(ConnectorStatistics[] connectorStatistics) { this.connectorStatistics = connectorStatistics; } @Override public long getRequestCount() { long count = 0; for(ConnectorStatistics c : connectorStatistics) { count += c.getRequestCount(); } return count; } @Override public long getBytesSent() { long count = 0; for(ConnectorStatistics c : connectorStatistics) { count += c.getBytesSent(); } return count; } @Override public long getBytesReceived() { long count = 0; for(ConnectorStatistics c : connectorStatistics) { count += c.getBytesReceived(); } return count; } @Override public long getErrorCount() { long count = 0; for(ConnectorStatistics c : connectorStatistics) { count += c.getErrorCount(); } return count; } @Override public long getProcessingTime() { long count = 0; for(ConnectorStatistics c : connectorStatistics) { count += c.getProcessingTime(); } return count; } @Override public long getMaxProcessingTime() { long max = 0; for(ConnectorStatistics c : connectorStatistics) { max = Math.max(c.getMaxProcessingTime(), max); } return max; } @Override public void reset() { for(ConnectorStatistics c : connectorStatistics) { c.reset(); } } @Override public long getActiveConnections() { long count = 0; for(ConnectorStatistics c : connectorStatistics) { count += c.getActiveConnections(); } return count; } @Override public long getMaxActiveConnections() { long count = 0; for(ConnectorStatistics c : connectorStatistics) { count += c.getMaxActiveConnections(); //not 100% accurate, but the best we can do } return count; } @Override public long getActiveRequests() { long count = 0; for(ConnectorStatistics c : connectorStatistics) { count += c.getActiveRequests(); } return count; } @Override public long getMaxActiveRequests() { long count = 0; for(ConnectorStatistics c : connectorStatistics) { count += c.getMaxActiveRequests(); //not 100% accurate, but the best we can do } return count; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/BasicSSLSessionInfo.java000066400000000000000000000163531420065311100303000ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import io.undertow.UndertowMessages; import io.undertow.util.HexConverter; import org.xnio.SslClientAuthMode; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.security.cert.CertificateException; import javax.security.cert.X509Certificate; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Collection; /** * Basic SSL session information. This information is generally provided by a front end proxy. * * @author Stuart Douglas */ public class BasicSSLSessionInfo implements SSLSessionInfo { private final byte[] sessionId; private final String cypherSuite; private final java.security.cert.Certificate[] peerCertificate; private final X509Certificate[] certificate; private final Integer keySize; /** * * @param sessionId The SSL session ID * @param cypherSuite The cypher suite name * @param certificate A string representation of the client certificate * @param keySize The key-size used by the cypher * @throws java.security.cert.CertificateException If the client cert could not be decoded * @throws CertificateException If the client cert could not be decoded */ public BasicSSLSessionInfo(byte[] sessionId, String cypherSuite, String certificate, Integer keySize) throws java.security.cert.CertificateException, CertificateException { this.sessionId = sessionId; this.cypherSuite = cypherSuite; this.keySize = keySize; if (certificate != null) { java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509"); byte[] certificateBytes = certificate.getBytes(StandardCharsets.US_ASCII); ByteArrayInputStream stream = new ByteArrayInputStream(certificateBytes); Collection certCol = cf.generateCertificates(stream); this.peerCertificate = new java.security.cert.Certificate[certCol.size()]; X509Certificate[] legacyCertificate = new X509Certificate[certCol.size()]; int i=0; for(java.security.cert.Certificate cert : certCol) { this.peerCertificate[i] = cert; if (legacyCertificate != null) { try { legacyCertificate[i] = X509Certificate.getInstance(cert.getEncoded()); } catch (CertificateException ce) { // [UNDERTOW-1969] We don't care about deprecated JDK methods failure caused by the fact newer JDKs // doesn't support them anymore. "this.certificate" is used only by deprecated method // {@link SSLSessionInfo.getPeerCertificateChain()} which call should be avoided by API users. legacyCertificate = null; } } i++; } this.certificate = legacyCertificate; } else { this.peerCertificate = null; this.certificate = null; } } /** * * @param sessionId The SSL session ID * @param cypherSuite The cypher suite name * @param certificate A string representation of the client certificate * @throws java.security.cert.CertificateException If the client cert could not be decoded * @throws CertificateException If the client cert could not be decoded */ public BasicSSLSessionInfo(byte[] sessionId, String cypherSuite, String certificate) throws java.security.cert.CertificateException, CertificateException { this(sessionId, cypherSuite, certificate, null); } /** * * @param sessionId The encoded SSL session ID * @param cypherSuite The cypher suite name * @param certificate A string representation of the client certificate * @throws java.security.cert.CertificateException If the client cert could not be decoded * @throws CertificateException If the client cert could not be decoded */ public BasicSSLSessionInfo(String sessionId, String cypherSuite, String certificate) throws java.security.cert.CertificateException, CertificateException { this(sessionId == null ? null : fromHex(sessionId), cypherSuite, certificate, null); } /** * * @param sessionId The encoded SSL session ID * @param cypherSuite The cypher suite name * @param certificate A string representation of the client certificate * @param keySize The key-size used by the cypher * @throws java.security.cert.CertificateException If the client cert could not be decoded * @throws CertificateException If the client cert could not be decoded */ public BasicSSLSessionInfo(String sessionId, String cypherSuite, String certificate, Integer keySize) throws java.security.cert.CertificateException, CertificateException { this(sessionId == null ? null : fromHex(sessionId), cypherSuite, certificate, keySize); } @Override public byte[] getSessionId() { if(sessionId == null) { return null; } final byte[] copy = new byte[sessionId.length]; System.arraycopy(sessionId, 0, copy, 0, copy.length); return copy; } @Override public String getCipherSuite() { return cypherSuite; } @Override public int getKeySize() { if (keySize != null) { return keySize; } else { return SSLSessionInfo.super.getKeySize(); } } @Override public java.security.cert.Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { if (peerCertificate == null) { throw UndertowMessages.MESSAGES.peerUnverified(); } return peerCertificate; } @Override public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { if (certificate == null) { throw UndertowMessages.MESSAGES.peerUnverified(); } return certificate; } @Override public void renegotiate(HttpServerExchange exchange, SslClientAuthMode sslClientAuthMode) throws IOException { throw UndertowMessages.MESSAGES.renegotiationNotSupported(); } @Override public SSLSession getSSLSession() { return null; } private static byte[] fromHex(String sessionId) { try { return HexConverter.convertFromHex(sessionId); } catch (Exception e) { //can happen if the session id is invalid return null; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/BlockingHttpExchange.java000066400000000000000000000036261420065311100305470ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import io.undertow.io.Receiver; import io.undertow.io.Sender; /** * An interface that provides the input and output streams for blocking HTTP requests. * * @author Stuart Douglas */ public interface BlockingHttpExchange extends Closeable { /** * Returns the input stream that is in use for this exchange. * * @return The input stream */ InputStream getInputStream(); /** * Returns the output stream that is in use for this exchange. * * In some circumstances this may not be available, such as if a writer * is being used for a servlet response * * @return The output stream */ OutputStream getOutputStream(); /** * Returns a sender based on the provided output stream * * @return A sender that uses the output stream */ Sender getSender(); /** * Closes both the input and output streams */ void close() throws IOException; /** * returns a receiver based on the provided input stream. * @return The receiver */ Receiver getReceiver(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/ConduitWrapper.java000066400000000000000000000032561420065311100274610ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import io.undertow.util.ConduitFactory; import org.xnio.conduits.Conduit; /** * Interface that provides a means of wrapping a {@link Conduit}. Every conduit wrapper has a chance * to replace the conduit with a conduit which either wraps or replaces the passed in conduit. However it is the responsibility * of either the conduit wrapper instance or the conduit it creates to ensure that the original conduit is eventually * cleaned up and shut down properly when the request is terminated. * * @author Stuart Douglas */ public interface ConduitWrapper { /** * Wrap the conduit. The wrapper should not return {@code null}. If no wrapping is desired, the original * conduit should be returned. * * @param factory the original conduit * @param exchange the in-flight HTTP exchange * @return the replacement conduit */ T wrap(final ConduitFactory factory, final HttpServerExchange exchange); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/ConnectionSSLSessionInfo.java000066400000000000000000000242111420065311100313460ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import io.undertow.UndertowMessages; import io.undertow.UndertowOptions; import io.undertow.server.protocol.http.HttpServerConnection; import org.xnio.ChannelListener; import org.xnio.IoUtils; import org.xnio.Options; import io.undertow.connector.PooledByteBuffer; import org.xnio.SslClientAuthMode; import org.xnio.channels.Channels; import org.xnio.channels.SslChannel; import org.xnio.channels.StreamSourceChannel; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.security.cert.X509Certificate; import java.io.IOException; import java.nio.ByteBuffer; import java.security.cert.Certificate; import java.util.concurrent.TimeUnit; /** * SSL session information that is read directly from the SSL session of the * XNIO connection * * @author Stuart Douglas */ public class ConnectionSSLSessionInfo implements SSLSessionInfo { private static final SSLPeerUnverifiedException PEER_UNVERIFIED_EXCEPTION = new SSLPeerUnverifiedException(""); private static final RenegotiationRequiredException RENEGOTIATION_REQUIRED_EXCEPTION = new RenegotiationRequiredException(); private static final long MAX_RENEGOTIATION_WAIT = 30000; private final SslChannel channel; private final HttpServerConnection serverConnection; private SSLPeerUnverifiedException unverified; private RenegotiationRequiredException renegotiationRequiredException; public ConnectionSSLSessionInfo(SslChannel channel, HttpServerConnection serverConnection) { this.channel = channel; this.serverConnection = serverConnection; } @Override public byte[] getSessionId() { return channel.getSslSession().getId(); } @Override public String getCipherSuite() { return channel.getSslSession().getCipherSuite(); } @Override public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException, RenegotiationRequiredException { if(unverified != null) { throw unverified; } if(renegotiationRequiredException != null) { throw renegotiationRequiredException; } try { return channel.getSslSession().getPeerCertificates(); } catch (SSLPeerUnverifiedException e) { try { SslClientAuthMode sslClientAuthMode = channel.getOption(Options.SSL_CLIENT_AUTH_MODE); if (sslClientAuthMode == SslClientAuthMode.NOT_REQUESTED) { renegotiationRequiredException = RENEGOTIATION_REQUIRED_EXCEPTION; throw renegotiationRequiredException; } } catch (IOException e1) { //ignore, will not actually happen } unverified = PEER_UNVERIFIED_EXCEPTION; throw unverified; } } @Override public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException, RenegotiationRequiredException { if(unverified != null) { throw unverified; } if(renegotiationRequiredException != null) { throw renegotiationRequiredException; } try { return channel.getSslSession().getPeerCertificateChain(); } catch (SSLPeerUnverifiedException e) { try { SslClientAuthMode sslClientAuthMode = channel.getOption(Options.SSL_CLIENT_AUTH_MODE); if (sslClientAuthMode == SslClientAuthMode.NOT_REQUESTED) { renegotiationRequiredException = RENEGOTIATION_REQUIRED_EXCEPTION; throw renegotiationRequiredException; } } catch (IOException e1) { //ignore, will not actually happen } unverified = PEER_UNVERIFIED_EXCEPTION; throw unverified; } } @Override public void renegotiate(HttpServerExchange exchange, SslClientAuthMode sslClientAuthMode) throws IOException { if ("TLSv1.3".equals(channel.getSslSession().getProtocol())) { // UNDERTOW-1812: TLSv1.3 does not support renegotiation, attempting renegotiation will block // the full MAX_RENEGOTIATION_WAIT timeout. throw UndertowMessages.MESSAGES.renegotiationNotSupported(); } unverified = null; renegotiationRequiredException = null; if (exchange.isRequestComplete()) { renegotiateNoRequest(exchange, sslClientAuthMode); } else { renegotiateBufferRequest(exchange, sslClientAuthMode); } } @Override public SSLSession getSSLSession() { return channel.getSslSession(); } public void renegotiateBufferRequest(HttpServerExchange exchange, SslClientAuthMode newAuthMode) throws IOException { int maxSize = exchange.getConnection().getUndertowOptions().get(UndertowOptions.MAX_BUFFERED_REQUEST_SIZE, UndertowOptions.DEFAULT_MAX_BUFFERED_REQUEST_SIZE); if (maxSize <= 0) { throw new SSLPeerUnverifiedException(""); } //first we need to read the request boolean requestResetRequired = false; StreamSourceChannel requestChannel = Connectors.getExistingRequestChannel(exchange); if (requestChannel == null) { requestChannel = exchange.getRequestChannel(); requestResetRequired = true; } PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().allocate(); boolean free = true; //if the pooled buffer should be freed int usedBuffers = 0; PooledByteBuffer[] poolArray = null; final int bufferSize = pooled.getBuffer().remaining(); int allowedBuffers = ((maxSize + bufferSize - 1) / bufferSize); poolArray = new PooledByteBuffer[allowedBuffers]; poolArray[usedBuffers++] = pooled; boolean overflow = false; try { int res; for(;;) { final ByteBuffer buf = pooled.getBuffer(); res = Channels.readBlocking(requestChannel, buf); if (!buf.hasRemaining()) { buf.flip(); if(allowedBuffers == usedBuffers) { overflow = true; break; } else { pooled = exchange.getConnection().getByteBufferPool().allocate(); poolArray[usedBuffers++] = pooled; } } else if(res == -1) { buf.flip(); break; } } free = false; Connectors.ungetRequestBytes(exchange, poolArray); if(overflow) { throw new SSLPeerUnverifiedException("Cannot renegotiate"); } renegotiateNoRequest(exchange, newAuthMode); } finally { if (free) { for(PooledByteBuffer buf : poolArray) { if(buf != null) { buf.close(); } } } if(requestResetRequired) { exchange.requestChannel = null; } } } public void renegotiateNoRequest(HttpServerExchange exchange, SslClientAuthMode newAuthMode) throws IOException { AbstractServerConnection.ConduitState oldState = serverConnection.resetChannel(); try { SslClientAuthMode sslClientAuthMode = channel.getOption(Options.SSL_CLIENT_AUTH_MODE); if (sslClientAuthMode == SslClientAuthMode.NOT_REQUESTED) { SslHandshakeWaiter waiter = new SslHandshakeWaiter(); channel.getHandshakeSetter().set(waiter); //we use requested, to place nicely with other auth modes channel.setOption(Options.SSL_CLIENT_AUTH_MODE, newAuthMode); channel.getSslSession().invalidate(); channel.startHandshake(); serverConnection.getOriginalSinkConduit().flush(); ByteBuffer buff = ByteBuffer.wrap(new byte[1]); long end = System.currentTimeMillis() + MAX_RENEGOTIATION_WAIT; while (!waiter.isDone() && serverConnection.isOpen() && System.currentTimeMillis() < end) { int read = serverConnection.getSourceChannel().read(buff); if (read != 0) { throw new SSLPeerUnverifiedException(""); } if (!waiter.isDone()) { serverConnection.getSourceChannel().awaitReadable(end - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } } if(!waiter.isDone()) { if(serverConnection.isOpen()) { IoUtils.safeClose(serverConnection); throw UndertowMessages.MESSAGES.rengotiationTimedOut(); } else { IoUtils.safeClose(serverConnection); throw UndertowMessages.MESSAGES.rengotiationFailed(); } } } } finally { if (oldState != null) { serverConnection.restoreChannel(oldState); } } } private static class SslHandshakeWaiter implements ChannelListener { private volatile boolean done = false; boolean isDone() { return done; } @Override public void handleEvent(SslChannel channel) { done = true; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/ConnectorStatistics.java000066400000000000000000000042441420065311100305160ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; /** * Connector level statistics * * * @author Stuart Douglas */ public interface ConnectorStatistics { /** * * @return The number of requests processed by this connector */ long getRequestCount(); /** * * @return The number of bytes sent on this connector */ long getBytesSent(); /** * * @return The number of bytes that have been received by this connector */ long getBytesReceived(); /** * * @return The number of requests that triggered an error (i.e. 500) response. */ long getErrorCount(); /** * * @return The total amount of time spent processing all requests on this connector * (nanoseconds) */ long getProcessingTime(); /** * * @return The time taken by the slowest request * (nanoseconds) */ long getMaxProcessingTime(); /** * Resets all values to zero */ void reset(); /** * * @return The current number of active connections */ long getActiveConnections(); /** * * @return The maximum number of active connections that have every been active on this connector */ long getMaxActiveConnections(); /** * * @return The current number of active requests */ long getActiveRequests(); /** * * @return The maximum number of active requests */ long getMaxActiveRequests(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/ConnectorStatisticsImpl.java000066400000000000000000000203271420065311100313400ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import io.undertow.conduits.ByteActivityCallback; import io.undertow.util.StatusCodes; import java.util.concurrent.atomic.AtomicLongFieldUpdater; /** * @author Stuart Douglas */ public class ConnectorStatisticsImpl implements ConnectorStatistics { private static final AtomicLongFieldUpdater requestCountUpdater = AtomicLongFieldUpdater.newUpdater(ConnectorStatisticsImpl.class, "requestCount"); private static final AtomicLongFieldUpdater bytesSentUpdater = AtomicLongFieldUpdater.newUpdater(ConnectorStatisticsImpl.class, "bytesSent"); private static final AtomicLongFieldUpdater bytesReceivedUpdater = AtomicLongFieldUpdater.newUpdater(ConnectorStatisticsImpl.class, "bytesReceived"); private static final AtomicLongFieldUpdater errorCountUpdater = AtomicLongFieldUpdater.newUpdater(ConnectorStatisticsImpl.class, "errorCount"); private static final AtomicLongFieldUpdater processingTimeUpdater = AtomicLongFieldUpdater.newUpdater(ConnectorStatisticsImpl.class, "processingTime"); private static final AtomicLongFieldUpdater maxProcessingTimeUpdater = AtomicLongFieldUpdater.newUpdater(ConnectorStatisticsImpl.class, "maxProcessingTime"); private static final AtomicLongFieldUpdater activeConnectionsUpdater = AtomicLongFieldUpdater.newUpdater(ConnectorStatisticsImpl.class, "activeConnections"); private static final AtomicLongFieldUpdater maxActiveConnectionsUpdater = AtomicLongFieldUpdater.newUpdater(ConnectorStatisticsImpl.class, "maxActiveConnections"); private static final AtomicLongFieldUpdater activeRequestsUpdater = AtomicLongFieldUpdater.newUpdater(ConnectorStatisticsImpl.class, "activeRequests"); private static final AtomicLongFieldUpdater maxActiveRequestsUpdater = AtomicLongFieldUpdater.newUpdater(ConnectorStatisticsImpl.class, "maxActiveRequests"); private volatile long requestCount; private volatile long bytesSent; private volatile long bytesReceived; private volatile long errorCount; private volatile long processingTime; private volatile long maxProcessingTime; private volatile long activeConnections; private volatile long maxActiveConnections; private volatile long activeRequests; private volatile long maxActiveRequests; private final ExchangeCompletionListener completionListener = new ExchangeCompletionListener() { @Override public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { try { activeRequestsUpdater.decrementAndGet(ConnectorStatisticsImpl.this); if (exchange.getStatusCode() == StatusCodes.INTERNAL_SERVER_ERROR) { errorCountUpdater.incrementAndGet(ConnectorStatisticsImpl.this); } long start = exchange.getRequestStartTime(); if (start > 0) { long elapsed = System.nanoTime() - start; processingTimeUpdater.addAndGet(ConnectorStatisticsImpl.this, elapsed); long oldMax; do { oldMax = maxProcessingTimeUpdater.get(ConnectorStatisticsImpl.this); if (oldMax >= elapsed) { break; } } while (!maxProcessingTimeUpdater.compareAndSet(ConnectorStatisticsImpl.this, oldMax, elapsed)); } } finally { nextListener.proceed(); } } }; private final ByteActivityCallback bytesSentAccumulator = new BytesSentAccumulator(); private final ByteActivityCallback bytesReceivedAccumulator = new BytesReceivedAccumulator(); @Override public long getRequestCount() { return requestCountUpdater.get(this); } @Override public long getBytesSent() { return bytesSentUpdater.get(this); } @Override public long getBytesReceived() { return bytesReceivedUpdater.get(this); } @Override public long getErrorCount() { return errorCountUpdater.get(this); } @Override public long getProcessingTime() { return processingTimeUpdater.get(this); } @Override public long getMaxProcessingTime() { return maxProcessingTimeUpdater.get(this); } @Override public void reset() { requestCountUpdater.set(this, 0); bytesSentUpdater.set(this, 0); bytesReceivedUpdater.set(this, 0); errorCountUpdater.set(this, 0); maxProcessingTimeUpdater.set(this, 0); processingTimeUpdater.set(this, 0); maxActiveConnectionsUpdater.set(this, 0); maxActiveRequestsUpdater.set(this, 0); //we don't update active requests or connections, as these will still be live } public void requestFinished(long bytesSent, long bytesReceived, boolean error) { bytesSentUpdater.addAndGet(this, bytesSent); bytesReceivedUpdater.addAndGet(this, bytesReceived); if (error) { errorCountUpdater.incrementAndGet(this); } } public void updateBytesSent(long bytes) { bytesSentUpdater.addAndGet(this, bytes); } public void updateBytesReceived(long bytes) { bytesReceivedUpdater.addAndGet(this, bytes); } public void setup(HttpServerExchange exchange) { requestCountUpdater.incrementAndGet(this); long current = activeRequestsUpdater.incrementAndGet(this); long maxActiveRequests; do { maxActiveRequests = this.maxActiveRequests; if(current <= maxActiveRequests) { break; } } while (!maxActiveRequestsUpdater.compareAndSet(this, maxActiveRequests, current)); exchange.addExchangeCompleteListener(completionListener); } public ByteActivityCallback sentAccumulator() { return bytesSentAccumulator; } public ByteActivityCallback receivedAccumulator() { return bytesReceivedAccumulator; } //todo: we can do a way private class BytesSentAccumulator implements ByteActivityCallback { @Override public void activity(long bytes) { bytesSentUpdater.addAndGet(ConnectorStatisticsImpl.this, bytes); } } private class BytesReceivedAccumulator implements ByteActivityCallback { @Override public void activity(long bytes) { bytesReceivedUpdater.addAndGet(ConnectorStatisticsImpl.this, bytes); } } @Override public long getActiveConnections() { return activeConnections; } @Override public long getMaxActiveConnections() { return maxActiveConnections; } public void incrementConnectionCount() { long current = activeConnectionsUpdater.incrementAndGet(this); long maxActiveConnections; do { maxActiveConnections = this.maxActiveConnections; if(current <= maxActiveConnections) { return; } } while (!maxActiveConnectionsUpdater.compareAndSet(this, maxActiveConnections, current)); } public void decrementConnectionCount() { activeConnectionsUpdater.decrementAndGet(this); } @Override public long getActiveRequests() { return activeRequests; } @Override public long getMaxActiveRequests() { return maxActiveRequests; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/Connectors.java000066400000000000000000000557061420065311100266370ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.UndertowOptions; import io.undertow.server.handlers.Cookie; import io.undertow.util.DateUtils; import io.undertow.util.HeaderMap; import io.undertow.util.HeaderValues; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.LegacyCookieSupport; import io.undertow.util.ParameterLimitException; import io.undertow.util.StatusCodes; import io.undertow.util.URLUtils; import io.undertow.connector.PooledByteBuffer; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.ConduitStreamSinkChannel; import java.io.IOException; import java.util.Date; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; /** * This class provides the connector part of the {@link HttpServerExchange} API. *

* It contains methods that logically belong on the exchange, however should only be used * by connector implementations. * * @author Stuart Douglas * @author Richard Opalka */ public class Connectors { private static final boolean[] ALLOWED_TOKEN_CHARACTERS = new boolean[256]; private static final boolean[] ALLOWED_SCHEME_CHARACTERS = new boolean[256]; static { for(int i = 0; i < ALLOWED_TOKEN_CHARACTERS.length; ++i) { if((i >='0' && i <= '9') || (i >='a' && i <= 'z') || (i >='A' && i <= 'Z')) { ALLOWED_TOKEN_CHARACTERS[i] = true; } else { switch (i) { case '!': case '#': case '$': case '%': case '&': case '\'': case '*': case '+': case '-': case '.': case '^': case '_': case '`': case '|': case '~': { ALLOWED_TOKEN_CHARACTERS[i] = true; break; } default: ALLOWED_TOKEN_CHARACTERS[i] = false; } } } for(int i = 0; i < ALLOWED_SCHEME_CHARACTERS.length; ++i) { if((i >='0' && i <= '9') || (i >='a' && i <= 'z') || (i >='A' && i <= 'Z')) { ALLOWED_SCHEME_CHARACTERS[i] = true; } else { switch (i) { case '+': case '-': case '.': { ALLOWED_SCHEME_CHARACTERS[i] = true; break; } default: ALLOWED_SCHEME_CHARACTERS[i] = false; } } } } /** * Flattens the exchange cookie map into the response header map. This should be called by a * connector just before the response is started. * * @param exchange The server exchange */ public static void flattenCookies(final HttpServerExchange exchange) { boolean enableRfc6265Validation = exchange.getConnection().getUndertowOptions().get(UndertowOptions.ENABLE_RFC6265_COOKIE_VALIDATION, UndertowOptions.DEFAULT_ENABLE_RFC6265_COOKIE_VALIDATION); for (Cookie cookie : exchange.responseCookies()) { exchange.getResponseHeaders().add(Headers.SET_COOKIE, getCookieString(cookie, enableRfc6265Validation)); } } /** * Adds the cookie into the response header map. This should be called * before the response is started. * * @param exchange The server exchange * @param cookie The cookie */ public static void addCookie(final HttpServerExchange exchange, Cookie cookie) { boolean enableRfc6265Validation = exchange.getConnection().getUndertowOptions().get(UndertowOptions.ENABLE_RFC6265_COOKIE_VALIDATION, UndertowOptions.DEFAULT_ENABLE_RFC6265_COOKIE_VALIDATION); exchange.getResponseHeaders().add(Headers.SET_COOKIE, getCookieString(cookie, enableRfc6265Validation)); } /** * Attached buffered data to the exchange. The will generally be used to allow data to be re-read. * * @param exchange The HTTP server exchange * @param buffers The buffers to attach */ public static void ungetRequestBytes(final HttpServerExchange exchange, PooledByteBuffer... buffers) { PooledByteBuffer[] existing = exchange.getAttachment(HttpServerExchange.BUFFERED_REQUEST_DATA); PooledByteBuffer[] newArray; if (existing == null) { newArray = new PooledByteBuffer[buffers.length]; System.arraycopy(buffers, 0, newArray, 0, buffers.length); } else { newArray = new PooledByteBuffer[existing.length + buffers.length]; System.arraycopy(existing, 0, newArray, 0, existing.length); System.arraycopy(buffers, 0, newArray, existing.length, buffers.length); } exchange.putAttachment(HttpServerExchange.BUFFERED_REQUEST_DATA, newArray); //todo: force some kind of wakeup? exchange.addExchangeCompleteListener(BufferedRequestDataCleanupListener.INSTANCE); } private enum BufferedRequestDataCleanupListener implements ExchangeCompletionListener { INSTANCE; @Override public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { PooledByteBuffer[] bufs = exchange.getAttachment(HttpServerExchange.BUFFERED_REQUEST_DATA); if (bufs != null) { for (PooledByteBuffer i : bufs) { if(i != null) { i.close(); } } } nextListener.proceed(); } } public static void terminateRequest(final HttpServerExchange exchange) { exchange.terminateRequest(); } public static void terminateResponse(final HttpServerExchange exchange) { exchange.terminateResponse(); } public static void resetRequestChannel(final HttpServerExchange exchange) { exchange.resetRequestChannel(); } private static String getCookieString(final Cookie cookie, boolean enableRfc6265Validation) { if(enableRfc6265Validation) { return addRfc6265ResponseCookieToExchange(cookie); } else { switch (LegacyCookieSupport.adjustedCookieVersion(cookie)) { case 0: return addVersion0ResponseCookieToExchange(cookie); case 1: default: return addVersion1ResponseCookieToExchange(cookie); } } } public static void setRequestStartTime(HttpServerExchange exchange) { exchange.setRequestStartTime(System.nanoTime()); } public static void setRequestStartTime(HttpServerExchange existing, HttpServerExchange newExchange) { newExchange.setRequestStartTime(existing.getRequestStartTime()); } private static String addRfc6265ResponseCookieToExchange(final Cookie cookie) { final StringBuilder header = new StringBuilder(cookie.getName()); header.append("="); if(cookie.getValue() != null) { header.append(cookie.getValue()); } if (cookie.getPath() != null) { header.append("; Path="); header.append(cookie.getPath()); } if (cookie.getDomain() != null) { header.append("; Domain="); header.append(cookie.getDomain()); } if (cookie.isDiscard()) { header.append("; Discard"); } if (cookie.isSecure()) { header.append("; Secure"); } if (cookie.isHttpOnly()) { header.append("; HttpOnly"); } if (cookie.getMaxAge() != null) { if (cookie.getMaxAge() >= 0) { header.append("; Max-Age="); header.append(cookie.getMaxAge()); } // Microsoft IE and Microsoft Edge don't understand Max-Age so send // expires as well. Without this, persistent cookies fail with those // browsers. They do understand Expires, even with V1 cookies. // So, we add Expires header when Expires is not explicitly specified. if (cookie.getExpires() == null) { if (cookie.getMaxAge() == 0) { Date expires = new Date(); expires.setTime(0); header.append("; Expires="); header.append(DateUtils.toOldCookieDateString(expires)); } else if (cookie.getMaxAge() > 0) { Date expires = new Date(); expires.setTime(expires.getTime() + cookie.getMaxAge() * 1000L); header.append("; Expires="); header.append(DateUtils.toOldCookieDateString(expires)); } } } if (cookie.getExpires() != null) { header.append("; Expires="); header.append(DateUtils.toDateString(cookie.getExpires())); } if (cookie.getComment() != null && !cookie.getComment().isEmpty()) { header.append("; Comment="); header.append(cookie.getComment()); } if (cookie.isSameSite()) { if (cookie.getSameSiteMode() != null && !cookie.getSameSiteMode().isEmpty()) { header.append("; SameSite="); header.append(cookie.getSameSiteMode()); } } return header.toString(); } private static String addVersion0ResponseCookieToExchange(final Cookie cookie) { final StringBuilder header = new StringBuilder(cookie.getName()); header.append("="); if(cookie.getValue() != null) { LegacyCookieSupport.maybeQuote(header, cookie.getValue()); } if (cookie.getPath() != null) { header.append("; path="); LegacyCookieSupport.maybeQuote(header, cookie.getPath()); } if (cookie.getDomain() != null) { header.append("; domain="); LegacyCookieSupport.maybeQuote(header, cookie.getDomain()); } if (cookie.isSecure()) { header.append("; secure"); } if (cookie.isHttpOnly()) { header.append("; HttpOnly"); } if (cookie.getExpires() != null) { header.append("; Expires="); header.append(DateUtils.toOldCookieDateString(cookie.getExpires())); } else if (cookie.getMaxAge() != null) { if (cookie.getMaxAge() >= 0) { header.append("; Max-Age="); header.append(cookie.getMaxAge()); } if (cookie.getMaxAge() == 0) { Date expires = new Date(); expires.setTime(0); header.append("; Expires="); header.append(DateUtils.toOldCookieDateString(expires)); } else if (cookie.getMaxAge() > 0) { Date expires = new Date(); expires.setTime(expires.getTime() + cookie.getMaxAge() * 1000L); header.append("; Expires="); header.append(DateUtils.toOldCookieDateString(expires)); } } if (cookie.isSameSite()) { if (cookie.getSameSiteMode() != null && !cookie.getSameSiteMode().isEmpty()) { header.append("; SameSite="); header.append(cookie.getSameSiteMode()); } } return header.toString(); } private static String addVersion1ResponseCookieToExchange(final Cookie cookie) { final StringBuilder header = new StringBuilder(cookie.getName()); header.append("="); if(cookie.getValue() != null) { LegacyCookieSupport.maybeQuote(header, cookie.getValue()); } header.append("; Version=1"); if (cookie.getPath() != null) { header.append("; Path="); LegacyCookieSupport.maybeQuote(header, cookie.getPath()); } if (cookie.getDomain() != null) { header.append("; Domain="); LegacyCookieSupport.maybeQuote(header, cookie.getDomain()); } if (cookie.isDiscard()) { header.append("; Discard"); } if (cookie.isSecure()) { header.append("; Secure"); } if (cookie.isHttpOnly()) { header.append("; HttpOnly"); } if (cookie.getMaxAge() != null) { if (cookie.getMaxAge() >= 0) { header.append("; Max-Age="); header.append(cookie.getMaxAge()); } // Microsoft IE and Microsoft Edge don't understand Max-Age so send // expires as well. Without this, persistent cookies fail with those // browsers. They do understand Expires, even with V1 cookies. // So, we add Expires header when Expires is not explicitly specified. if (cookie.getExpires() == null) { if (cookie.getMaxAge() == 0) { Date expires = new Date(); expires.setTime(0); header.append("; Expires="); header.append(DateUtils.toOldCookieDateString(expires)); } else if (cookie.getMaxAge() > 0) { Date expires = new Date(); expires.setTime(expires.getTime() + cookie.getMaxAge() * 1000L); header.append("; Expires="); header.append(DateUtils.toOldCookieDateString(expires)); } } } if (cookie.getExpires() != null) { header.append("; Expires="); header.append(DateUtils.toDateString(cookie.getExpires())); } if (cookie.getComment() != null && !cookie.getComment().isEmpty()) { header.append("; Comment="); LegacyCookieSupport.maybeQuote(header, cookie.getComment()); } if (cookie.isSameSite()) { if (cookie.getSameSiteMode() != null && !cookie.getSameSiteMode().isEmpty()) { header.append("; SameSite="); header.append(cookie.getSameSiteMode()); } } return header.toString(); } public static void executeRootHandler(final HttpHandler handler, final HttpServerExchange exchange) { try { exchange.setInCall(true); handler.handleRequest(exchange); exchange.setInCall(false); boolean resumed = exchange.isResumed(); if (exchange.isDispatched()) { if (resumed) { UndertowLogger.REQUEST_LOGGER.resumedAndDispatched(); exchange.setStatusCode(500); exchange.endExchange(); return; } final Runnable dispatchTask = exchange.getDispatchTask(); Executor executor = exchange.getDispatchExecutor(); exchange.setDispatchExecutor(null); exchange.unDispatch(); if (dispatchTask != null) { executor = executor == null ? exchange.getConnection().getWorker() : executor; try { executor.execute(dispatchTask); } catch (RejectedExecutionException e) { UndertowLogger.REQUEST_LOGGER.debug("Failed to dispatch to worker", e); exchange.setStatusCode(StatusCodes.SERVICE_UNAVAILABLE); exchange.endExchange(); } } } else if (!resumed) { exchange.endExchange(); } else { exchange.runResumeReadWrite(); } } catch (Throwable t) { exchange.putAttachment(DefaultResponseListener.EXCEPTION, t); exchange.setInCall(false); if (!exchange.isResponseStarted()) { exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); } if(t instanceof IOException) { UndertowLogger.REQUEST_IO_LOGGER.ioException((IOException) t); } else { UndertowLogger.REQUEST_LOGGER.undertowRequestFailed(t, exchange); } exchange.endExchange(); } } /** * Sets the request path and query parameters, decoding to the requested charset. * * @param exchange The exchange * @param encodedPath The encoded path * @param charset The charset */ @Deprecated public static void setExchangeRequestPath(final HttpServerExchange exchange, final String encodedPath, final String charset, boolean decode, final boolean allowEncodedSlash, StringBuilder decodeBuffer) { try { setExchangeRequestPath(exchange, encodedPath, charset, decode, allowEncodedSlash, decodeBuffer, exchange.getConnection().getUndertowOptions().get(UndertowOptions.MAX_PARAMETERS, UndertowOptions.DEFAULT_MAX_PARAMETERS)); } catch (ParameterLimitException e) { throw new RuntimeException(e); } } /** * Sets the request path and query parameters, decoding to the requested charset. * * @param exchange The exchange * @param encodedPath The encoded path * @param charset The charset */ public static void setExchangeRequestPath(final HttpServerExchange exchange, final String encodedPath, final String charset, boolean decode, final boolean allowEncodedSlash, StringBuilder decodeBuffer, int maxParameters) throws ParameterLimitException { boolean requiresDecode = false; final StringBuilder pathBuilder = new StringBuilder(); int currentPathPartIndex = 0; for (int i = 0; i < encodedPath.length(); ++i) { char c = encodedPath.charAt(i); if (c == '?') { String part; String encodedPart = encodedPath.substring(currentPathPartIndex, i); if (requiresDecode) { part = URLUtils.decode(encodedPart, charset, allowEncodedSlash,false, decodeBuffer); } else { part = encodedPart; } pathBuilder.append(part); part = pathBuilder.toString(); exchange.setRequestPath(part); exchange.setRelativePath(part); exchange.setRequestURI(encodedPath.substring(0, i)); final String qs = encodedPath.substring(i + 1); exchange.setQueryString(qs); URLUtils.parseQueryString(qs, exchange, charset, decode, maxParameters); return; } else if(c == ';') { String part; String encodedPart = encodedPath.substring(currentPathPartIndex, i); if (requiresDecode) { part = URLUtils.decode(encodedPart, charset, allowEncodedSlash, false, decodeBuffer); } else { part = encodedPart; } pathBuilder.append(part); exchange.setRequestURI(encodedPath); currentPathPartIndex = i + 1 + URLUtils.parsePathParams(encodedPath.substring(i + 1), exchange, charset, decode, maxParameters); i = currentPathPartIndex -1 ; } else if(c == '%' || c == '+') { requiresDecode = decode; } } String part; String encodedPart = encodedPath.substring(currentPathPartIndex); if (requiresDecode) { part = URLUtils.decode(encodedPart, charset, allowEncodedSlash, false, decodeBuffer); } else { part = encodedPart; } pathBuilder.append(part); part = pathBuilder.toString(); exchange.setRequestPath(part); exchange.setRelativePath(part); exchange.setRequestURI(encodedPath); } /** * Returns the existing request channel, if it exists. Otherwise returns null * * @param exchange The http server exchange */ public static StreamSourceChannel getExistingRequestChannel(final HttpServerExchange exchange) { return exchange.requestChannel; } public static boolean isEntityBodyAllowed(HttpServerExchange exchange){ int code = exchange.getStatusCode(); return isEntityBodyAllowed(code); } public static boolean isEntityBodyAllowed(int code) { if(code >= 100 && code < 200) { return false; } if(code == 204 || code == 304) { return false; } return true; } public static void updateResponseBytesSent(HttpServerExchange exchange, long bytes) { exchange.updateBytesSent(bytes); } public static ConduitStreamSinkChannel getConduitSinkChannel(HttpServerExchange exchange) { return exchange.getConnection().getSinkChannel(); } /** * Verifies that the contents of the HttpString are a valid token according to rfc7230. * @param header The header to verify */ public static void verifyToken(HttpString header) { int length = header.length(); for(int i = 0; i < length; ++i) { byte c = header.byteAt(i); if(!ALLOWED_TOKEN_CHARACTERS[c]) { throw UndertowMessages.MESSAGES.invalidToken(c); } } } /** * Returns true if the token character is valid according to rfc7230 */ public static boolean isValidTokenCharacter(byte c) { return ALLOWED_TOKEN_CHARACTERS[c]; } public static boolean isValidSchemeCharacter(byte c) { return ALLOWED_SCHEME_CHARACTERS[c]; } /** * Verifies that the provided request headers are valid according to rfc7230. In particular: * - At most one content-length or transfer encoding */ public static boolean areRequestHeadersValid(HeaderMap headers) { HeaderValues te = headers.get(Headers.TRANSFER_ENCODING); HeaderValues cl = headers.get(Headers.CONTENT_LENGTH); if(te != null && cl != null) { return false; } else if(te != null && te.size() > 1) { return false; } else if(cl != null && cl.size() > 1) { return false; } return true; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/DefaultByteBufferPool.java000066400000000000000000000262011420065311100307020ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import io.undertow.UndertowMessages; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import java.lang.ref.WeakReference; import java.nio.ByteBuffer; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; /** * A byte buffer pool that supports reference counted pools. * * TODO: move this somewhere more appropriate * * @author Stuart Douglas */ public class DefaultByteBufferPool implements ByteBufferPool { private final ThreadLocal threadLocalCache = new ThreadLocal<>(); // Access requires synchronization on the threadLocalDataList instance private final List> threadLocalDataList = new ArrayList<>(); private final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); private final boolean direct; private final int bufferSize; private final int maximumPoolSize; private final int threadLocalCacheSize; private final int leakDectionPercent; private int count; //racily updated count used in leak detection @SuppressWarnings({"unused", "FieldCanBeLocal"}) private volatile int currentQueueLength = 0; private static final AtomicIntegerFieldUpdater currentQueueLengthUpdater = AtomicIntegerFieldUpdater.newUpdater(DefaultByteBufferPool.class, "currentQueueLength"); @SuppressWarnings({"unused", "FieldCanBeLocal"}) private volatile int reclaimedThreadLocals = 0; private static final AtomicIntegerFieldUpdater reclaimedThreadLocalsUpdater = AtomicIntegerFieldUpdater.newUpdater(DefaultByteBufferPool.class, "reclaimedThreadLocals"); private volatile boolean closed; private final DefaultByteBufferPool arrayBackedPool; /** * @param direct If this implementation should use direct buffers * @param bufferSize The buffer size to use */ public DefaultByteBufferPool(boolean direct, int bufferSize) { this(direct, bufferSize, -1, 12, 0); } /** * @param direct If this implementation should use direct buffers * @param bufferSize The buffer size to use * @param maximumPoolSize The maximum pool size, in number of buffers, it does not include buffers in thread local caches * @param threadLocalCacheSize The maximum number of buffers that can be stored in a thread local cache */ public DefaultByteBufferPool(boolean direct, int bufferSize, int maximumPoolSize, int threadLocalCacheSize, int leakDecetionPercent) { this.direct = direct; this.bufferSize = bufferSize; this.maximumPoolSize = maximumPoolSize; this.threadLocalCacheSize = threadLocalCacheSize; this.leakDectionPercent = leakDecetionPercent; if(direct) { arrayBackedPool = new DefaultByteBufferPool(false, bufferSize, maximumPoolSize, 0, leakDecetionPercent); } else { arrayBackedPool = this; } } /** * @param direct If this implementation should use direct buffers * @param bufferSize The buffer size to use * @param maximumPoolSize The maximum pool size, in number of buffers, it does not include buffers in thread local caches * @param threadLocalCacheSize The maximum number of buffers that can be stored in a thread local cache */ public DefaultByteBufferPool(boolean direct, int bufferSize, int maximumPoolSize, int threadLocalCacheSize) { this(direct, bufferSize, maximumPoolSize, threadLocalCacheSize, 0); } @Override public int getBufferSize() { return bufferSize; } @Override public boolean isDirect() { return direct; } @Override public PooledByteBuffer allocate() { if (closed) { throw UndertowMessages.MESSAGES.poolIsClosed(); } ByteBuffer buffer = null; ThreadLocalData local = null; if(threadLocalCacheSize > 0) { local = threadLocalCache.get(); if (local != null) { buffer = local.buffers.poll(); } else { local = new ThreadLocalData(); synchronized (threadLocalDataList) { if (closed) { throw UndertowMessages.MESSAGES.poolIsClosed(); } cleanupThreadLocalData(); threadLocalDataList.add(new WeakReference<>(local)); threadLocalCache.set(local); } } } if (buffer == null) { buffer = queue.poll(); if (buffer != null) { currentQueueLengthUpdater.decrementAndGet(this); } } if (buffer == null) { if (direct) { buffer = ByteBuffer.allocateDirect(bufferSize); } else { buffer = ByteBuffer.allocate(bufferSize); } } if(local != null) { if(local.allocationDepth < threadLocalCacheSize) { //prevent overflow if the thread only allocates and never frees local.allocationDepth++; } } buffer.clear(); return new DefaultPooledBuffer(this, buffer, leakDectionPercent == 0 ? false : (++count % 100 < leakDectionPercent)); } @Override public ByteBufferPool getArrayBackedPool() { return arrayBackedPool; } private void cleanupThreadLocalData() { // Called under lock, and only when at least quarter of the capacity has been collected. int size = threadLocalDataList.size(); if (reclaimedThreadLocals > (size / 4)) { int j = 0; for (int i = 0; i < size; i++) { WeakReference ref = threadLocalDataList.get(i); if (ref.get() != null) { threadLocalDataList.set(j++, ref); } } for (int i = size - 1; i >= j; i--) { // A tail remove is inlined to a range change check and a decrement threadLocalDataList.remove(i); } reclaimedThreadLocalsUpdater.addAndGet(this, -1 * (size - j)); } } private void freeInternal(ByteBuffer buffer) { if (closed) { DirectByteBufferDeallocator.free(buffer); return; //GC will take care of it } ThreadLocalData local = threadLocalCache.get(); if(local != null) { if(local.allocationDepth > 0) { local.allocationDepth--; if (local.buffers.size() < threadLocalCacheSize) { local.buffers.add(buffer); return; } } } queueIfUnderMax(buffer); } private void queueIfUnderMax(ByteBuffer buffer) { int size; do { size = currentQueueLength; if(size > maximumPoolSize) { DirectByteBufferDeallocator.free(buffer); return; } } while (!currentQueueLengthUpdater.compareAndSet(this, size, size + 1)); queue.add(buffer); } @Override public void close() { if (closed) { return; } closed = true; queue.clear(); synchronized (threadLocalDataList) { for (WeakReference ref : threadLocalDataList) { ThreadLocalData local = ref.get(); if (local != null) { local.buffers.clear(); } ref.clear(); } threadLocalDataList.clear(); } } @Override protected void finalize() throws Throwable { super.finalize(); close(); } private static class DefaultPooledBuffer implements PooledByteBuffer { private final DefaultByteBufferPool pool; private final LeakDetector leakDetector; private ByteBuffer buffer; private volatile int referenceCount = 1; private static final AtomicIntegerFieldUpdater referenceCountUpdater = AtomicIntegerFieldUpdater.newUpdater(DefaultPooledBuffer.class, "referenceCount"); DefaultPooledBuffer(DefaultByteBufferPool pool, ByteBuffer buffer, boolean detectLeaks) { this.pool = pool; this.buffer = buffer; this.leakDetector = detectLeaks ? new LeakDetector() : null; } @Override public ByteBuffer getBuffer() { if(referenceCount == 0) { throw UndertowMessages.MESSAGES.bufferAlreadyFreed(); } return buffer; } @Override public void close() { if(referenceCountUpdater.compareAndSet(this, 1, 0)) { if(leakDetector != null) { leakDetector.closed = true; } pool.freeInternal(buffer); this.buffer = null; } } @Override public boolean isOpen() { return referenceCount > 0; } @Override public String toString() { return "DefaultPooledBuffer{" + "buffer=" + buffer + ", referenceCount=" + referenceCount + '}'; } } private class ThreadLocalData { ArrayDeque buffers = new ArrayDeque<>(threadLocalCacheSize); int allocationDepth = 0; @Override protected void finalize() throws Throwable { super.finalize(); reclaimedThreadLocalsUpdater.incrementAndGet(DefaultByteBufferPool.this); if (buffers != null) { // Recycle them ByteBuffer buffer; while ((buffer = buffers.poll()) != null) { queueIfUnderMax(buffer); } } } } private static class LeakDetector { volatile boolean closed = false; private final Throwable allocationPoint; private LeakDetector() { this.allocationPoint = new Throwable("Buffer leak detected"); } @Override protected void finalize() throws Throwable { super.finalize(); if(!closed) { allocationPoint.printStackTrace(); } } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/DefaultResponseListener.java000066400000000000000000000026541420065311100313250ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import io.undertow.util.AttachmentKey; /** * Listener interface for default response handlers. These are handlers that generate default content * such as error pages. * * @author Stuart Douglas */ public interface DefaultResponseListener { /** * If the default response listener was invoked as a result of an exception being thrown * then the exception will be available under this attachment key. */ AttachmentKey EXCEPTION = AttachmentKey.create(Throwable.class); /** * * @param exchange The exchange * @return true if this listener is generating a default response. */ boolean handleDefaultResponse(final HttpServerExchange exchange); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/DelegateOpenListener.java000066400000000000000000000023461420065311100305540ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import io.undertow.connector.PooledByteBuffer; import org.xnio.StreamConnection; /** * An open listener that handles being delegated to, e.g. by NPN or ALPN * * @author Stuart Douglas */ public interface DelegateOpenListener extends OpenListener { /** * * @param channel The channel * @param additionalData Any additional data that was read from the stream as part of the handshake process */ void handleEvent(final StreamConnection channel, PooledByteBuffer additionalData); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/DelegatingIterable.java000066400000000000000000000023031420065311100302160ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import java.util.Iterator; /** * @author Richard Opalka */ final class DelegatingIterable implements Iterable { private final Iterable delegate; DelegatingIterable(final Iterable delegate) { this.delegate = delegate; } Iterable getDelegate() { return delegate; } @Override public Iterator iterator() { return new ReadOnlyIterator(delegate.iterator()); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/DirectByteBufferDeallocator.java000066400000000000000000000122201420065311100320440ustar00rootroot00000000000000package io.undertow.server; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.security.AccessController; import java.security.PrivilegedAction; import io.undertow.UndertowLogger; import sun.misc.Unsafe; /** * {@link DirectByteBufferDeallocator} Utility class used to free direct buffer memory. */ public final class DirectByteBufferDeallocator { private static final boolean SUPPORTED; private static final Method cleaner; private static final Method cleanerClean; private static final Unsafe UNSAFE; static { String versionString = System.getProperty("java.specification.version"); if(versionString.startsWith("1.")) { versionString = versionString.substring(2); } int version = Integer.parseInt(versionString); Method tmpCleaner = null; Method tmpCleanerClean = null; boolean supported; Unsafe tmpUnsafe = null; if (version < 9) { try { tmpCleaner = getAccesibleMethod("java.nio.DirectByteBuffer", "cleaner"); tmpCleanerClean = getAccesibleMethod("sun.misc.Cleaner", "clean"); supported = true; } catch (Throwable t) { UndertowLogger.ROOT_LOGGER.directBufferDeallocatorInitializationFailed(t); supported = false; } } else { try { tmpUnsafe = getUnsafe(); tmpCleanerClean = getDeclaredMethod(tmpUnsafe, "invokeCleaner", ByteBuffer.class); supported = true; } catch (Throwable t) { UndertowLogger.ROOT_LOGGER.directBufferDeallocatorInitializationFailed(t); supported = false; } } SUPPORTED = supported; cleaner = tmpCleaner; cleanerClean = tmpCleanerClean; UNSAFE = tmpUnsafe; } private DirectByteBufferDeallocator() { // Utility Class } /** * Attempts to deallocate the underlying direct memory. * This is a no-op for buffers where {@link ByteBuffer#isDirect()} returns false. * * @param buffer to deallocate */ public static void free(ByteBuffer buffer) { if (SUPPORTED && buffer != null && buffer.isDirect()) { try { if (UNSAFE != null) { //use the JDK9 method cleanerClean.invoke(UNSAFE, buffer); } else { Object cleaner = DirectByteBufferDeallocator.cleaner.invoke(buffer); cleanerClean.invoke(cleaner); } } catch (Throwable t) { UndertowLogger.ROOT_LOGGER.directBufferDeallocationFailed(t); } } } private static Unsafe getUnsafe() { if (System.getSecurityManager() != null) { return AccessController.doPrivileged(new PrivilegedAction() { public Unsafe run() { return getUnsafe0(); } }); } return getUnsafe0(); } private static Unsafe getUnsafe0() { try { Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true); return (Unsafe) theUnsafe.get(null); } catch (Throwable t) { throw new RuntimeException("JDK did not allow accessing unsafe", t); } } private static Method getAccesibleMethod(String className, String methodName) { if (System.getSecurityManager() != null) { return AccessController.doPrivileged(new PrivilegedAction() { @Override public Method run() { return getAccesibleMethod0(className, methodName); } }); } return getAccesibleMethod0(className, methodName); } private static Method getAccesibleMethod0(String className, String methodName) { try { Method method = Class.forName(className).getMethod(methodName); method.setAccessible(true); return method; } catch (Throwable t) { throw new RuntimeException("JDK did not allow accessing method", t); } } private static Method getDeclaredMethod(Unsafe tmpUnsafe, String methodName, Class... parameterTypes) { if (System.getSecurityManager() != null) { return AccessController.doPrivileged(new PrivilegedAction() { @Override public Method run() { return getDeclaredMethod0(tmpUnsafe, methodName, parameterTypes); } }); } return getDeclaredMethod0(tmpUnsafe, methodName, parameterTypes); } private static Method getDeclaredMethod0(Unsafe tmpUnsafe, String methodName, Class... parameterTypes) { try { Method method = tmpUnsafe.getClass().getDeclaredMethod(methodName, parameterTypes); method.setAccessible(true); return method; } catch (Throwable t) { throw new RuntimeException("JDK did not allow accessing method", t); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/ExchangeCompletionListener.java000066400000000000000000000036611420065311100317750ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; /** * Listener interface for events that are run at the completion of a request/response * cycle (i.e. when the request has been completely read, and the response has been fully written). * * At this point it is too late to modify the exchange further. * * Implementations are required invoke {@link NextListener#proceed()} to allow other listeners to release * resources, even in failure scenarios. This chain allows transfer of request ownership between * listeners in order to complete using non-blocking mechanisms, and must not be used to conditionally * proceed. * * Completion listeners are invoked in reverse order. * * @author Stuart Douglas */ public interface ExchangeCompletionListener { void exchangeEvent(final HttpServerExchange exchange, final NextListener nextListener); interface NextListener { /** * Invokes the next {@link ExchangeCompletionListener listener}. This must be executed by * every {@link ExchangeCompletionListener} implementation, and may be invoked from a * different thread as a callback. Failure to proceed will cause resource leaks, and * potentially cause requests to hang. */ void proceed(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/HandlerWrapper.java000066400000000000000000000016621420065311100274300ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; /** * Interface that can be used to wrap the handler chains, adding additional handlers. * * @author Stuart Douglas */ public interface HandlerWrapper { HttpHandler wrap(HttpHandler handler); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/HttpHandler.java000066400000000000000000000022141420065311100267210ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; /** * A handler for an HTTP request. The request handler must eventually either call another handler or end the exchange. * * * @author David M. Lloyd */ public interface HttpHandler { /** * Handle the request. * * @param exchange the HTTP request/response exchange * */ void handleRequest(HttpServerExchange exchange) throws Exception; } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/HttpServerExchange.java000066400000000000000000002636411420065311100302720ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.UndertowOptions; import io.undertow.channels.DetachableStreamSinkChannel; import io.undertow.channels.DetachableStreamSourceChannel; import io.undertow.conduits.EmptyStreamSourceConduit; import io.undertow.connector.PooledByteBuffer; import io.undertow.io.AsyncReceiverImpl; import io.undertow.io.AsyncSenderImpl; import io.undertow.io.BlockingReceiverImpl; import io.undertow.io.BlockingSenderImpl; import io.undertow.io.Receiver; import io.undertow.io.Sender; import io.undertow.io.UndertowInputStream; import io.undertow.io.UndertowOutputStream; import io.undertow.security.api.SecurityContext; import io.undertow.server.handlers.Cookie; import io.undertow.util.AbstractAttachable; import io.undertow.util.AttachmentKey; import io.undertow.util.ConduitFactory; import io.undertow.util.Cookies; import io.undertow.util.HeaderMap; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.Methods; import io.undertow.util.NetworkUtils; import io.undertow.util.Protocols; import io.undertow.util.Rfc6265CookieSupport; import io.undertow.util.StatusCodes; import org.jboss.logging.Logger; import org.xnio.Buffers; import org.xnio.ChannelExceptionHandler; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.XnioIoThread; import org.xnio.channels.Channels; import org.xnio.channels.Configurable; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.Conduit; import org.xnio.conduits.ConduitStreamSinkChannel; import org.xnio.conduits.ConduitStreamSourceChannel; import org.xnio.conduits.StreamSinkConduit; import org.xnio.conduits.StreamSourceConduit; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.Channel; import java.nio.channels.FileChannel; import java.util.ArrayDeque; import java.util.Deque; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import static org.xnio.Bits.allAreSet; import static org.xnio.Bits.anyAreClear; import static org.xnio.Bits.anyAreSet; import static org.xnio.Bits.intBitMask; /** * An HTTP server request/response exchange. An instance of this class is constructed as soon as the request headers are * fully parsed. * * @author David M. Lloyd * @author Richard Opalka */ public final class HttpServerExchange extends AbstractAttachable { // immutable state private static final Logger log = Logger.getLogger(HttpServerExchange.class); private static final RuntimePermission SET_SECURITY_CONTEXT = new RuntimePermission("io.undertow.SET_SECURITY_CONTEXT"); private static final String ISO_8859_1 = "ISO-8859-1"; private static final String HTTPS = "https"; /** * The HTTP reason phrase to send. This is an attachment rather than a field as it is rarely used. If this is not set * a generic description from the RFC is used instead. */ private static final AttachmentKey REASON_PHRASE = AttachmentKey.create(String.class); /** * The attachment key that buffered request data is attached under. */ static final AttachmentKey BUFFERED_REQUEST_DATA = AttachmentKey.create(PooledByteBuffer[].class); /** * Attachment key that can be used to hold additional request attributes */ public static final AttachmentKey> REQUEST_ATTRIBUTES = AttachmentKey.create(Map.class); /** * Attachment key that can be used to hold a remotely authenticated user */ public static final AttachmentKey REMOTE_USER = AttachmentKey.create(String.class); /** * Attachment key that can be used as a flag of secure attribute */ public static final AttachmentKey SECURE_REQUEST = AttachmentKey.create(Boolean.class); private final ServerConnection connection; private final HeaderMap requestHeaders; private final HeaderMap responseHeaders; private int exchangeCompletionListenersCount = 0; private ExchangeCompletionListener[] exchangeCompleteListeners; private DefaultResponseListener[] defaultResponseListeners; private Map> queryParameters; private Map> pathParameters; private DelegatingIterable requestCookies; private DelegatingIterable responseCookies; private Map deprecatedRequestCookies; private Map deprecatedResponseCookies; /** * The actual response channel. May be null if it has not been created yet. */ private WriteDispatchChannel responseChannel; /** * The actual request channel. May be null if it has not been created yet. */ protected ReadDispatchChannel requestChannel; private BlockingHttpExchange blockingHttpExchange; private HttpString protocol; /** * The security context */ private SecurityContext securityContext; // mutable state private int state = 200; private HttpString requestMethod = HttpString.EMPTY; private String requestScheme; /** * The original request URI. This will include the host name if it was specified by the client. *

* This is not decoded in any way, and does not include the query string. *

* Examples: * GET http://localhost:8080/myFile.jsf?foo=bar HTTP/1.1 -> 'http://localhost:8080/myFile.jsf' * POST /my+File.jsf?foo=bar HTTP/1.1 -> '/my+File.jsf' */ private String requestURI; /** * The request path. This will be decoded by the server, and does not include the query string. *

* This path is not canonicalised, so care must be taken to ensure that escape attacks are not possible. *

* Examples: * GET http://localhost:8080/b/../my+File.jsf?foo=bar HTTP/1.1 -> '/b/../my+File.jsf' * POST /my+File.jsf?foo=bar HTTP/1.1 -> '/my File.jsf' */ private String requestPath; /** * The remaining unresolved portion of request path. If a {@link io.undertow.server.handlers.CanonicalPathHandler} is * installed this will be canonicalised. *

* Initially this will be equal to {@link #requestPath}, however it will be modified as handlers resolve the path. */ private String relativePath; /** * The resolved part of the canonical path. */ private String resolvedPath = ""; /** * the query string */ private String queryString = ""; private int requestWrapperCount = 0; private ConduitWrapper[] requestWrappers; //we don't allocate these by default, as for get requests they are not used private int responseWrapperCount = 0; private ConduitWrapper[] responseWrappers; private Sender sender; private Receiver receiver; private long requestStartTime = -1; /** * The maximum entity size. This can be modified before the request stream is obtained, however once the request * stream is obtained this cannot be modified further. *

* The default value for this is determined by the {@link io.undertow.UndertowOptions#MAX_ENTITY_SIZE} option. A value * of 0 indicates that this is unbounded. *

* In case of multipart handling, this will default to {@link io.undertow.UndertowOptions#MULTIPART_MAX_ENTITY_SIZE} *

* If this entity size is exceeded the request channel will be forcibly closed. *

* TODO: integrate this with HTTP 100-continue responses, to make it possible to send a 417 rather than just forcibly * closing the channel. * * @see io.undertow.UndertowOptions#MAX_ENTITY_SIZE * @see io.undertow.UndertowOptions#MULTIPART_MAX_ENTITY_SIZE */ private long maxEntitySize; /** * When the call stack return this task will be executed by the executor specified in {@link #dispatchExecutor}. * If the executor is null then it will be executed by the XNIO worker. */ private Runnable dispatchTask; /** * The executor that is to be used to dispatch the {@link #dispatchTask}. Note that this is not cleared * between dispatches, so once a request has been dispatched once then all subsequent dispatches will use * the same executor. */ private Executor dispatchExecutor; /** * The number of bytes that have been sent to the remote client. This does not include headers, * only the entity body, and does not take any transfer or content encoding into account. */ private long responseBytesSent = 0; private static final int MASK_RESPONSE_CODE = intBitMask(0, 9); /** * Flag that is set when the response sending begins */ private static final int FLAG_RESPONSE_SENT = 1 << 10; /** * Flag that is sent when the response has been fully written and flushed. */ private static final int FLAG_RESPONSE_TERMINATED = 1 << 11; /** * Flag that is set once the request has been fully read. For zero * length requests this is set immediately. */ private static final int FLAG_REQUEST_TERMINATED = 1 << 12; /** * Flag that is set if this is a persistent connection, and the * connection should be re-used. */ private static final int FLAG_PERSISTENT = 1 << 14; /** * If this flag is set it means that the request has been dispatched, * and will not be ending when the call stack returns. *

* This could be because it is being dispatched to a worker thread from * an IO thread, or because resume(Reads/Writes) has been called. */ private static final int FLAG_DISPATCHED = 1 << 15; /** * Flag that is set if the {@link #requestURI} field contains the hostname. */ private static final int FLAG_URI_CONTAINS_HOST = 1 << 16; /** * If this flag is set then the request is current running through a * handler chain. *

* This will be true most of the time, this only time this will return * false is when performing async operations outside the scope of a call to * {@link Connectors#executeRootHandler(HttpHandler, HttpServerExchange)}, * such as when performing async IO. *

* If this is true then when the call stack returns the exchange will either be dispatched, * or the exchange will be ended. */ private static final int FLAG_IN_CALL = 1 << 17; /** * Flag that indicates that reads should be resumed when the call stack returns. */ private static final int FLAG_SHOULD_RESUME_READS = 1 << 18; /** * Flag that indicates that writes should be resumed when the call stack returns */ private static final int FLAG_SHOULD_RESUME_WRITES = 1 << 19; /** * Flag that indicates that the request channel has been reset, and {@link #getRequestChannel()} can be called again */ private static final int FLAG_REQUEST_RESET= 1 << 20; /** * The source address for the request. If this is null then the actual source address from the channel is used */ private InetSocketAddress sourceAddress; /** * The destination address for the request. If this is null then the actual source address from the channel is used */ private InetSocketAddress destinationAddress; public HttpServerExchange(final ServerConnection connection, long maxEntitySize) { this(connection, new HeaderMap(), new HeaderMap(), maxEntitySize); } public HttpServerExchange(final ServerConnection connection) { this(connection, 0); } public HttpServerExchange(final ServerConnection connection, final HeaderMap requestHeaders, final HeaderMap responseHeaders, long maxEntitySize) { this.connection = connection; this.maxEntitySize = maxEntitySize; this.requestHeaders = requestHeaders; this.responseHeaders = responseHeaders; } /** * Get the request protocol string. Normally this is one of the strings listed in {@link Protocols}. * * @return the request protocol string */ public HttpString getProtocol() { return protocol; } /** * Sets the http protocol * * @param protocol */ public HttpServerExchange setProtocol(final HttpString protocol) { this.protocol = protocol; return this; } /** * Determine whether this request conforms to HTTP 0.9. * * @return {@code true} if the request protocol is equal to {@link Protocols#HTTP_0_9}, {@code false} otherwise */ public boolean isHttp09() { return protocol.equals(Protocols.HTTP_0_9); } /** * Determine whether this request conforms to HTTP 1.0. * * @return {@code true} if the request protocol is equal to {@link Protocols#HTTP_1_0}, {@code false} otherwise */ public boolean isHttp10() { return protocol.equals(Protocols.HTTP_1_0); } /** * Determine whether this request conforms to HTTP 1.1. * * @return {@code true} if the request protocol is equal to {@link Protocols#HTTP_1_1}, {@code false} otherwise */ public boolean isHttp11() { return protocol.equals(Protocols.HTTP_1_1); } public boolean isSecure() { Boolean secure = getAttachment(SECURE_REQUEST); if(secure != null && secure) { return true; } String scheme = getRequestScheme(); if (scheme != null && scheme.equalsIgnoreCase(HTTPS)) { return true; } return false; } /** * Get the HTTP request method. Normally this is one of the strings listed in {@link io.undertow.util.Methods}. * * @return the HTTP request method */ public HttpString getRequestMethod() { return requestMethod; } /** * Set the HTTP request method. * * @param requestMethod the HTTP request method */ public HttpServerExchange setRequestMethod(final HttpString requestMethod) { this.requestMethod = requestMethod; return this; } /** * Get the request URI scheme. Normally this is one of {@code http} or {@code https}. * * @return the request URI scheme */ public String getRequestScheme() { return requestScheme; } /** * Set the request URI scheme. * * @param requestScheme the request URI scheme */ public HttpServerExchange setRequestScheme(final String requestScheme) { this.requestScheme = requestScheme; return this; } /** * The original request URI. This will include the host name, protocol etc * if it was specified by the client. *

* This is not decoded in any way, and does not include the query string. *

* Examples: * GET http://localhost:8080/myFile.jsf?foo=bar HTTP/1.1 -> 'http://localhost:8080/myFile.jsf' * POST /my+File.jsf?foo=bar HTTP/1.1 -> '/my+File.jsf' */ public String getRequestURI() { return requestURI; } /** * Sets the request URI * * @param requestURI The new request URI */ public HttpServerExchange setRequestURI(final String requestURI) { this.requestURI = requestURI; return this; } /** * Sets the request URI * * @param requestURI The new request URI * @param containsHost If this is true the request URI contains the host part */ public HttpServerExchange setRequestURI(final String requestURI, boolean containsHost) { this.requestURI = requestURI; if (containsHost) { this.state |= FLAG_URI_CONTAINS_HOST; } else { this.state &= ~FLAG_URI_CONTAINS_HOST; } return this; } /** * If a request was submitted to the server with a full URI instead of just a path this * will return true. For example: *

* GET http://localhost:8080/b/../my+File.jsf?foo=bar HTTP/1.1 -> true * POST /my+File.jsf?foo=bar HTTP/1.1 -> false * * @return true If the request URI contains the host part of the URI */ public boolean isHostIncludedInRequestURI() { return anyAreSet(state, FLAG_URI_CONTAINS_HOST); } /** * The request path. This will be decoded by the server, and does not include the query string. *

* This path is not canonicalised, so care must be taken to ensure that escape attacks are not possible. *

* Examples: * GET http://localhost:8080/b/../my+File.jsf?foo=bar HTTP/1.1 -> '/b/../my+File.jsf' * POST /my+File.jsf?foo=bar HTTP/1.1 -> '/my File.jsf' */ public String getRequestPath() { return requestPath; } /** * Set the request URI path. * * @param requestPath the request URI path */ public HttpServerExchange setRequestPath(final String requestPath) { this.requestPath = requestPath; return this; } /** * Get the request relative path. This is the path which should be evaluated by the current handler. *

* If the {@link io.undertow.server.handlers.CanonicalPathHandler} is installed in the current chain * then this path with be canonicalized * * @return the request relative path */ public String getRelativePath() { return relativePath; } /** * Set the request relative path. * * @param relativePath the request relative path */ public HttpServerExchange setRelativePath(final String relativePath) { this.relativePath = relativePath; return this; } /** * Get the resolved path. * * @return the resolved path */ public String getResolvedPath() { return resolvedPath; } /** * Set the resolved path. * * @param resolvedPath the resolved path */ public HttpServerExchange setResolvedPath(final String resolvedPath) { this.resolvedPath = resolvedPath; return this; } /** * * @return The query string, without the leading ? */ public String getQueryString() { return queryString; } /** * Set query string. Leading ? char will be removed automatically. */ public HttpServerExchange setQueryString(final String queryString) { // Clean leading ? if( queryString.length() > 0 && queryString.charAt(0) == '?' ) { this.queryString = queryString.substring(1); } else { this.queryString = queryString; } return this; } /** * Reconstructs the complete URL as seen by the user. This includes scheme, host name etc, * but does not include query string. *

* This is not decoded. */ public String getRequestURL() { if (isHostIncludedInRequestURI()) { return getRequestURI(); } else { return getRequestScheme() + "://" + getHostAndPort() + getRequestURI(); } } /** * Returns the request charset. If none was explicitly specified it will return * "ISO-8859-1", which is the default charset for HTTP requests. * * @return The character encoding */ public String getRequestCharset() { return extractCharset(requestHeaders); } /** * Returns the response charset. If none was explicitly specified it will return * "ISO-8859-1", which is the default charset for HTTP requests. * * @return The character encoding */ public String getResponseCharset() { HeaderMap headers = responseHeaders; return extractCharset(headers); } private String extractCharset(HeaderMap headers) { String contentType = headers.getFirst(Headers.CONTENT_TYPE); if (contentType != null) { String value = Headers.extractQuotedValueFromHeader(contentType, "charset"); if (value != null) { return value; } } return ISO_8859_1; } /** * Return the host that this request was sent to, in general this will be the * value of the Host header, minus the port specifier. *

* If this resolves to an IPv6 address it will not be enclosed by square brackets. * Care must be taken when constructing URLs based on this method to ensure IPv6 URLs * are handled correctly. * * @return The host part of the destination address */ public String getHostName() { String host = requestHeaders.getFirst(Headers.HOST); if (host == null || "".equals(host.trim())) { host = getDestinationAddress().getHostString(); } else { if (host.startsWith("[")) { host = host.substring(1, host.indexOf(']')); } else if (host.indexOf(':') != -1) { host = host.substring(0, host.indexOf(':')); } } return host; } /** * Return the host, and also the port if this request was sent to a non-standard port. In general * this will just be the value of the Host header. *

* If this resolves to an IPv6 address it *will* be enclosed by square brackets. The return * value of this method is suitable for inclusion in a URL. * * @return The host and port part of the destination address */ public String getHostAndPort() { String host = requestHeaders.getFirst(Headers.HOST); if (host == null || "".equals(host.trim())) { InetSocketAddress address = getDestinationAddress(); host = NetworkUtils.formatPossibleIpv6Address(address.getHostString()); int port = address.getPort(); if (!((getRequestScheme().equals("http") && port == 80) || (getRequestScheme().equals("https") && port == 443))) { host = host + ":" + port; } } return host; } /** * Return the port that this request was sent to. In general this will be the value of the Host * header, minus the host name. * * @return The port part of the destination address */ public int getHostPort() { String host = requestHeaders.getFirst(Headers.HOST); if (host != null) { //for ipv6 addresses we make sure we take out the first part, which can have multiple occurrences of : final int colonIndex; if (host.startsWith("[")) { colonIndex = host.indexOf(':', host.indexOf(']')); } else { colonIndex = host.indexOf(':'); } if (colonIndex != -1) { try { return Integer.parseInt(host.substring(colonIndex + 1)); } catch (NumberFormatException ignore) {} } if (getRequestScheme().equals("https")) { return 443; } else if (getRequestScheme().equals("http")) { return 80; } } return getDestinationAddress().getPort(); } /** * Get the underlying HTTP connection. * * @return the underlying HTTP connection */ public ServerConnection getConnection() { return connection; } public boolean isPersistent() { return anyAreSet(state, FLAG_PERSISTENT); } /** * * @return true If the current thread in the IO thread for the exchange */ public boolean isInIoThread() { return getIoThread() == Thread.currentThread(); } /** * * @return True if this exchange represents an upgrade response */ public boolean isUpgrade() { return getStatusCode() == StatusCodes.SWITCHING_PROTOCOLS; } /** * * @return The number of bytes sent in the entity body */ public long getResponseBytesSent() { if(Connectors.isEntityBodyAllowed(this) && !getRequestMethod().equals(Methods.HEAD)) { return responseBytesSent; } else { return 0; //body is not allowed, even if we attempt to write it will be ignored } } /** * Updates the number of response bytes sent. Used when compression is in use * @param bytes The number of bytes to increase the response size by. May be negative */ void updateBytesSent(long bytes) { if(Connectors.isEntityBodyAllowed(this) && !getRequestMethod().equals(Methods.HEAD)) { responseBytesSent += bytes; } } public HttpServerExchange setPersistent(final boolean persistent) { if (persistent) { this.state = this.state | FLAG_PERSISTENT; } else { this.state = this.state & ~FLAG_PERSISTENT; } return this; } public boolean isDispatched() { return anyAreSet(state, FLAG_DISPATCHED); } public HttpServerExchange unDispatch() { state &= ~FLAG_DISPATCHED; dispatchTask = null; return this; } /** * {@link #dispatch(Executor, Runnable)} should be used instead of this method, as it is hard to use safely. * * Use {@link io.undertow.util.SameThreadExecutor#INSTANCE} if you do not want to dispatch to another thread. * * @return this exchange */ @Deprecated public HttpServerExchange dispatch() { state |= FLAG_DISPATCHED; return this; } /** * Dispatches this request to the XNIO worker thread pool. Once the call stack returns * the given runnable will be submitted to the executor. *

* In general handlers should first check the value of {@link #isInIoThread()} before * calling this method, and only dispatch if the request is actually running in the IO * thread. * * @param runnable The task to run * @throws IllegalStateException If this exchange has already been dispatched */ public HttpServerExchange dispatch(final Runnable runnable) { dispatch(null, runnable); return this; } /** * Dispatches this request to the given executor. Once the call stack returns * the given runnable will be submitted to the executor. *

* In general handlers should first check the value of {@link #isInIoThread()} before * calling this method, and only dispatch if the request is actually running in the IO * thread. * * @param runnable The task to run * @throws IllegalStateException If this exchange has already been dispatched */ public HttpServerExchange dispatch(final Executor executor, final Runnable runnable) { if (isInCall()) { if (executor != null) { this.dispatchExecutor = executor; } state |= FLAG_DISPATCHED; if(anyAreSet(state, FLAG_SHOULD_RESUME_READS | FLAG_SHOULD_RESUME_WRITES)) { throw UndertowMessages.MESSAGES.resumedAndDispatched(); } this.dispatchTask = runnable; } else { if (executor == null) { getConnection().getWorker().execute(runnable); } else { executor.execute(runnable); } } return this; } public HttpServerExchange dispatch(final HttpHandler handler) { dispatch(null, handler); return this; } public HttpServerExchange dispatch(final Executor executor, final HttpHandler handler) { final Runnable runnable = new Runnable() { @Override public void run() { Connectors.executeRootHandler(handler, HttpServerExchange.this); } }; dispatch(executor, runnable); return this; } /** * Sets the executor that is used for dispatch operations where no executor is specified. * * @param executor The executor to use */ public HttpServerExchange setDispatchExecutor(final Executor executor) { if (executor == null) { dispatchExecutor = null; } else { dispatchExecutor = executor; } return this; } /** * Gets the current executor that is used for dispatch operations. This may be null * * @return The current dispatch executor */ public Executor getDispatchExecutor() { return dispatchExecutor; } /** * @return The current dispatch task */ Runnable getDispatchTask() { return dispatchTask; } boolean isInCall() { return anyAreSet(state, FLAG_IN_CALL); } HttpServerExchange setInCall(boolean value) { if (value) { state |= FLAG_IN_CALL; } else { state &= ~FLAG_IN_CALL; } return this; } /** * Upgrade the channel to a raw socket. This method set the response code to 101, and then marks both the * request and response as terminated, which means that once the current request is completed the raw channel * can be obtained from {@link io.undertow.server.protocol.http.HttpServerConnection#getChannel()} * * @throws IllegalStateException if a response or upgrade was already sent, or if the request body is already being * read */ public HttpServerExchange upgradeChannel(final HttpUpgradeListener listener) { if (!connection.isUpgradeSupported()) { throw UndertowMessages.MESSAGES.upgradeNotSupported(); } if(!getRequestHeaders().contains(Headers.UPGRADE)) { throw UndertowMessages.MESSAGES.notAnUpgradeRequest(); } UndertowLogger.REQUEST_LOGGER.debugf("Upgrading request %s", this); connection.setUpgradeListener(listener); setStatusCode(StatusCodes.SWITCHING_PROTOCOLS); getResponseHeaders().put(Headers.CONNECTION, Headers.UPGRADE_STRING); return this; } /** * Upgrade the channel to a raw socket. This method set the response code to 101, and then marks both the * request and response as terminated, which means that once the current request is completed the raw channel * can be obtained from {@link io.undertow.server.protocol.http.HttpServerConnection#getChannel()} * * @param productName the product name to report to the client * @throws IllegalStateException if a response or upgrade was already sent, or if the request body is already being * read */ public HttpServerExchange upgradeChannel(String productName, final HttpUpgradeListener listener) { if (!connection.isUpgradeSupported()) { throw UndertowMessages.MESSAGES.upgradeNotSupported(); } UndertowLogger.REQUEST_LOGGER.debugf("Upgrading request %s", this); connection.setUpgradeListener(listener); setStatusCode(StatusCodes.SWITCHING_PROTOCOLS); final HeaderMap headers = getResponseHeaders(); headers.put(Headers.UPGRADE, productName); headers.put(Headers.CONNECTION, Headers.UPGRADE_STRING); return this; } /** * * @param connectListener * @return */ public HttpServerExchange acceptConnectRequest(HttpUpgradeListener connectListener) { if(!getRequestMethod().equals(Methods.CONNECT)) { throw UndertowMessages.MESSAGES.notAConnectRequest(); } connection.setConnectListener(connectListener); return this; } public HttpServerExchange addExchangeCompleteListener(final ExchangeCompletionListener listener) { if(isComplete() || this.exchangeCompletionListenersCount == -1) { throw UndertowMessages.MESSAGES.exchangeAlreadyComplete(); } final int exchangeCompletionListenersCount = this.exchangeCompletionListenersCount++; ExchangeCompletionListener[] exchangeCompleteListeners = this.exchangeCompleteListeners; if (exchangeCompleteListeners == null || exchangeCompleteListeners.length == exchangeCompletionListenersCount) { ExchangeCompletionListener[] old = exchangeCompleteListeners; this.exchangeCompleteListeners = exchangeCompleteListeners = new ExchangeCompletionListener[exchangeCompletionListenersCount + 2]; if(old != null) { System.arraycopy(old, 0, exchangeCompleteListeners, 0, exchangeCompletionListenersCount); } } exchangeCompleteListeners[exchangeCompletionListenersCount] = listener; return this; } public HttpServerExchange addDefaultResponseListener(final DefaultResponseListener listener) { int i = 0; if(defaultResponseListeners == null) { defaultResponseListeners = new DefaultResponseListener[2]; } else { while (i != defaultResponseListeners.length && defaultResponseListeners[i] != null) { ++i; } if (i == defaultResponseListeners.length) { DefaultResponseListener[] old = defaultResponseListeners; defaultResponseListeners = new DefaultResponseListener[defaultResponseListeners.length + 2]; System.arraycopy(old, 0, defaultResponseListeners, 0, old.length); } } defaultResponseListeners[i] = listener; return this; } /** * Get the source address of the HTTP request. * * @return the source address of the HTTP request */ public InetSocketAddress getSourceAddress() { if (sourceAddress != null) { return sourceAddress; } return connection.getPeerAddress(InetSocketAddress.class); } /** * Sets the source address of the HTTP request. If this is not explicitly set * the actual source address of the channel is used. * * @param sourceAddress The address */ public HttpServerExchange setSourceAddress(InetSocketAddress sourceAddress) { this.sourceAddress = sourceAddress; return this; } /** * Get the destination address of the HTTP request. * * @return the destination address of the HTTP request */ public InetSocketAddress getDestinationAddress() { if (destinationAddress != null) { return destinationAddress; } return connection.getLocalAddress(InetSocketAddress.class); } /** * Sets the destination address of the HTTP request. If this is not explicitly set * the actual destination address of the channel is used. * * @param destinationAddress The address */ public HttpServerExchange setDestinationAddress(InetSocketAddress destinationAddress) { this.destinationAddress = destinationAddress; return this; } /** * Get the request headers. * * @return the request headers */ public HeaderMap getRequestHeaders() { return requestHeaders; } /** * @return The content length of the request, or -1 if it has not been set */ public long getRequestContentLength() { String contentLengthString = requestHeaders.getFirst(Headers.CONTENT_LENGTH); if (contentLengthString == null) { return -1; } return Long.parseLong(contentLengthString); } /** * Get the response headers. * * @return the response headers */ public HeaderMap getResponseHeaders() { return responseHeaders; } /** * @return The content length of the response, or -1 if it has not been set */ public long getResponseContentLength() { String contentLengthString = responseHeaders.getFirst(Headers.CONTENT_LENGTH); if (contentLengthString == null) { return -1; } return Long.parseLong(contentLengthString); } /** * Sets the response content length * * @param length The content length */ public HttpServerExchange setResponseContentLength(long length) { if (length == -1) { responseHeaders.remove(Headers.CONTENT_LENGTH); } else { responseHeaders.put(Headers.CONTENT_LENGTH, Long.toString(length)); } return this; } /** * Returns a mutable map of query parameters. * * @return The query parameters */ public Map> getQueryParameters() { if (queryParameters == null) { queryParameters = new TreeMap<>(); } return queryParameters; } public HttpServerExchange addQueryParam(final String name, final String param) { if (queryParameters == null) { queryParameters = new TreeMap<>(); } Deque list = queryParameters.get(name); if (list == null) { queryParameters.put(name, list = new ArrayDeque<>(2)); } list.add(param); return this; } /** * Returns a mutable map of path parameters * * @return The path parameters */ public Map> getPathParameters() { if (pathParameters == null) { pathParameters = new TreeMap<>(); } return pathParameters; } public HttpServerExchange addPathParam(final String name, final String param) { if (pathParameters == null) { pathParameters = new TreeMap<>(); } Deque list = pathParameters.get(name); if (list == null) { pathParameters.put(name, list = new ArrayDeque<>(2)); } list.add(param); return this; } /** * @return A mutable map of request cookies * @deprecated use either {@link #requestCookies()} or {@link #getRequestCookie(String)} or {@link #setRequestCookie(Cookie)} methods instead */ @Deprecated public Map getRequestCookies() { if (deprecatedRequestCookies == null) { deprecatedRequestCookies = new MapDelegatingToSet((Set)((DelegatingIterable)requestCookies()).getDelegate()); } return deprecatedRequestCookies; } /** * Sets a request cookie * * @param cookie The cookie */ public HttpServerExchange setRequestCookie(final Cookie cookie) { if (cookie == null) return this; if (getConnection().getUndertowOptions().get(UndertowOptions.ENABLE_RFC6265_COOKIE_VALIDATION, UndertowOptions.DEFAULT_ENABLE_RFC6265_COOKIE_VALIDATION)) { if (cookie.getValue() != null && !cookie.getValue().isEmpty()) { Rfc6265CookieSupport.validateCookieValue(cookie.getValue()); } if (cookie.getPath() != null && !cookie.getPath().isEmpty()) { Rfc6265CookieSupport.validatePath(cookie.getPath()); } if (cookie.getDomain() != null && !cookie.getDomain().isEmpty()) { Rfc6265CookieSupport.validateDomain(cookie.getDomain()); } } ((Set)((DelegatingIterable)requestCookies()).getDelegate()).add(cookie); return this; } public Cookie getRequestCookie(final String name) { if (name == null) return null; for (Cookie cookie : requestCookies()) { if (name.equals(cookie.getName())) { // TODO: QUESTION: Shouldn't we check instead of just name also // TODO requestPath (stored in this exchange request path) and // TODO: domain (stored in Host HTTP header). return cookie; } } return null; } /** * Returns unmodifiable enumeration of request cookies. * @return A read-only enumeration of request cookies */ public Iterable requestCookies() { if (requestCookies == null) { Set requestCookiesParam = new OverridableTreeSet<>(); requestCookies = new DelegatingIterable<>(requestCookiesParam); Cookies.parseRequestCookies( getConnection().getUndertowOptions().get(UndertowOptions.MAX_COOKIES, 200), getConnection().getUndertowOptions().get(UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, false), requestHeaders.get(Headers.COOKIE), requestCookiesParam); } return requestCookies; } /** * Sets a response cookie * * @param cookie The cookie */ public HttpServerExchange setResponseCookie(final Cookie cookie) { if (cookie == null) return this; if (getConnection().getUndertowOptions().get(UndertowOptions.ENABLE_RFC6265_COOKIE_VALIDATION, UndertowOptions.DEFAULT_ENABLE_RFC6265_COOKIE_VALIDATION)) { if (cookie.getValue() != null && !cookie.getValue().isEmpty()) { Rfc6265CookieSupport.validateCookieValue(cookie.getValue()); } if (cookie.getPath() != null && !cookie.getPath().isEmpty()) { Rfc6265CookieSupport.validatePath(cookie.getPath()); } if (cookie.getDomain() != null && !cookie.getDomain().isEmpty()) { Rfc6265CookieSupport.validateDomain(cookie.getDomain()); } } ((Set)((DelegatingIterable)responseCookies()).getDelegate()).add(cookie); return this; } /** * @return A mutable map of response cookies * @deprecated use either {@link #responseCookies()} or {@link #setResponseCookie(Cookie)} methods instead */ @Deprecated public Map getResponseCookies() { if (deprecatedResponseCookies == null) { deprecatedResponseCookies = new MapDelegatingToSet((Set)((DelegatingIterable)responseCookies()).getDelegate()); } return deprecatedResponseCookies; } /** * Returns unmodifiable enumeration of response cookies. * @return A read-only enumeration of response cookies */ public Iterable responseCookies() { if (responseCookies == null) { responseCookies = new DelegatingIterable<>(new OverridableTreeSet<>()); } return responseCookies; } /** * @return true If the response has already been started */ public boolean isResponseStarted() { return allAreSet(state, FLAG_RESPONSE_SENT); } /** * Get the inbound request. If there is no request body, calling this method * may cause the next request to immediately be processed. The {@link StreamSourceChannel#close()} or {@link StreamSourceChannel#shutdownReads()} * method must be called at some point after the request is processed to prevent resource leakage and to allow * the next request to proceed. Any unread content will be discarded. * * @return the channel for the inbound request, or {@code null} if another party already acquired the channel */ public StreamSourceChannel getRequestChannel() { if (requestChannel != null) { if(anyAreSet(state, FLAG_REQUEST_RESET)) { state &= ~FLAG_REQUEST_RESET; return requestChannel; } return null; } if (anyAreSet(state, FLAG_REQUEST_TERMINATED)) { return requestChannel = new ReadDispatchChannel(new ConduitStreamSourceChannel(Configurable.EMPTY, new EmptyStreamSourceConduit(getIoThread()))); } final ConduitWrapper[] wrappers = this.requestWrappers; final ConduitStreamSourceChannel sourceChannel = connection.getSourceChannel(); if (wrappers != null) { this.requestWrappers = null; final WrapperConduitFactory factory = new WrapperConduitFactory<>(wrappers, requestWrapperCount, sourceChannel.getConduit(), this); sourceChannel.setConduit(factory.create()); } return requestChannel = new ReadDispatchChannel(sourceChannel); } void resetRequestChannel() { state |= FLAG_REQUEST_RESET; } public boolean isRequestChannelAvailable() { return requestChannel == null || anyAreSet(state, FLAG_REQUEST_RESET); } /** * Returns true if the completion handler for this exchange has been invoked, and the request is considered * finished. */ public boolean isComplete() { return allAreSet(state, FLAG_REQUEST_TERMINATED | FLAG_RESPONSE_TERMINATED); } /** * Returns true if all data has been read from the request, or if there * was not data. * * @return true if the request is complete */ public boolean isRequestComplete() { PooledByteBuffer[] data = getAttachment(BUFFERED_REQUEST_DATA); if(data != null) { return false; } return allAreSet(state, FLAG_REQUEST_TERMINATED); } /** * @return true if the responses is complete */ public boolean isResponseComplete() { return allAreSet(state, FLAG_RESPONSE_TERMINATED); } /** * Force the codec to treat the request as fully read. Should only be invoked by handlers which downgrade * the socket or implement a transfer coding. */ void terminateRequest() { int oldVal = state; if (allAreSet(oldVal, FLAG_REQUEST_TERMINATED)) { // idempotent return; } if (requestChannel != null) { requestChannel.requestDone(); } this.state = oldVal | FLAG_REQUEST_TERMINATED; if (anyAreSet(oldVal, FLAG_RESPONSE_TERMINATED)) { invokeExchangeCompleteListeners(); } } private void invokeExchangeCompleteListeners() { if (exchangeCompletionListenersCount > 0) { int i = exchangeCompletionListenersCount - 1; ExchangeCompletionListener next = exchangeCompleteListeners[i]; exchangeCompletionListenersCount = -1; next.exchangeEvent(this, new ExchangeCompleteNextListener(exchangeCompleteListeners, this, i)); } else if (exchangeCompletionListenersCount == 0) { exchangeCompletionListenersCount = -1; connection.exchangeComplete(this); } } /** * Get the response channel. The channel must be closed and fully flushed before the next response can be started. * In order to close the channel you must first call {@link org.xnio.channels.StreamSinkChannel#shutdownWrites()}, * and then call {@link org.xnio.channels.StreamSinkChannel#flush()} until it returns true. Alternatively you can * call {@link #endExchange()}, which will close the channel as part of its cleanup. *

* Closing a fixed-length response before the corresponding number of bytes has been written will cause the connection * to be reset and subsequent requests to fail; thus it is important to ensure that the proper content length is * delivered when one is specified. The response channel may not be writable until after the response headers have * been sent. *

* If this method is not called then an empty or default response body will be used, depending on the response code set. *

* The returned channel will begin to write out headers when the first write request is initiated, or when * {@link org.xnio.channels.StreamSinkChannel#shutdownWrites()} is called on the channel with no content being written. * Once the channel is acquired, however, the response code and headers may not be modified. *

* * @return the response channel, or {@code null} if another party already acquired the channel */ public StreamSinkChannel getResponseChannel() { if (responseChannel != null) { return null; } final ConduitWrapper[] wrappers = responseWrappers; this.responseWrappers = null; final ConduitStreamSinkChannel sinkChannel = connection.getSinkChannel(); if (sinkChannel == null) { return null; } if(wrappers != null) { final WrapperStreamSinkConduitFactory factory = new WrapperStreamSinkConduitFactory(wrappers, responseWrapperCount, this, sinkChannel.getConduit()); sinkChannel.setConduit(factory.create()); } else { sinkChannel.setConduit(connection.getSinkConduit(this, sinkChannel.getConduit())); } this.responseChannel = new WriteDispatchChannel(sinkChannel); this.startResponse(); return responseChannel; } /** * Get the response sender. *

* For blocking exchanges this will return a sender that uses the underlying output stream. * * @return the response sender, or {@code null} if another party already acquired the channel or the sender * @see #getResponseChannel() */ public Sender getResponseSender() { if (blockingHttpExchange != null) { return blockingHttpExchange.getSender(); } if (sender != null) { return sender; } return sender = new AsyncSenderImpl(this); } public Receiver getRequestReceiver() { if(blockingHttpExchange != null) { return blockingHttpExchange.getReceiver(); } if(receiver != null) { return receiver; } return receiver = new AsyncReceiverImpl(this); } /** * @return true if {@link #getResponseChannel()} has not been called */ public boolean isResponseChannelAvailable() { return responseChannel == null; } /** * Get the status code. * * @see #getStatusCode() * @return the status code */ @Deprecated public int getResponseCode() { return state & MASK_RESPONSE_CODE; } /** * Change the status code for this response. If not specified, the code will be a {@code 200}. Setting * the status code after the response headers have been transmitted has no effect. * * @see #setStatusCode(int) * @param statusCode the new code * @throws IllegalStateException if a response or upgrade was already sent */ @Deprecated public HttpServerExchange setResponseCode(final int statusCode) { return setStatusCode(statusCode); } /** * Get the status code. * * @return the status code */ public int getStatusCode() { return state & MASK_RESPONSE_CODE; } /** * Change the status code for this response. If not specified, the code will be a {@code 200}. Setting * the status code after the response headers have been transmitted has no effect. * * @param statusCode the new code * @throws IllegalStateException if a response or upgrade was already sent */ public HttpServerExchange setStatusCode(final int statusCode) { if (statusCode < 0 || statusCode > 999) { throw new IllegalArgumentException("Invalid response code"); } int oldVal = state; if (allAreSet(oldVal, FLAG_RESPONSE_SENT)) { throw UndertowMessages.MESSAGES.responseAlreadyStarted(); } if(statusCode >= 500) { if(UndertowLogger.ERROR_RESPONSE.isDebugEnabled()) { UndertowLogger.ERROR_RESPONSE.debugf(new RuntimeException(), "Setting error code %s for exchange %s", statusCode, this); } } this.state = oldVal & ~MASK_RESPONSE_CODE | statusCode & MASK_RESPONSE_CODE; return this; } /** * Sets the HTTP reason phrase. Depending on the protocol this may or may not be honoured. In particular HTTP2 * has removed support for the reason phrase. * * This method should only be used to interact with legacy frameworks that give special meaning to the reason phrase. * * @param message The status message * @return this exchange */ public HttpServerExchange setReasonPhrase(String message) { putAttachment(REASON_PHRASE, message); return this; } /** * * @return The current reason phrase */ public String getReasonPhrase() { return getAttachment(REASON_PHRASE); } /** * Adds a {@link ConduitWrapper} to the request wrapper chain. * * @param wrapper the wrapper */ public HttpServerExchange addRequestWrapper(final ConduitWrapper wrapper) { ConduitWrapper[] wrappers = requestWrappers; if (requestChannel != null) { throw UndertowMessages.MESSAGES.requestChannelAlreadyProvided(); } if (wrappers == null) { wrappers = requestWrappers = new ConduitWrapper[2]; } else if (wrappers.length == requestWrapperCount) { requestWrappers = new ConduitWrapper[wrappers.length + 2]; System.arraycopy(wrappers, 0, requestWrappers, 0, wrappers.length); wrappers = requestWrappers; } wrappers[requestWrapperCount++] = wrapper; return this; } /** * Adds a {@link ConduitWrapper} to the response wrapper chain. * * @param wrapper the wrapper */ public HttpServerExchange addResponseWrapper(final ConduitWrapper wrapper) { ConduitWrapper[] wrappers = responseWrappers; if (responseChannel != null) { throw UndertowMessages.MESSAGES.responseChannelAlreadyProvided(); } if(wrappers == null) { this.responseWrappers = wrappers = new ConduitWrapper[2]; } else if (wrappers.length == responseWrapperCount) { responseWrappers = new ConduitWrapper[wrappers.length + 2]; System.arraycopy(wrappers, 0, responseWrappers, 0, wrappers.length); wrappers = responseWrappers; } wrappers[responseWrapperCount++] = wrapper; return this; } /** * Calling this method puts the exchange in blocking mode, and creates a * {@link BlockingHttpExchange} object to store the streams. *

* When an exchange is in blocking mode the input stream methods become * available, other than that there is presently no major difference * between blocking an non-blocking modes. * * @return The existing blocking exchange, if any */ public BlockingHttpExchange startBlocking() { final BlockingHttpExchange old = this.blockingHttpExchange; blockingHttpExchange = new DefaultBlockingHttpExchange(this); return old; } /** * Calling this method puts the exchange in blocking mode, using the given * blocking exchange as the source of the streams. *

* When an exchange is in blocking mode the input stream methods become * available, other than that there is presently no major difference * between blocking an non-blocking modes. *

* Note that this method may be called multiple times with different * exchange objects, to allow handlers to modify the streams * that are being used. * * @return The existing blocking exchange, if any */ public BlockingHttpExchange startBlocking(final BlockingHttpExchange httpExchange) { final BlockingHttpExchange old = this.blockingHttpExchange; blockingHttpExchange = httpExchange; return old; } /** * Returns true if {@link #startBlocking()} or {@link #startBlocking(BlockingHttpExchange)} has been called. * * @return true If this is a blocking HTTP server exchange */ public boolean isBlocking() { return blockingHttpExchange != null; } /** * @return The input stream * @throws IllegalStateException if {@link #startBlocking()} has not been called */ public InputStream getInputStream() { if (blockingHttpExchange == null) { throw UndertowMessages.MESSAGES.startBlockingHasNotBeenCalled(); } return blockingHttpExchange.getInputStream(); } /** * @return The output stream * @throws IllegalStateException if {@link #startBlocking()} has not been called */ public OutputStream getOutputStream() { if (blockingHttpExchange == null) { throw UndertowMessages.MESSAGES.startBlockingHasNotBeenCalled(); } return blockingHttpExchange.getOutputStream(); } /** * Force the codec to treat the response as fully written. Should only be invoked by handlers which downgrade * the socket or implement a transfer coding. */ HttpServerExchange terminateResponse() { int oldVal = state; if (allAreSet(oldVal, FLAG_RESPONSE_TERMINATED)) { // idempotent return this; } if(responseChannel != null) { responseChannel.responseDone(); } this.state = oldVal | FLAG_RESPONSE_TERMINATED; if (anyAreSet(oldVal, FLAG_REQUEST_TERMINATED)) { invokeExchangeCompleteListeners(); } return this; } /** * @return The request start time using the JVM's high-resolution time source, * in nanoseconds, or -1 if this was not recorded * @see UndertowOptions#RECORD_REQUEST_START_TIME * @see Connectors#setRequestStartTime(HttpServerExchange) */ public long getRequestStartTime() { return requestStartTime; } HttpServerExchange setRequestStartTime(long requestStartTime) { this.requestStartTime = requestStartTime; return this; } /** * Ends the exchange by fully draining the request channel, and flushing the response channel. *

* This can result in handoff to an XNIO worker, so after this method is called the exchange should * not be modified by the caller. *

* If the exchange is already complete this method is a noop */ public HttpServerExchange endExchange() { final int state = this.state; if (allAreSet(state, FLAG_REQUEST_TERMINATED | FLAG_RESPONSE_TERMINATED)) { if(blockingHttpExchange != null) { //we still have to close the blocking exchange in this case, IoUtils.safeClose(blockingHttpExchange); } return this; } if(defaultResponseListeners != null) { int i = defaultResponseListeners.length - 1; while (i >= 0) { DefaultResponseListener listener = defaultResponseListeners[i]; if (listener != null) { defaultResponseListeners[i] = null; try { if (listener.handleDefaultResponse(this)) { return this; } } catch (Throwable e) { UndertowLogger.REQUEST_LOGGER.debug("Exception running default response listener", e); } } i--; } } if (anyAreClear(state, FLAG_REQUEST_TERMINATED)) { connection.terminateRequestChannel(this); } if (blockingHttpExchange != null) { try { //TODO: can we end up in this situation in a IO thread? blockingHttpExchange.close(); } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); IoUtils.safeClose(connection); } catch (Throwable t) { UndertowLogger.REQUEST_IO_LOGGER.handleUnexpectedFailure(t); IoUtils.safeClose(connection); } } //417 means that we are rejecting the request //so the client should not actually send any data if (anyAreClear(state, FLAG_REQUEST_TERMINATED)) { //not really sure what the best thing to do here is //for now we are just going to drain the channel if (requestChannel == null) { getRequestChannel(); } int totalRead = 0; for (; ; ) { try { long read = Channels.drain(requestChannel, Long.MAX_VALUE); totalRead += read; if (read == 0) { //if the response code is 417 this is a rejected continuation request. //however there is a chance the client could have sent the data anyway //so we attempt to drain, and if we have not drained anything then we //assume the server has not sent any data if (getStatusCode() != StatusCodes.EXPECTATION_FAILED || totalRead > 0) { requestChannel.getReadSetter().set(ChannelListeners.drainListener(Long.MAX_VALUE, new ChannelListener() { @Override public void handleEvent(final StreamSourceChannel channel) { if (anyAreClear(state, FLAG_RESPONSE_TERMINATED)) { closeAndFlushResponse(); } } }, new ChannelExceptionHandler() { @Override public void handleException(final StreamSourceChannel channel, final IOException e) { //make sure the listeners have been invoked //unless the connection has been killed this is a no-op terminateRequest(); terminateResponse(); UndertowLogger.REQUEST_LOGGER.debug("Exception draining request stream", e); IoUtils.safeClose(connection); } } )); requestChannel.resumeReads(); return this; } else { break; } } else if (read == -1) { break; } } catch (Throwable t) { if (t instanceof IOException) { UndertowLogger.REQUEST_IO_LOGGER.ioException((IOException) t); } else { UndertowLogger.REQUEST_IO_LOGGER.handleUnexpectedFailure(t); } invokeExchangeCompleteListeners(); IoUtils.safeClose(connection); return this; } } } if (anyAreClear(state, FLAG_RESPONSE_TERMINATED)) { closeAndFlushResponse(); } return this; } private void closeAndFlushResponse() { if(!connection.isOpen()) { //not much point trying to flush //make sure the listeners have been invoked terminateRequest(); terminateResponse(); return; } try { if (isResponseChannelAvailable()) { if(!getRequestMethod().equals(Methods.CONNECT) && !(getRequestMethod().equals(Methods.HEAD) && getResponseHeaders().contains(Headers.CONTENT_LENGTH)) && Connectors.isEntityBodyAllowed(this)) { //according to getResponseHeaders().put(Headers.CONTENT_LENGTH, "0"); } getResponseChannel(); } else if (anyAreClear(state, FLAG_RESPONSE_TERMINATED) && !responseChannel.isOpen()) { // UNDERTOW-1664: Http/2 response channels may be closed prior to the connection. There's // no reason to attempt to flush a response for a closed channel but we must ensure // the listeners have been invoked. invokeExchangeCompleteListeners(); IoUtils.safeClose(connection); return; } responseChannel.shutdownWrites(); if (!responseChannel.flush()) { responseChannel.getWriteSetter().set(ChannelListeners.flushingChannelListener( new ChannelListener() { @Override public void handleEvent(final StreamSinkChannel channel) { channel.suspendWrites(); channel.getWriteSetter().set(null); //defensive programming, should never happen if (anyAreClear(state, FLAG_RESPONSE_TERMINATED)) { //make sure the listeners have been invoked invokeExchangeCompleteListeners(); UndertowLogger.ROOT_LOGGER.responseWasNotTerminated(connection, HttpServerExchange.this); IoUtils.safeClose(connection); } } }, new ChannelExceptionHandler() { @Override public void handleException(final Channel channel, final IOException exception) { //make sure the listeners have been invoked invokeExchangeCompleteListeners(); UndertowLogger.REQUEST_LOGGER.debug("Exception ending request", exception); IoUtils.safeClose(connection); } } )); responseChannel.resumeWrites(); } else { //defensive programming, should never happen if (anyAreClear(state, FLAG_RESPONSE_TERMINATED)) { //make sure the listeners have been invoked invokeExchangeCompleteListeners(); UndertowLogger.ROOT_LOGGER.responseWasNotTerminated(connection, this); IoUtils.safeClose(connection); } } } catch (Throwable t) { if (t instanceof IOException) { UndertowLogger.REQUEST_IO_LOGGER.ioException((IOException) t); } else { UndertowLogger.REQUEST_IO_LOGGER.handleUnexpectedFailure(t); } invokeExchangeCompleteListeners(); IoUtils.safeClose(connection); } } /** * Transmit the response headers. After this method successfully returns, * the response channel may become writable. *

* If this method fails the request and response channels will be closed. *

* This method runs asynchronously. If the channel is writable it will * attempt to write as much of the response header as possible, and then * queue the rest in a listener and return. *

* If future handlers in the chain attempt to write before this is finished * XNIO will just magically sort it out so it works. This is not actually * implemented yet, so we just terminate the connection straight away at * the moment. *

* TODO: make this work properly * * @throws IllegalStateException if the response headers were already sent */ HttpServerExchange startResponse() throws IllegalStateException { int oldVal = state; if (allAreSet(oldVal, FLAG_RESPONSE_SENT)) { throw UndertowMessages.MESSAGES.responseAlreadyStarted(); } this.state = oldVal | FLAG_RESPONSE_SENT; log.tracef("Starting to write response for %s", this); return this; } public XnioIoThread getIoThread() { return connection.getIoThread(); } /** * @return The maximum entity size for this exchange */ public long getMaxEntitySize() { return maxEntitySize; } /** * Sets the max entity size for this exchange. This cannot be modified after the request channel has been obtained. * * @param maxEntitySize The max entity size */ public HttpServerExchange setMaxEntitySize(final long maxEntitySize) { if (!isRequestChannelAvailable()) { throw UndertowMessages.MESSAGES.requestChannelAlreadyProvided(); } this.maxEntitySize = maxEntitySize; connection.maxEntitySizeUpdated(this); return this; } public SecurityContext getSecurityContext() { return securityContext; } public void setSecurityContext(SecurityContext securityContext) { SecurityManager sm = System.getSecurityManager(); if(sm != null) { sm.checkPermission(SET_SECURITY_CONTEXT); } this.securityContext = securityContext; } /** * Adds a listener that will be invoked on response commit * * @param listener The response listener */ public void addResponseCommitListener(final ResponseCommitListener listener) { //technically it is possible to modify the exchange after the response conduit has been created //as the response channel should not be retrieved until it is about to be written to //if we get complaints about this we can add support for it, however it makes the exchange bigger and the connectors more complex addResponseWrapper(new ConduitWrapper() { @Override public StreamSinkConduit wrap(ConduitFactory factory, HttpServerExchange exchange) { listener.beforeCommit(exchange); return factory.create(); } }); } /** * Actually resumes reads or writes, if the relevant method has been called. * * @return true if reads or writes were resumed */ boolean runResumeReadWrite() { boolean ret = false; if(anyAreSet(state, FLAG_SHOULD_RESUME_WRITES)) { responseChannel.runResume(); ret = true; } if(anyAreSet(state, FLAG_SHOULD_RESUME_READS)) { requestChannel.runResume(); ret = true; } return ret; } boolean isResumed() { return anyAreSet(state, FLAG_SHOULD_RESUME_WRITES | FLAG_SHOULD_RESUME_READS); } private static class ExchangeCompleteNextListener implements ExchangeCompletionListener.NextListener { private final ExchangeCompletionListener[] list; private final HttpServerExchange exchange; private int i; ExchangeCompleteNextListener(final ExchangeCompletionListener[] list, final HttpServerExchange exchange, int i) { this.list = list; this.exchange = exchange; this.i = i; } @Override public void proceed() { if (--i >= 0) { final ExchangeCompletionListener next = list[i]; next.exchangeEvent(exchange, this); } else if(i == -1) { exchange.connection.exchangeComplete(exchange); } } } private static class DefaultBlockingHttpExchange implements BlockingHttpExchange { private InputStream inputStream; private UndertowOutputStream outputStream; private Sender sender; private final HttpServerExchange exchange; DefaultBlockingHttpExchange(final HttpServerExchange exchange) { this.exchange = exchange; } public InputStream getInputStream() { if (inputStream == null) { inputStream = new UndertowInputStream(exchange); } return inputStream; } public UndertowOutputStream getOutputStream() { if (outputStream == null) { outputStream = new UndertowOutputStream(exchange); } return outputStream; } @Override public Sender getSender() { if (sender == null) { sender = new BlockingSenderImpl(exchange, getOutputStream()); } return sender; } @Override public void close() throws IOException { try { getInputStream().close(); } finally { getOutputStream().close(); } } @Override public Receiver getReceiver() { return new BlockingReceiverImpl(exchange, getInputStream()); } } /** * Channel implementation that is actually provided to clients of the exchange. *

* We do not provide the underlying conduit channel, as this is shared between requests, so we need to make sure that after this request * is done the the channel cannot affect the next request. *

* It also delays a wakeup/resumesWrites calls until the current call stack has returned, thus ensuring that only 1 thread is * active in the exchange at any one time. */ private class WriteDispatchChannel extends DetachableStreamSinkChannel implements StreamSinkChannel { private boolean wakeup; WriteDispatchChannel(final ConduitStreamSinkChannel delegate) { super(delegate); } @Override protected boolean isFinished() { return allAreSet(state, FLAG_RESPONSE_TERMINATED); } @Override public void resumeWrites() { if (isInCall()) { state |= FLAG_SHOULD_RESUME_WRITES; if(anyAreSet(state, FLAG_DISPATCHED)) { throw UndertowMessages.MESSAGES.resumedAndDispatched(); } } else if(!isFinished()){ delegate.resumeWrites(); } } @Override public void suspendWrites() { state &= ~FLAG_SHOULD_RESUME_WRITES; super.suspendWrites(); } @Override public void wakeupWrites() { if (isFinished()) { return; } if (isInCall()) { wakeup = true; state |= FLAG_SHOULD_RESUME_WRITES; if(anyAreSet(state, FLAG_DISPATCHED)) { throw UndertowMessages.MESSAGES.resumedAndDispatched(); } } else { delegate.wakeupWrites(); } } @Override public boolean isWriteResumed() { return anyAreSet(state, FLAG_SHOULD_RESUME_WRITES) || super.isWriteResumed(); } public void runResume() { if (isWriteResumed()) { if(isFinished()) { invokeListener(); } else { if (wakeup) { wakeup = false; state &= ~FLAG_SHOULD_RESUME_WRITES; delegate.wakeupWrites(); } else { state &= ~FLAG_SHOULD_RESUME_WRITES; delegate.resumeWrites(); } } } else if(wakeup) { wakeup = false; invokeListener(); } } private void invokeListener() { if(writeSetter != null) { super.getIoThread().execute(new Runnable() { @Override public void run() { ChannelListeners.invokeChannelListener(WriteDispatchChannel.this, writeSetter.get()); } }); } } @Override public void awaitWritable() throws IOException { if(Thread.currentThread() == super.getIoThread()) { throw UndertowMessages.MESSAGES.awaitCalledFromIoThread(); } super.awaitWritable(); } @Override public void awaitWritable(long time, TimeUnit timeUnit) throws IOException { if(Thread.currentThread() == super.getIoThread()) { throw UndertowMessages.MESSAGES.awaitCalledFromIoThread(); } super.awaitWritable(time, timeUnit); } @Override public long transferFrom(FileChannel src, long position, long count) throws IOException { long l = super.transferFrom(src, position, count); if(l > 0) { responseBytesSent += l; } return l; } @Override public long transferFrom(StreamSourceChannel source, long count, ByteBuffer throughBuffer) throws IOException { long l = super.transferFrom(source, count, throughBuffer); if(l > 0) { responseBytesSent += l; } return l; } @Override public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { long l = super.write(srcs, offset, length); responseBytesSent += l; return l; } @Override public long write(ByteBuffer[] srcs) throws IOException { long l = super.write(srcs); responseBytesSent += l; return l; } @Override public int writeFinal(ByteBuffer src) throws IOException { int l = super.writeFinal(src); responseBytesSent += l; return l; } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { long l = super.writeFinal(srcs, offset, length); responseBytesSent += l; return l; } @Override public long writeFinal(ByteBuffer[] srcs) throws IOException { long l = super.writeFinal(srcs); responseBytesSent += l; return l; } @Override public int write(ByteBuffer src) throws IOException { int l = super.write(src); responseBytesSent += l; return l; } } /** * Channel implementation that is actually provided to clients of the exchange. We do not provide the underlying * conduit channel, as this will become the next requests conduit channel, so if a thread is still hanging onto this * exchange it can result in problems. *

* It also delays a readResume call until the current call stack has returned, thus ensuring that only 1 thread is * active in the exchange at any one time. *

* It also handles buffered request data. */ private final class ReadDispatchChannel extends DetachableStreamSourceChannel implements StreamSourceChannel { private boolean wakeup = true; private boolean readsResumed = false; ReadDispatchChannel(final ConduitStreamSourceChannel delegate) { super(delegate); } @Override protected boolean isFinished() { return allAreSet(state, FLAG_REQUEST_TERMINATED); } @Override public void resumeReads() { readsResumed = true; if (isInCall()) { state |= FLAG_SHOULD_RESUME_READS; if(anyAreSet(state, FLAG_DISPATCHED)) { throw UndertowMessages.MESSAGES.resumedAndDispatched(); } } else if (!isFinished()) { delegate.resumeReads(); } } public void wakeupReads() { if (isInCall()) { wakeup = true; state |= FLAG_SHOULD_RESUME_READS; if(anyAreSet(state, FLAG_DISPATCHED)) { throw UndertowMessages.MESSAGES.resumedAndDispatched(); } } else { if(isFinished()) { invokeListener(); } else { delegate.wakeupReads(); } } } private void invokeListener() { if(readSetter != null) { super.getIoThread().execute(new Runnable() { @Override public void run() { ChannelListeners.invokeChannelListener(ReadDispatchChannel.this, readSetter.get()); } }); } } public void requestDone() { if(delegate instanceof ConduitStreamSourceChannel) { ((ConduitStreamSourceChannel)delegate).setReadListener(null); ((ConduitStreamSourceChannel)delegate).setCloseListener(null); } else { delegate.getReadSetter().set(null); delegate.getCloseSetter().set(null); } } @Override public long transferTo(long position, long count, FileChannel target) throws IOException { PooledByteBuffer[] buffered = getAttachment(BUFFERED_REQUEST_DATA); if (buffered == null) { return super.transferTo(position, count, target); } return target.transferFrom(this, position, count); } @Override public void awaitReadable() throws IOException { if(Thread.currentThread() == super.getIoThread()) { throw UndertowMessages.MESSAGES.awaitCalledFromIoThread(); } PooledByteBuffer[] buffered = getAttachment(BUFFERED_REQUEST_DATA); if (buffered == null) { super.awaitReadable(); } } @Override public void suspendReads() { readsResumed = false; state &= ~(FLAG_SHOULD_RESUME_READS); super.suspendReads(); } @Override public long transferTo(long count, ByteBuffer throughBuffer, StreamSinkChannel target) throws IOException { PooledByteBuffer[] buffered = getAttachment(BUFFERED_REQUEST_DATA); if (buffered == null) { return super.transferTo(count, throughBuffer, target); } //make sure there is no garbage in throughBuffer throughBuffer.position(0); throughBuffer.limit(0); long copied = 0; for (int i = 0; i < buffered.length; ++i) { PooledByteBuffer pooled = buffered[i]; if (pooled != null) { final ByteBuffer buf = pooled.getBuffer(); if (buf.hasRemaining()) { int res = target.write(buf); if (!buf.hasRemaining()) { pooled.close(); buffered[i] = null; } if (res == 0) { return copied; } else { copied += res; } } else { pooled.close(); buffered[i] = null; } } } removeAttachment(BUFFERED_REQUEST_DATA); if (copied == 0) { return super.transferTo(count, throughBuffer, target); } else { return copied; } } @Override public void awaitReadable(long time, TimeUnit timeUnit) throws IOException { if(Thread.currentThread() == super.getIoThread()) { throw UndertowMessages.MESSAGES.awaitCalledFromIoThread(); } PooledByteBuffer[] buffered = getAttachment(BUFFERED_REQUEST_DATA); if (buffered == null) { super.awaitReadable(time, timeUnit); } } @Override public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { PooledByteBuffer[] buffered = getAttachment(BUFFERED_REQUEST_DATA); if (buffered == null) { return super.read(dsts, offset, length); } long copied = 0; for (int i = 0; i < buffered.length; ++i) { PooledByteBuffer pooled = buffered[i]; if (pooled != null) { final ByteBuffer buf = pooled.getBuffer(); if (buf.hasRemaining()) { copied += Buffers.copy(dsts, offset, length, buf); if (!buf.hasRemaining()) { pooled.close(); buffered[i] = null; } if (!Buffers.hasRemaining(dsts, offset, length)) { return copied; } } else { pooled.close(); buffered[i] = null; } } } removeAttachment(BUFFERED_REQUEST_DATA); if (copied == 0) { return super.read(dsts, offset, length); } else { return copied; } } @Override public long read(ByteBuffer[] dsts) throws IOException { return read(dsts, 0, dsts.length); } @Override public boolean isOpen() { PooledByteBuffer[] buffered = getAttachment(BUFFERED_REQUEST_DATA); if (buffered != null) { return true; } return super.isOpen(); } @Override public void close() throws IOException { PooledByteBuffer[] buffered = getAttachment(BUFFERED_REQUEST_DATA); if (buffered != null) { for (PooledByteBuffer pooled : buffered) { if (pooled != null) { pooled.close(); } } } removeAttachment(BUFFERED_REQUEST_DATA); super.close(); } @Override public boolean isReadResumed() { PooledByteBuffer[] buffered = getAttachment(BUFFERED_REQUEST_DATA); if (buffered != null) { return readsResumed; } if(isFinished()) { return false; } return anyAreSet(state, FLAG_SHOULD_RESUME_READS) || super.isReadResumed(); } @Override public int read(ByteBuffer dst) throws IOException { PooledByteBuffer[] buffered = getAttachment(BUFFERED_REQUEST_DATA); if (buffered == null) { return super.read(dst); } int copied = 0; for (int i = 0; i < buffered.length; ++i) { PooledByteBuffer pooled = buffered[i]; if (pooled != null) { final ByteBuffer buf = pooled.getBuffer(); if (buf.hasRemaining()) { copied += Buffers.copy(dst, buf); if (!buf.hasRemaining()) { pooled.close(); buffered[i] = null; } if (!dst.hasRemaining()) { return copied; } } else { pooled.close(); buffered[i] = null; } } } removeAttachment(BUFFERED_REQUEST_DATA); if (copied == 0) { return super.read(dst); } else { return copied; } } public void runResume() { if (isReadResumed()) { if(isFinished()) { invokeListener(); } else { if (wakeup) { wakeup = false; state &= ~FLAG_SHOULD_RESUME_READS; delegate.wakeupReads(); } else { state &= ~FLAG_SHOULD_RESUME_READS; delegate.resumeReads(); } } } else if(wakeup) { wakeup = false; invokeListener(); } } } public static class WrapperStreamSinkConduitFactory implements ConduitFactory { private final HttpServerExchange exchange; private final ConduitWrapper[] wrappers; private int position; private final StreamSinkConduit first; public WrapperStreamSinkConduitFactory(ConduitWrapper[] wrappers, int wrapperCount, HttpServerExchange exchange, StreamSinkConduit first) { this.wrappers = wrappers; this.exchange = exchange; this.first = first; this.position = wrapperCount - 1; } @Override public StreamSinkConduit create() { if (position == -1) { return exchange.getConnection().getSinkConduit(exchange, first); } else { return wrappers[position--].wrap(this, exchange); } } } public static class WrapperConduitFactory implements ConduitFactory { private final HttpServerExchange exchange; private final ConduitWrapper[] wrappers; private int position; private T first; public WrapperConduitFactory(ConduitWrapper[] wrappers, int wrapperCount, T first, HttpServerExchange exchange) { this.wrappers = wrappers; this.exchange = exchange; this.position = wrapperCount - 1; this.first = first; } @Override public T create() { if (position == -1) { return first; } else { return wrappers[position--].wrap(this, exchange); } } } @Override public String toString() { return "HttpServerExchange{ " + getRequestMethod().toString() + " " + getRequestURI() + '}'; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/HttpUpgradeListener.java000066400000000000000000000022531420065311100304440ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import org.xnio.StreamConnection; /** * Listener that is used to perform a HTTP upgrade. * * @author Stuart Douglas */ public interface HttpUpgradeListener { /** * Method that is called once the upgrade is complete. * * @param streamConnection The connection that can be used to send or receive data * @param exchange */ void handleUpgrade(final StreamConnection streamConnection, HttpServerExchange exchange); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/JvmRouteHandler.java000066400000000000000000000102121420065311100275520ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import io.undertow.server.handlers.Cookie; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.ConduitFactory; import org.xnio.conduits.StreamSinkConduit; /** * * Handler that appends the JVM route to the session id. * * @author Stuart Douglas * @author Richard Opalka */ public class JvmRouteHandler implements HttpHandler { private final HttpHandler next; private final String sessionCookieName; private final String jvmRoute; private final JvmRouteWrapper wrapper = new JvmRouteWrapper(); public JvmRouteHandler(HttpHandler next, String sessionCookieName, String jvmRoute) { this.next = next; this.sessionCookieName = sessionCookieName; this.jvmRoute = jvmRoute; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { for (Cookie cookie : exchange.requestCookies()) { if (sessionCookieName.equals(cookie.getName())) { int part = cookie.getValue().indexOf('.'); if (part != -1) { cookie.setValue(cookie.getValue().substring(0, part)); } } } exchange.addResponseWrapper(wrapper); next.handleRequest(exchange); } private class JvmRouteWrapper implements ConduitWrapper { @Override public StreamSinkConduit wrap(ConduitFactory factory, HttpServerExchange exchange) { for (Cookie cookie : exchange.responseCookies()) { if (sessionCookieName.equals(cookie.getName())) { StringBuilder sb = new StringBuilder(cookie.getValue()); sb.append('.'); sb.append(jvmRoute); cookie.setValue(sb.toString()); } } return factory.create(); } } public static class Builder implements HandlerBuilder { @Override public String name() { return "jvm-route"; } @Override public Map> parameters() { Map> params = new HashMap<>(); params.put("value", String.class); params.put("session-cookie-name", String.class); return params; } @Override public Set requiredParameters() { return Collections.singleton("value"); } @Override public String defaultParameter() { return "value"; } @Override public HandlerWrapper build(Map config) { String sessionCookieName = (String) config.get("session-cookie-name"); return new Wrapper((String)config.get("value"), sessionCookieName == null ? "JSESSIONID" : sessionCookieName); } } private static class Wrapper implements HandlerWrapper { private final String value; private final String sessionCookieName; private Wrapper(String value, String sessionCookieName) { this.value = value; this.sessionCookieName = sessionCookieName; } @Override public HttpHandler wrap(HttpHandler handler) { return new JvmRouteHandler(handler, sessionCookieName, value); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/ListenerRegistry.java000066400000000000000000000124221420065311100300240ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import java.net.InetSocketAddress; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArraySet; import io.undertow.UndertowMessages; import io.undertow.util.CopyOnWriteMap; /** * A registry of listeners, and the services that are exposed via these listeners. * * This is not used directly by Undertow, but can be used by embedding applications to * track listener metadata. * * @author Stuart Douglas */ public class ListenerRegistry { private final ConcurrentMap listeners = new CopyOnWriteMap<>(); public Listener getListener(final String name) { return listeners.get(name); } public void addListener(final Listener listener) { if(listeners.putIfAbsent(listener.getName(), listener) != null) { throw UndertowMessages.MESSAGES.listenerAlreadyRegistered(listener.getName()); } } public void removeListener(final String name) { listeners.remove(name); } public static final class Listener { private final String protocol; private final String name; private final String serverName; private final InetSocketAddress bindAddress; /** * Map that can be used to store additional listener metadata */ private final Map contextInformation = new CopyOnWriteMap<>(); /** * Information about any HTTP upgrade handlers that are registered on this handler. */ private final Set httpUpgradeMetadata = new CopyOnWriteArraySet<>(); public Listener(final String protocol, final String name, final String serverName, final InetSocketAddress bindAddress) { this.protocol = protocol; this.name = name; this.serverName = serverName; this.bindAddress = bindAddress; } /** * The protocol that this listener is using */ public String getProtocol() { return protocol; } /** * The optional listener name; */ public String getName() { return name; } /** * The server name */ public String getServerName() { return serverName; } /** * The address that this listener is bound to */ public InetSocketAddress getBindAddress() { return bindAddress; } public Collection getContextKeys() { return contextInformation.keySet(); } public Object removeContextInformation(final String key) { return contextInformation.remove(key); } public Object setContextInformation(final String key, final Object value) { return contextInformation.put(key, value); } public Object getContextInformation(final String key) { return contextInformation.get(key); } public void addHttpUpgradeMetadata(final HttpUpgradeMetadata upgradeMetadata) { httpUpgradeMetadata.add(upgradeMetadata); } public void removeHttpUpgradeMetadata(final HttpUpgradeMetadata upgradeMetadata) { httpUpgradeMetadata.remove(upgradeMetadata); } public Set getHttpUpgradeMetadata() { return Collections.unmodifiableSet(httpUpgradeMetadata); } } public static final class HttpUpgradeMetadata { private final String protocol; private final String subProtocol; private final Map contextInformation = new CopyOnWriteMap<>(); public HttpUpgradeMetadata(final String protocol, final String subProtocol) { this.protocol = protocol; this.subProtocol = subProtocol; } public String getProtocol() { return protocol; } public String getSubProtocol() { return subProtocol; } public Collection getContextKeys() { return contextInformation.keySet(); } public Object removeContextInformation(final String key) { return contextInformation.remove(key); } public Object setContextInformation(final String key, final Object value) { return contextInformation.put(key, value); } public Object getContextInformation(final String key) { return contextInformation.get(key); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/MapDelegatingToSet.java000066400000000000000000000165051420065311100301740ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import static java.util.Collections.emptySet; import static java.util.Collections.unmodifiableCollection; import static java.util.Collections.unmodifiableSet; import io.undertow.server.handlers.Cookie; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; /** * @author Richard Opalka */ final class MapDelegatingToSet extends HashMap { private final Set delegate; MapDelegatingToSet(final Set delegate) { this.delegate = delegate; } @Override public int size() { return delegate.size(); } @Override public boolean isEmpty() { return delegate.isEmpty(); } @Override public Cookie get(final Object key) { if (key == null) return null; for (Cookie cookie : delegate) { if (key.equals(cookie.getName())) return cookie; } return null; } @Override public boolean containsKey(final Object key) { if (key == null) return false; for (Cookie cookie : delegate) { if (key.equals(cookie.getName())) return true; } return false; } @Override public Cookie put(final String key, final Cookie value) { if (key == null) return null; final Cookie retVal = remove(key); if (value != null) { delegate.add(value); } return retVal; } @Override public void putAll(final Map m) { if (m == null) return; for (Map.Entry entry : m.entrySet()) { put(entry.getKey(), entry.getValue()); } } @Override public Cookie remove(final Object key) { if (key == null) return null; Cookie removedValue = null; for (Cookie cookie : delegate) { if (key.equals(cookie.getName())) { removedValue = cookie; break; } } if (removedValue != null) delegate.remove(removedValue); return removedValue; } @Override public void clear() { delegate.clear(); } @Override public boolean containsValue(final Object value) { if (value == null) return false; return delegate.contains(value); } @Override public Set keySet() { if (delegate.isEmpty()) return emptySet(); final Set retVal = new HashSet<>(); for (Cookie cookie : delegate) { retVal.add(cookie.getName()); } return unmodifiableSet(retVal); } @Override public Collection values() { return delegate.isEmpty() ? emptySet() : unmodifiableCollection(delegate); } @Override public Set> entrySet() { if (delegate.isEmpty()) return emptySet(); final Set> retVal = new HashSet<>(delegate.size()); for (Cookie cookie : delegate) { retVal.add(new ReadOnlyEntry(cookie.getName(), cookie)); } return unmodifiableSet(retVal); } @Override public Cookie getOrDefault(final Object key, final Cookie defaultValue) { if (key == null) return null; final Cookie retVal = get(key); return retVal != null ? retVal : defaultValue; } @Override public Cookie putIfAbsent(final String key, final Cookie value) { if (key == null) return null; final Cookie oldVal = get(key); if (oldVal == null) delegate.add(value); return oldVal; } @Override public boolean remove(final Object key, final Object value) { if (key == null || value == null) return false; Cookie removedValue = null; for (Cookie cookie : delegate) { if (cookie == value) { removedValue = cookie; break; } } if (removedValue != null) delegate.remove(removedValue); return removedValue != null; } @Override public boolean replace(final String key, final Cookie oldValue, final Cookie newValue) { if (key == null) return false; final Cookie previousValue = get(key); if (previousValue == oldValue) { delegate.remove(oldValue); if (newValue != null) { delegate.add(newValue); } return true; } return false; } @Override public Cookie replace(final String key, final Cookie value) { if (key == null) return null; final Cookie oldValue = get(key); if (oldValue != null) { delegate.remove(oldValue); if (value != null) { delegate.add(value); } } return oldValue; } @Override public Cookie computeIfAbsent(final String key, final Function mappingFunction) { throw new UnsupportedOperationException(); } @Override public Cookie computeIfPresent(String key, BiFunction remappingFunction) { throw new UnsupportedOperationException(); } @Override public Cookie compute(String key, BiFunction remappingFunction) { throw new UnsupportedOperationException(); } @Override public Cookie merge(String key, Cookie value, BiFunction remappingFunction) { throw new UnsupportedOperationException(); } @Override public void forEach(BiConsumer action) { throw new UnsupportedOperationException(); } @Override public void replaceAll(BiFunction function) { throw new UnsupportedOperationException(); } private static final class ReadOnlyEntry implements Entry { private final String key; private final Cookie value; private ReadOnlyEntry(final String key, final Cookie value) { this.key = key; this.value = value; } @Override public String getKey() { return key; } @Override public Cookie getValue() { return value; } @Override public Cookie setValue(final Cookie cookie) { throw new UnsupportedOperationException(); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/OpenListener.java000066400000000000000000000037361420065311100271250ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import org.xnio.ChannelListener; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import org.xnio.StreamConnection; /** * Interface that represents an open listener, aka a connector. * * @author Stuart Douglas */ public interface OpenListener extends ChannelListener { /** * * @return The first handler that will be executed by requests on the connector */ HttpHandler getRootHandler(); /** * Sets the root handler * * @param rootHandler The new root handler */ void setRootHandler(HttpHandler rootHandler); /** * * @return The connector options */ OptionMap getUndertowOptions(); /** * * @param undertowOptions The connector options */ void setUndertowOptions(OptionMap undertowOptions); /** * * @return The buffer pool in use by this connector */ ByteBufferPool getBufferPool(); /** * * @return The connector statistics, or null if statistics gathering is disabled. */ ConnectorStatistics getConnectorStatistics(); /** * Close all active connections that were handled by this listener */ default void closeConnections() { //nnop by default } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/OverridableTreeSet.java000066400000000000000000000020511420065311100302350ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import java.util.TreeSet; /** * @author Richard Opalka */ final class OverridableTreeSet extends TreeSet { @Override public boolean add(final T o) { // always override previous value super.remove(o); super.add(o); return true; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/ReadOnlyIterator.java000066400000000000000000000026621420065311100277420ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import java.util.Iterator; import java.util.function.Consumer; /** * @author Richard Opalka */ final class ReadOnlyIterator implements Iterator { final Iterator delegate; ReadOnlyIterator(final Iterator delegate) { this.delegate = delegate; } @Override public void remove() { throw new UnsupportedOperationException(); } @Override public void forEachRemaining(final Consumer action) { delegate.forEachRemaining(action); } @Override public boolean hasNext() { return delegate.hasNext(); } @Override public E next() { return delegate.next(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/RenegotiationRequiredException.java000066400000000000000000000031731420065311100327000ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; /** * Exception that is thrown that indicates that SSL renegotiation is required * in order to get a client cert. * * This will be thrown if a user attempts to retrieve a client cert and the SSL mode * is {@link org.xnio.SslClientAuthMode#NOT_REQUESTED}. * * @author Stuart Douglas */ public class RenegotiationRequiredException extends Exception { public RenegotiationRequiredException() { } public RenegotiationRequiredException(String message) { super(message); } public RenegotiationRequiredException(String message, Throwable cause) { super(message, cause); } public RenegotiationRequiredException(Throwable cause) { super(cause); } public RenegotiationRequiredException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/RequestTooBigException.java000066400000000000000000000007461420065311100311270ustar00rootroot00000000000000package io.undertow.server; import java.io.IOException; /** * @author Stuart Douglas */ public class RequestTooBigException extends IOException { public RequestTooBigException() { super(); } public RequestTooBigException(String message) { super(message); } public RequestTooBigException(String message, Throwable cause) { super(message, cause); } public RequestTooBigException(Throwable cause) { super(cause); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/ResponseCommitListener.java000066400000000000000000000020641420065311100311640ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; /** * Callback that is invoked just before the response is commit * * @author Stuart Douglas */ public interface ResponseCommitListener { /** * Invoked before the first bytes of the response are sent to the client * @param exchange The server exchange */ void beforeCommit(HttpServerExchange exchange); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/RoutingHandler.java000066400000000000000000000263431420065311100274420ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import io.undertow.predicate.Predicate; import io.undertow.server.handlers.ResponseCodeHandler; import io.undertow.util.CopyOnWriteMap; import io.undertow.util.HttpString; import io.undertow.util.Methods; import io.undertow.util.PathTemplate; import io.undertow.util.PathTemplateMatch; import io.undertow.util.PathTemplateMatcher; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.CopyOnWriteArrayList; /** * A Handler that handles the common case of routing via path template and method name. * * @author Stuart Douglas */ public class RoutingHandler implements HttpHandler { // Matcher objects grouped by http methods. private final Map> matches = new CopyOnWriteMap<>(); // Matcher used to find if this instance contains matches for any http method for a path. // This matcher is used to report if this instance can match a path for one of the http methods. private final PathTemplateMatcher allMethodsMatcher = new PathTemplateMatcher<>(); // Handler called when no match was found and invalid method handler can't be invoked. private volatile HttpHandler fallbackHandler = ResponseCodeHandler.HANDLE_404; // Handler called when this instance can not match the http method but can match another http method. // For example: For an exchange the POST method is not matched by this instance but at least one http method is // matched for the same exchange. // If this handler is null the fallbackHandler will be used. private volatile HttpHandler invalidMethodHandler = ResponseCodeHandler.HANDLE_405; // If this is true then path matches will be added to the query parameters for easy access by later handlers. private final boolean rewriteQueryParameters; public RoutingHandler(boolean rewriteQueryParameters) { this.rewriteQueryParameters = rewriteQueryParameters; } public RoutingHandler() { this.rewriteQueryParameters = true; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { PathTemplateMatcher matcher = matches.get(exchange.getRequestMethod()); if (matcher == null) { handleNoMatch(exchange); return; } PathTemplateMatcher.PathMatchResult match = matcher.match(exchange.getRelativePath()); if (match == null) { handleNoMatch(exchange); return; } exchange.putAttachment(PathTemplateMatch.ATTACHMENT_KEY, match); if (rewriteQueryParameters) { for (Map.Entry entry : match.getParameters().entrySet()) { exchange.addQueryParam(entry.getKey(), entry.getValue()); } } for (HandlerHolder handler : match.getValue().predicatedHandlers) { if (handler.predicate.resolve(exchange)) { handler.handler.handleRequest(exchange); return; } } if (match.getValue().defaultHandler != null) { match.getValue().defaultHandler.handleRequest(exchange); } else { fallbackHandler.handleRequest(exchange); } } /** * Handles the case in with a match was not found for the http method but might exist for another http method. * For example: POST not matched for a path but at least one match exists for same path. * * @param exchange The object for which its handled the "no match" case. * @throws Exception */ private void handleNoMatch(final HttpServerExchange exchange) throws Exception { // if invalidMethodHandler is null we fail fast without matching with allMethodsMatcher if (invalidMethodHandler != null && allMethodsMatcher.match(exchange.getRelativePath()) != null) { invalidMethodHandler.handleRequest(exchange); return; } fallbackHandler.handleRequest(exchange); } public synchronized RoutingHandler add(final String method, final String template, HttpHandler handler) { return add(new HttpString(method), template, handler); } public synchronized RoutingHandler add(HttpString method, String template, HttpHandler handler) { PathTemplateMatcher matcher = matches.get(method); if (matcher == null) { matches.put(method, matcher = new PathTemplateMatcher<>()); } RoutingMatch res = matcher.get(template); if (res == null) { matcher.add(template, res = new RoutingMatch()); } if (allMethodsMatcher.match(template) == null) { allMethodsMatcher.add(template, res); } res.defaultHandler = handler; return this; } public synchronized RoutingHandler get(final String template, HttpHandler handler) { return add(Methods.GET, template, handler); } public synchronized RoutingHandler post(final String template, HttpHandler handler) { return add(Methods.POST, template, handler); } public synchronized RoutingHandler put(final String template, HttpHandler handler) { return add(Methods.PUT, template, handler); } public synchronized RoutingHandler delete(final String template, HttpHandler handler) { return add(Methods.DELETE, template, handler); } public synchronized RoutingHandler add(final String method, final String template, Predicate predicate, HttpHandler handler) { return add(new HttpString(method), template, predicate, handler); } public synchronized RoutingHandler add(HttpString method, String template, Predicate predicate, HttpHandler handler) { PathTemplateMatcher matcher = matches.get(method); if (matcher == null) { matches.put(method, matcher = new PathTemplateMatcher<>()); } RoutingMatch res = matcher.get(template); if (res == null) { matcher.add(template, res = new RoutingMatch()); } if (allMethodsMatcher.match(template) == null) { allMethodsMatcher.add(template, res); } res.predicatedHandlers.add(new HandlerHolder(predicate, handler)); return this; } public synchronized RoutingHandler get(final String template, Predicate predicate, HttpHandler handler) { return add(Methods.GET, template, predicate, handler); } public synchronized RoutingHandler post(final String template, Predicate predicate, HttpHandler handler) { return add(Methods.POST, template, predicate, handler); } public synchronized RoutingHandler put(final String template, Predicate predicate, HttpHandler handler) { return add(Methods.PUT, template, predicate, handler); } public synchronized RoutingHandler delete(final String template, Predicate predicate, HttpHandler handler) { return add(Methods.DELETE, template, predicate, handler); } public synchronized RoutingHandler addAll(RoutingHandler routingHandler) { for (Entry> entry : routingHandler.getMatches().entrySet()) { HttpString method = entry.getKey(); PathTemplateMatcher matcher = matches.get(method); if (matcher == null) { matches.put(method, matcher = new PathTemplateMatcher<>()); } matcher.addAll(entry.getValue()); // If we use allMethodsMatcher.addAll() we can have duplicate // PathTemplates which we want to ignore here so it does not crash. for (PathTemplate template : entry.getValue().getPathTemplates()) { if (allMethodsMatcher.match(template.getTemplateString()) == null) { allMethodsMatcher.add(template, new RoutingMatch()); } } } return this; } /** * * Removes the specified route from the handler * * @param method The method to remove * @param path the path tempate to remove * @return this handler */ public RoutingHandler remove(HttpString method, String path) { PathTemplateMatcher handler = matches.get(method); if(handler != null) { handler.remove(path); } return this; } /** * * Removes the specified route from the handler * * @param path the path tempate to remove * @return this handler */ public RoutingHandler remove(String path) { allMethodsMatcher.remove(path); return this; } Map> getMatches() { return matches; } /** * @return Handler called when no match was found and invalid method handler can't be invoked. */ public HttpHandler getFallbackHandler() { return fallbackHandler; } /** * @param fallbackHandler Handler that will be called when no match was found and invalid method handler can't be * invoked. * @return This instance. */ public RoutingHandler setFallbackHandler(HttpHandler fallbackHandler) { this.fallbackHandler = fallbackHandler; return this; } /** * @return Handler called when this instance can not match the http method but can match another http method. */ public HttpHandler getInvalidMethodHandler() { return invalidMethodHandler; } /** * Sets the handler called when this instance can not match the http method but can match another http method. * For example: For an exchange the POST method is not matched by this instance but at least one http method matched * for the exchange. * If this handler is null the fallbackHandler will be used. * * @param invalidMethodHandler Handler that will be called when this instance can not match the http method but can * match another http method. * @return This instance. */ public RoutingHandler setInvalidMethodHandler(HttpHandler invalidMethodHandler) { this.invalidMethodHandler = invalidMethodHandler; return this; } private static class RoutingMatch { final List predicatedHandlers = new CopyOnWriteArrayList<>(); volatile HttpHandler defaultHandler; } private static class HandlerHolder { final Predicate predicate; final HttpHandler handler; private HandlerHolder(Predicate predicate, HttpHandler handler) { this.predicate = predicate; this.handler = handler; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/SSLSessionInfo.java000066400000000000000000000076721420065311100273420ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import org.xnio.SslClientAuthMode; import javax.net.ssl.SSLSession; import java.io.IOException; /** * SSL session information. * * @author Stuart Douglas */ public interface SSLSessionInfo { /** * Given the name of a TLS/SSL cipher suite, return an int representing it effective stream * cipher key strength. i.e. How much entropy material is in the key material being fed into the * encryption routines. *

* http://www.thesprawl.org/research/tls-and-ssl-cipher-suites/ *

* * @param cipherSuite String name of the TLS cipher suite. * @return int indicating the effective key entropy bit-length. */ static int calculateKeySize(String cipherSuite) { // Roughly ordered from most common to least common. if (cipherSuite == null) { return 0; } else if (cipherSuite.contains("WITH_AES_256_")) { return 256; } else if (cipherSuite.contains("WITH_RC4_128_")) { return 128; } else if (cipherSuite.contains("WITH_AES_128_")) { return 128; } else if (cipherSuite.contains("WITH_RC4_40_")) { return 40; } else if (cipherSuite.contains("WITH_3DES_EDE_CBC_")) { return 168; } else if (cipherSuite.contains("WITH_IDEA_CBC_")) { return 128; } else if (cipherSuite.contains("WITH_RC2_CBC_40_")) { return 40; } else if (cipherSuite.contains("WITH_DES40_CBC_")) { return 40; } else if (cipherSuite.contains("WITH_DES_CBC_")) { return 56; } else { return 0; } } /** * * @return The SSL session ID, or null if this could not be determined. */ byte[] getSessionId(); java.lang.String getCipherSuite(); default int getKeySize() { return calculateKeySize(this.getCipherSuite()); } /** * Gets the peer certificates. This may force SSL renegotiation. * * @return The peer certificates * @throws javax.net.ssl.SSLPeerUnverifiedException * @throws RenegotiationRequiredException If the session */ java.security.cert.Certificate[] getPeerCertificates() throws javax.net.ssl.SSLPeerUnverifiedException, RenegotiationRequiredException; /** * This method is no longer supported on java 15 and should be avoided. * @deprecated in favor of {@link #getPeerCertificates()} because {@link SSLSession#getPeerCertificateChain()} * throws java 15. * @see SSLSession#getPeerCertificateChain() */ @Deprecated javax.security.cert.X509Certificate[] getPeerCertificateChain() throws javax.net.ssl.SSLPeerUnverifiedException, RenegotiationRequiredException; /** * Renegotiate in a blocking manner. This will set the client aut * * TODO: we also need a non-blocking version * * @throws IOException * @param exchange The exchange * @param sslClientAuthMode The client cert mode to use when renegotiating */ void renegotiate(HttpServerExchange exchange, SslClientAuthMode sslClientAuthMode) throws IOException; /** * * @return The SSL session, or null if it is not applicable */ SSLSession getSSLSession(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/SecureCookieCommitListener.java000066400000000000000000000007511420065311100317470ustar00rootroot00000000000000package io.undertow.server; import io.undertow.server.handlers.Cookie; /** * Sets the
secure
attribute on all response cookies. * @author Richard Opalka */ public enum SecureCookieCommitListener implements ResponseCommitListener { INSTANCE; @Override public void beforeCommit(HttpServerExchange exchange) { for (Cookie cookie : exchange.responseCookies()) { cookie.setSecure(true); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/ServerConnection.java000066400000000000000000000227201420065311100277760ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import io.undertow.connector.ByteBufferPool; import io.undertow.util.AbstractAttachable; import io.undertow.util.HeaderMap; import io.undertow.util.HttpString; import org.xnio.Option; import org.xnio.OptionMap; import org.xnio.Pool; import org.xnio.StreamConnection; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.ConnectedChannel; import org.xnio.conduits.ConduitStreamSinkChannel; import org.xnio.conduits.ConduitStreamSourceChannel; import org.xnio.conduits.StreamSinkConduit; import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; import javax.net.ssl.SSLSession; /** * A server connection. * * @author Stuart Douglas */ public abstract class ServerConnection extends AbstractAttachable implements ConnectedChannel { /** * * @return The connections buffer pool */ @Deprecated public abstract Pool getBufferPool(); /** * * @return The connections buffer pool */ public abstract ByteBufferPool getByteBufferPool(); /** * * @return The connections worker */ public abstract XnioWorker getWorker(); /** * * @return The IO thread associated with the connection */ @Override public abstract XnioIoThread getIoThread(); /** * Sends an out of band response, such as a HTTP 100-continue response. * * WARNING: do not attempt to write to the current exchange until the out of band * exchange has been fully written. Doing so may have unexpected results. * * TODO: this needs more thought. * * @return The out of band exchange. * @param exchange The current exchange */ public abstract HttpServerExchange sendOutOfBandResponse(HttpServerExchange exchange); /** * * @return true if this connection supports sending a 100-continue response */ public abstract boolean isContinueResponseSupported(); /** * Invoked when the exchange is complete, and there is still data in the request channel. Some implementations * (such as SPDY and HTTP2) have more efficient ways to drain the request than simply reading all data * (e.g. RST_STREAM). * * After this method is invoked the stream will be drained normally. * * @param exchange The current exchange. */ public abstract void terminateRequestChannel(HttpServerExchange exchange); /** * * @return true if the connection is open */ public abstract boolean isOpen(); public abstract boolean supportsOption(Option option); public abstract T getOption(Option option) throws IOException; public abstract T setOption(Option option, T value) throws IllegalArgumentException, IOException; public abstract void close() throws IOException; /** * * Gets the SSLSession of the underlying connection, or null if SSL is not in use. * * Note that for client cert auth {@link #getSslSessionInfo()} should be used instead, as it * takes into account other information potentially provided by load balancers that terminate SSL * * @return The SSLSession of the connection */ public SSLSession getSslSession() { return null; } /** * Returns the actual address of the remote connection. This will not take things like X-Forwarded-for * into account. * @return The address of the remote peer */ public abstract SocketAddress getPeerAddress(); /** * Returns the actual address of the remote connection. This will not take things like X-Forwarded-for * into account. * * @param type The type of address to return * @param The address type * @return The remote endpoint address */ public abstract A getPeerAddress(Class type); public abstract SocketAddress getLocalAddress(); public abstract A getLocalAddress(Class type); public abstract OptionMap getUndertowOptions(); public abstract int getBufferSize(); /** * Gets SSL information about the connection. This could represent the actual * client connection, or could be providing SSL information that was provided * by a front end proxy. * * @return SSL information about the connection */ public abstract SSLSessionInfo getSslSessionInfo(); /** * Sets the current SSL information. This can be used by handlers to setup SSL * information that was provided by a front end proxy. * * If this is being set of a per request basis then you must ensure that it is either * cleared by an exchange completion listener at the end of the request, or is always * set for every request. Otherwise it is possible to SSL information to 'leak' between * requests. * * @param sessionInfo The ssl session information */ public abstract void setSslSessionInfo(SSLSessionInfo sessionInfo); /** * Adds a close listener, than will be invoked with the connection is closed * * @param listener The close listener */ public abstract void addCloseListener(CloseListener listener); /** * Upgrade the connection, if allowed * @return The StreamConnection that should be passed to the upgrade handler */ protected abstract StreamConnection upgradeChannel(); protected abstract ConduitStreamSinkChannel getSinkChannel(); protected abstract ConduitStreamSourceChannel getSourceChannel(); /** * Gets the sink conduit that should be used for this request. * * This allows the connection to apply any per-request conduit wrapping * that is required, without adding to the response wrappers array. * * There is no corresponding method for source conduits, as in general * conduits can be directly inserted into the connection after the * request has been read. * * @return The source conduit */ protected abstract StreamSinkConduit getSinkConduit(HttpServerExchange exchange, final StreamSinkConduit conduit); /** * * @return true if this connection supports HTTP upgrade */ protected abstract boolean isUpgradeSupported(); /** * * @return true if this connection supports the HTTP CONNECT verb */ protected abstract boolean isConnectSupported(); /** * Invoked when the exchange is complete. */ protected abstract void exchangeComplete(HttpServerExchange exchange); protected abstract void setUpgradeListener(HttpUpgradeListener upgradeListener); protected abstract void setConnectListener(HttpUpgradeListener connectListener); /** * Callback that is invoked if the max entity size is updated. * * @param exchange The current exchange */ protected abstract void maxEntitySizeUpdated(HttpServerExchange exchange); /** * Returns a string representation describing the protocol used to transmit messages * on this connection. * * @return the transport protocol */ public abstract String getTransportProtocol(); /** * Attempts to push a resource if this connection supports server push. Otherwise the request is ignored. * * Note that push is always done on a best effort basis, even if this method returns true it is possible that * the remote endpoint will reset the stream * * * @param path The path of the resource * @param method The request method * @param requestHeaders The request headers * @return true if the server attempted the push, false otherwise */ public boolean pushResource(final String path, final HttpString method, final HeaderMap requestHeaders) { return false; } /** * Attempts to push a resource if this connection supports server push. Otherwise the request is ignored. * * Note that push is always done on a best effort basis, even if this method returns true it is possible that * the remote endpoint will reset the stream. * * The {@link io.undertow.server.HttpHandler} passed in will be used to generate the pushed response * * * @param path The path of the resource * @param method The request method * @param requestHeaders The request headers * @return true if the server attempted the push, false otherwise */ public boolean pushResource(final String path, final HttpString method, final HeaderMap requestHeaders, HttpHandler handler) { return false; } public boolean isPushSupported() { return false; } public abstract boolean isRequestTrailerFieldsSupported(); public interface CloseListener { void closed(final ServerConnection connection); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/TruncatedResponseException.java000066400000000000000000000050541420065311100320400ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import java.io.IOException; /** * An exception indicating that the response channel was prematurely closed. The response channel must be shut * down and flushed successfully after all requests, even those which do not send a response body. * * @author David M. Lloyd */ public class TruncatedResponseException extends IOException { /** * Constructs a {@code TruncatedResponseException} with no detail message. The cause is not initialized, and may * subsequently be initialized by a call to {@link #initCause(Throwable) initCause}. */ public TruncatedResponseException() { } /** * Constructs a {@code TruncatedResponseException} with the specified detail message. The cause is not initialized, * and may subsequently be initialized by a call to {@link #initCause(Throwable) initCause}. * * @param msg the detail message */ public TruncatedResponseException(final String msg) { super(msg); } /** * Constructs a {@code TruncatedResponseException} with the specified cause. The detail message is set to: *
(cause == null ? null : cause.toString())
* (which typically contains the class and detail message of {@code cause}). * * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method) */ public TruncatedResponseException(final Throwable cause) { super(cause); } /** * Constructs a {@code TruncatedResponseException} with the specified detail message and cause. * * @param msg the detail message * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method) */ public TruncatedResponseException(final String msg, final Throwable cause) { super(msg, cause); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/XnioBufferPoolAdaptor.java000066400000000000000000000035151420065311100307250ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import org.xnio.Pool; import org.xnio.Pooled; import java.nio.ByteBuffer; /** * Adaptor between a ByteBufferPool and an XNIO Pool * * @author Stuart Douglas */ public class XnioBufferPoolAdaptor implements Pool { private final ByteBufferPool byteBufferPool; public XnioBufferPoolAdaptor(ByteBufferPool byteBufferPool) { this.byteBufferPool = byteBufferPool; } @Override public Pooled allocate() { final PooledByteBuffer buf = byteBufferPool.allocate(); return new Pooled() { @Override public void discard() { buf.close(); } @Override public void free() { buf.close(); } @Override public ByteBuffer getResource() throws IllegalStateException { return buf.getBuffer(); } @Override public void close() { buf.close(); } }; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/XnioByteBufferPool.java000066400000000000000000000046321420065311100302370ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import org.xnio.Pool; import org.xnio.Pooled; import java.nio.ByteBuffer; /** * @author Stuart Douglas */ public class XnioByteBufferPool implements ByteBufferPool { private final Pool pool; private final ByteBufferPool arrayBackedPool; private final int bufferSize; private final boolean direct; public XnioByteBufferPool(Pool pool) { this.pool = pool; Pooled buf = pool.allocate(); bufferSize = buf.getResource().remaining(); direct = !buf.getResource().hasArray(); buf.free(); if(direct) { arrayBackedPool = new DefaultByteBufferPool(false, bufferSize); } else { arrayBackedPool = this; } } @Override public PooledByteBuffer allocate() { final Pooled buf = pool.allocate(); return new PooledByteBuffer() { private boolean open = true; @Override public ByteBuffer getBuffer() { return buf.getResource(); } @Override public void close() { open = false; buf.free(); } @Override public boolean isOpen() { return open; } }; } @Override public ByteBufferPool getArrayBackedPool() { return arrayBackedPool; } @Override public void close() { } @Override public int getBufferSize() { return bufferSize; } @Override public boolean isDirect() { return direct; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/000077500000000000000000000000001420065311100254425ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/AccessControlListHandler.java000066400000000000000000000174741420065311100332160ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.UndertowMessages; import io.undertow.attribute.ExchangeAttribute; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.StatusCodes; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; /** * Handler that can accept or reject a request based on an attribute of the remote peer * * todo: should we support non-regex values for performance reasons? * @author Stuart Douglas * @author Andre Dietisheim */ public class AccessControlListHandler implements HttpHandler { private volatile HttpHandler next; private volatile boolean defaultAllow = false; private final ExchangeAttribute attribute; private final List acl = new CopyOnWriteArrayList<>(); public AccessControlListHandler(final HttpHandler next, ExchangeAttribute attribute) { this.next = next; this.attribute = attribute; } public AccessControlListHandler(ExchangeAttribute attribute) { this.attribute = attribute; this.next = ResponseCodeHandler.HANDLE_404; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { String attribute = this.attribute.readAttribute(exchange); if (isAllowed(attribute)) { next.handleRequest(exchange); } else { exchange.setStatusCode(StatusCodes.FORBIDDEN); exchange.endExchange(); } } //package private for unit tests boolean isAllowed(String attribute) { if (attribute != null) { for (AclMatch rule : acl) { if (rule.matches(attribute)) { return !rule.isDeny(); } } } return defaultAllow; } public boolean isDefaultAllow() { return defaultAllow; } public AccessControlListHandler setDefaultAllow(final boolean defaultAllow) { this.defaultAllow = defaultAllow; return this; } public HttpHandler getNext() { return next; } public AccessControlListHandler setNext(final HttpHandler next) { this.next = next; return this; } /** * Adds an allowed user agent peer to the ACL list *

* User agent may be given as regex * * @param pattern The pattern to add to the ACL */ public AccessControlListHandler addAllow(final String pattern) { return addRule(pattern, false); } /** * Adds an denied user agent to the ACL list *

* User agent may be given as regex * * @param pattern The user agent to add to the ACL */ public AccessControlListHandler addDeny(final String pattern) { return addRule(pattern, true); } public AccessControlListHandler clearRules() { this.acl.clear(); return this; } private AccessControlListHandler addRule(final String userAgent, final boolean deny) { this.acl.add(new AclMatch(deny, userAgent)); return this; } static class AclMatch { private final boolean deny; private final Pattern pattern; protected AclMatch(final boolean deny, final String pattern) { this.deny = deny; this.pattern = createPattern(pattern); } private Pattern createPattern(final String pattern) { try { return Pattern.compile(pattern); } catch (PatternSyntaxException e) { throw UndertowMessages.MESSAGES.notAValidRegularExpressionPattern(pattern); } } boolean matches(final String attribute) { return pattern.matcher(attribute).matches(); } boolean isDeny() { return deny; } @Override public String toString() { return getClass().getSimpleName() + "{" + "deny=" + deny + ", pattern='" + pattern + '\'' + '}'; } } public static class Builder implements HandlerBuilder { @Override public String name() { return "access-control"; } @Override public Map> parameters() { Map> params = new HashMap<>(); params.put("acl", String[].class); params.put("default-allow", boolean.class); params.put("attribute", ExchangeAttribute.class); return params; } @Override public Set requiredParameters() { final HashSet ret = new HashSet<>(); ret.add("acl"); ret.add("attribute"); return ret; } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { String[] acl = (String[]) config.get("acl"); Boolean defaultAllow = (Boolean) config.get("default-allow"); ExchangeAttribute attribute = (ExchangeAttribute) config.get("attribute"); List peerMatches = new ArrayList<>(); for(String rule :acl) { String[] parts = rule.split(" "); if(parts.length != 2) { throw UndertowMessages.MESSAGES.invalidAclRule(rule); } if(parts[1].trim().equals("allow")) { peerMatches.add(new AclMatch(false, parts[0].trim())); } else if(parts[1].trim().equals("deny")) { peerMatches.add(new AclMatch(true, parts[0].trim())); } else { throw UndertowMessages.MESSAGES.invalidAclRule(rule); } } return new Wrapper(peerMatches, defaultAllow == null ? false : defaultAllow, attribute); } } private static class Wrapper implements HandlerWrapper { private final List peerMatches; private final boolean defaultAllow; private final ExchangeAttribute attribute; private Wrapper(List peerMatches, boolean defaultAllow, ExchangeAttribute attribute) { this.peerMatches = peerMatches; this.defaultAllow = defaultAllow; this.attribute = attribute; } @Override public HttpHandler wrap(HttpHandler handler) { AccessControlListHandler res = new AccessControlListHandler(handler, attribute); for(AclMatch match: peerMatches) { if(match.deny) { res.addDeny(match.pattern.pattern()); } else { res.addAllow(match.pattern.pattern()); } } res.setDefaultAllow(defaultAllow); return res; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/AllowedMethodsHandler.java000066400000000000000000000075641420065311100325320ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.HttpString; import io.undertow.util.StatusCodes; import java.util.stream.Collectors; /** * Handler that whitelists certain HTTP methods. Only requests with a method in * the allowed methods set will be allowed to continue. * * @author Stuart Douglas */ public class AllowedMethodsHandler implements HttpHandler { private final Set allowedMethods; private final HttpHandler next; public AllowedMethodsHandler(final HttpHandler next, final Set allowedMethods) { this.allowedMethods = new HashSet<>(allowedMethods); this.next = next; } public AllowedMethodsHandler(final HttpHandler next, final HttpString... allowedMethods) { this.allowedMethods = new HashSet<>(Arrays.asList(allowedMethods)); this.next = next; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { if (allowedMethods.contains(exchange.getRequestMethod())) { next.handleRequest(exchange); } else { exchange.setStatusCode(StatusCodes.METHOD_NOT_ALLOWED); exchange.endExchange(); } } public Set getAllowedMethods() { return Collections.unmodifiableSet(allowedMethods); } @Override public String toString() { if (allowedMethods.size() == 1) { return "allowed-methods( " + allowedMethods.toArray()[0] + " )"; } else { return "allowed-methods( {" + allowedMethods.stream().map(s -> s.toString()).collect(Collectors.joining(", ")) + "} )"; } } public static class Builder implements HandlerBuilder { @Override public String name() { return "allowed-methods"; } @Override public Map> parameters() { return Collections.>singletonMap("methods", String[].class); } @Override public Set requiredParameters() { return Collections.singleton("methods"); } @Override public String defaultParameter() { return "methods"; } @Override public HandlerWrapper build(Map config) { return new Wrapper((String[]) config.get("methods")); } } private static class Wrapper implements HandlerWrapper { private final String[] methods; private Wrapper(String[] methods) { this.methods = methods; } @Override public HttpHandler wrap(HttpHandler handler) { HttpString[] strings = new HttpString[methods.length]; for (int i = 0; i < methods.length; ++i) { strings[i] = new HttpString(methods[i]); } return new AllowedMethodsHandler(handler, strings); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/AttachmentHandler.java000066400000000000000000000037241420065311100317010ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.Handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.AttachmentKey; /** * Handler that adds an attachment to the request * * @author Stuart Douglas */ public class AttachmentHandler implements HttpHandler { private final AttachmentKey key; private volatile T instance; private volatile HttpHandler next; public AttachmentHandler(final AttachmentKey key, final HttpHandler next, final T instance) { this.next = next; this.key = key; this.instance = instance; } public AttachmentHandler(final AttachmentKey key, final HttpHandler next) { this(key, next, null); } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.putAttachment(key, instance); next.handleRequest(exchange); } public T getInstance() { return instance; } public void setInstance(final T instance) { this.instance = instance; } public HttpHandler getNext() { return next; } public void setNext(final HttpHandler next) { Handlers.handlerNotNull(next); this.next = next; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/BlockingHandler.java000066400000000000000000000055571420065311100313470ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.util.Collections; import java.util.Map; import java.util.Set; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; /** * A {@link HttpHandler} that initiates a blocking request. If the thread is currently running * in the io thread it will be dispatched. * * @author Stuart Douglas * @author David M. Lloyd */ public final class BlockingHandler implements HttpHandler { private volatile HttpHandler handler; public BlockingHandler(final HttpHandler handler) { this.handler = handler; } public BlockingHandler() { this(null); } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.startBlocking(); if (exchange.isInIoThread()) { exchange.dispatch(handler); } else { handler.handleRequest(exchange); } } public HttpHandler getHandler() { return handler; } public BlockingHandler setRootHandler(final HttpHandler rootHandler) { this.handler = rootHandler; return this; } @Override public String toString() { return "blocking()"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "blocking"; } @Override public Map> parameters() { return Collections.emptyMap(); } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { return new Wrapper(); } } private static class Wrapper implements HandlerWrapper { @Override public HttpHandler wrap(HttpHandler handler) { return new BlockingHandler(handler); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/BlockingReadTimeoutHandler.java000066400000000000000000000177671420065311100335200ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.server.ConduitWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.ServerConnection; import io.undertow.util.ConduitFactory; import org.xnio.IoUtils; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.ReadTimeoutException; import org.xnio.channels.StreamSinkChannel; import org.xnio.conduits.ReadReadyHandler; import org.xnio.conduits.StreamSourceConduit; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.time.Duration; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * {@link BlockingReadTimeoutHandler} allows configurable blocking I/O timeouts * for read operations within an exchange. *

* Unlike Options.READ_TIMEOUT this only applies to blocking operations which * can be helpful to prevent the worker pool from becoming saturated when * clients stop responding. *

* When a timeout occurs, a {@link ReadTimeoutException} is thrown, and the * {@link ServerConnection} is closed. * * @author Carter Kozak */ public final class BlockingReadTimeoutHandler implements HttpHandler { private final HttpHandler next; private final ConduitWrapper streamSourceConduitWrapper; private BlockingReadTimeoutHandler(HttpHandler next, Duration readTimeout) { this.next = next; this.streamSourceConduitWrapper = new TimeoutStreamSourceConduitWrapper(readTimeout); } private static final class TimeoutStreamSourceConduitWrapper implements ConduitWrapper { private final long timeoutNanoseconds; TimeoutStreamSourceConduitWrapper(Duration readTimeout) { this.timeoutNanoseconds = readTimeout.toNanos(); } @Override public StreamSourceConduit wrap(ConduitFactory factory, HttpServerExchange exchange) { return new TimeoutStreamSourceConduit(factory.create(), exchange.getConnection(), timeoutNanoseconds); } } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.addRequestWrapper(streamSourceConduitWrapper); next.handleRequest(exchange); } private static final class TimeoutStreamSourceConduit implements StreamSourceConduit { private final StreamSourceConduit delegate; private final ServerConnection serverConnection; private final long timeoutNanos; private long remaining; TimeoutStreamSourceConduit( StreamSourceConduit delegate, ServerConnection serverConnection, long timeoutNanos) { this.delegate = delegate; this.serverConnection = serverConnection; this.timeoutNanos = timeoutNanos; this.remaining = timeoutNanos; } @Override public long transferTo(long position, long count, FileChannel fileChannel) throws IOException { return resetTimeoutIfReadSucceeded(delegate.transferTo(position, count, fileChannel)); } @Override public long transferTo(long count, ByteBuffer byteBuffer, StreamSinkChannel streamSinkChannel) throws IOException { return resetTimeoutIfReadSucceeded(delegate.transferTo(count, byteBuffer, streamSinkChannel)); } @Override public int read(ByteBuffer byteBuffer) throws IOException { return resetTimeoutIfReadSucceeded(delegate.read(byteBuffer)); } @Override public long read(ByteBuffer[] byteBuffers, int offset, int length) throws IOException { return resetTimeoutIfReadSucceeded(delegate.read(byteBuffers, offset, length)); } @Override public void terminateReads() throws IOException { delegate.terminateReads(); } @Override public boolean isReadShutdown() { return delegate.isReadShutdown(); } @Override public void resumeReads() { delegate.resumeReads(); } @Override public void suspendReads() { delegate.suspendReads(); } @Override public void wakeupReads() { delegate.wakeupReads(); } @Override public boolean isReadResumed() { return delegate.isReadResumed(); } @Override public void awaitReadable() throws IOException { awaitReadable(remaining, TimeUnit.NANOSECONDS); } @Override public void awaitReadable(long duration, TimeUnit unit) throws IOException { long startTime = System.nanoTime(); long requestedNanos = unit.toNanos(duration); try { delegate.awaitReadable(Math.min(requestedNanos, remaining), TimeUnit.NANOSECONDS); } finally { remaining -= System.nanoTime() - startTime; } if (remaining < 0) { ReadTimeoutException rte = UndertowMessages.MESSAGES.blockingReadTimedOut(timeoutNanos); UndertowLogger.REQUEST_IO_LOGGER.blockingReadTimedOut(rte); IoUtils.safeClose(serverConnection); throw rte; } } @Override public XnioIoThread getReadThread() { return delegate.getReadThread(); } @Override public void setReadReadyHandler(ReadReadyHandler readReadyHandler) { delegate.setReadReadyHandler(readReadyHandler); } @Override public XnioWorker getWorker() { return delegate.getWorker(); } private long resetTimeoutIfReadSucceeded(long value) { if (value != 0) { // Reset the timeout remaining = timeoutNanos; } return value; } private int resetTimeoutIfReadSucceeded(int value) { if (value != 0) { // Reset the timeout remaining = timeoutNanos; } return value; } } public static Builder builder() { return new Builder(); } public static final class Builder { private HttpHandler nextHandler; private Duration readTimeout; private Builder() {} public Builder readTimeout(Duration readTimeout) { this.readTimeout = Objects.requireNonNull(readTimeout, "A read timeout is required"); return this; } public Builder nextHandler(HttpHandler nextHandler) { this.nextHandler = Objects.requireNonNull(nextHandler, "HttpHandler is required"); return this; } public HttpHandler build() { HttpHandler next = Objects.requireNonNull(nextHandler, "HttpHandler is required"); if (readTimeout == null) { throw new IllegalArgumentException("A read timeout is required"); } if (readTimeout.isZero() || readTimeout.isNegative()) { throw new IllegalArgumentException("Read timeout must be positive: " + readTimeout); } return new BlockingReadTimeoutHandler(next, readTimeout); } } } BlockingWriteTimeoutHandler.java000066400000000000000000000217301420065311100336410ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.server.ConduitWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.ServerConnection; import io.undertow.util.ConduitFactory; import org.xnio.IoUtils; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.StreamSourceChannel; import org.xnio.channels.WriteTimeoutException; import org.xnio.conduits.StreamSinkConduit; import org.xnio.conduits.WriteReadyHandler; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.time.Duration; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * BlockingTimeoutHandler allows configurable blocking I/O timeouts for write * operations within an exchange. *

* Unlike Options.WRITE_TIMEOUT this only applies to blocking operations which * can be helpful to prevent the worker pool from becoming saturated when * clients stop responding. *

* When a timeout occurs, a {@link WriteTimeoutException} is thrown, and the * {@link ServerConnection} is closed. * * @author Carter Kozak */ public final class BlockingWriteTimeoutHandler implements HttpHandler { private final HttpHandler next; private final ConduitWrapper streamSinkConduitWrapper; private BlockingWriteTimeoutHandler(HttpHandler next, Duration writeTimeout) { this.next = next; this.streamSinkConduitWrapper = new TimeoutStreamSinkConduitWrapper(writeTimeout); } private static final class TimeoutStreamSinkConduitWrapper implements ConduitWrapper { private final long timeoutNanoseconds; TimeoutStreamSinkConduitWrapper(Duration writeTimeout) { this.timeoutNanoseconds = writeTimeout.toNanos(); } @Override public StreamSinkConduit wrap(ConduitFactory factory, HttpServerExchange exchange) { return new TimeoutStreamSinkConduit(factory.create(), exchange.getConnection(), timeoutNanoseconds); } } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.addResponseWrapper(streamSinkConduitWrapper); next.handleRequest(exchange); } private static final class TimeoutStreamSinkConduit implements StreamSinkConduit { private final StreamSinkConduit delegate; private final ServerConnection serverConnection; private final long timeoutNanos; private long remaining; TimeoutStreamSinkConduit( StreamSinkConduit delegate, ServerConnection serverConnection, long timeoutNanos) { this.delegate = delegate; this.serverConnection = serverConnection; this.timeoutNanos = timeoutNanos; this.remaining = timeoutNanos; } @Override public long transferFrom(FileChannel fileChannel, long position, long count) throws IOException { return resetTimeoutIfWriteSucceeded(delegate.transferFrom(fileChannel, position, count)); } @Override public long transferFrom( StreamSourceChannel streamSourceChannel, long count, ByteBuffer byteBuffer) throws IOException { return resetTimeoutIfWriteSucceeded(delegate.transferFrom(streamSourceChannel, count, byteBuffer)); } @Override public int write(ByteBuffer byteBuffer) throws IOException { return resetTimeoutIfWriteSucceeded(delegate.write(byteBuffer)); } @Override public long write(ByteBuffer[] byteBuffers, int offset, int length) throws IOException { return resetTimeoutIfWriteSucceeded(delegate.write(byteBuffers, offset, length)); } @Override public int writeFinal(ByteBuffer byteBuffer) throws IOException { return resetTimeoutIfWriteSucceeded(delegate.writeFinal(byteBuffer)); } @Override public long writeFinal(ByteBuffer[] byteBuffers, int offset, int length) throws IOException { return resetTimeoutIfWriteSucceeded(delegate.writeFinal(byteBuffers, offset, length)); } @Override public void terminateWrites() throws IOException { delegate.terminateWrites(); } @Override public boolean isWriteShutdown() { return delegate.isWriteShutdown(); } @Override public void resumeWrites() { delegate.resumeWrites(); } @Override public void suspendWrites() { delegate.suspendWrites(); } @Override public void wakeupWrites() { delegate.wakeupWrites(); } @Override public boolean isWriteResumed() { return delegate.isWriteResumed(); } @Override public void awaitWritable() throws IOException { awaitWritable(remaining, TimeUnit.NANOSECONDS); } @Override public void awaitWritable(long duration, TimeUnit unit) throws IOException { long startTime = System.nanoTime(); long requestedNanos = unit.toNanos(duration); try { delegate.awaitWritable(Math.min(requestedNanos, remaining), TimeUnit.NANOSECONDS); } finally { remaining -= System.nanoTime() - startTime; } if (remaining < 0) { WriteTimeoutException wte = UndertowMessages.MESSAGES.blockingWriteTimedOut(timeoutNanos); UndertowLogger.REQUEST_IO_LOGGER.blockingWriteTimedOut(wte); IoUtils.safeClose(serverConnection); throw wte; } } @Override public XnioIoThread getWriteThread() { return delegate.getWriteThread(); } @Override public void setWriteReadyHandler(WriteReadyHandler writeReadyHandler) { delegate.setWriteReadyHandler(writeReadyHandler); } @Override public void truncateWrites() throws IOException { delegate.truncateWrites(); } @Override public boolean flush() throws IOException { return resetTimeoutIfWriteSucceeded(delegate.flush()); } @Override public XnioWorker getWorker() { return delegate.getWorker(); } private long resetTimeoutIfWriteSucceeded(long value) { if (value != 0) { // Reset the timeout remaining = timeoutNanos; } return value; } private int resetTimeoutIfWriteSucceeded(int value) { if (value != 0) { // Reset the timeout remaining = timeoutNanos; } return value; } private boolean resetTimeoutIfWriteSucceeded(boolean value) { if (value) { // Reset the timeout remaining = timeoutNanos; } return value; } } public static Builder builder() { return new Builder(); } public static final class Builder { private HttpHandler nextHandler; private Duration writeTimeout; private Builder() {} public Builder writeTimeout(Duration writeTimeout) { this.writeTimeout = Objects.requireNonNull(writeTimeout, "A write timeout is required"); return this; } public Builder nextHandler(HttpHandler nextHandler) { this.nextHandler = Objects.requireNonNull(nextHandler, "HttpHandler is required"); return this; } public HttpHandler build() { HttpHandler next = Objects.requireNonNull(nextHandler, "HttpHandler is required"); if (writeTimeout == null) { throw new IllegalArgumentException("A write timeout is required"); } if (writeTimeout.isZero() || writeTimeout.isNegative()) { throw new IllegalArgumentException("Write timeout must be positive: " + writeTimeout); } return new BlockingWriteTimeoutHandler(next, writeTimeout); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/ByteRangeHandler.java000066400000000000000000000153631420065311100314730ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.conduits.HeadStreamSinkConduit; import io.undertow.conduits.RangeStreamSinkConduit; import io.undertow.server.ConduitWrapper; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.ResponseCommitListener; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.ByteRange; import io.undertow.util.ConduitFactory; import io.undertow.util.DateUtils; import io.undertow.util.Headers; import io.undertow.util.Methods; import io.undertow.util.StatusCodes; import org.xnio.conduits.StreamSinkConduit; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * Handler for Range requests. This is a generic handler that can handle range requests to any resource * of a fixed content length i.e. any resource where the content-length header has been set. * * Note that this is not necessarily the most efficient way to handle range requests, as the full content * will be generated and then discarded. * * At present this handler can only handle simple (i.e. single range) requests. If multiple ranges are requested the * Range header will be ignored. * * @author Stuart Douglas */ public class ByteRangeHandler implements HttpHandler { private final HttpHandler next; private final boolean sendAcceptRanges; private static final ResponseCommitListener ACCEPT_RANGE_LISTENER = new ResponseCommitListener() { @Override public void beforeCommit(HttpServerExchange exchange) { if(!exchange.getResponseHeaders().contains(Headers.ACCEPT_RANGES)) { if (exchange.getResponseHeaders().contains(Headers.CONTENT_LENGTH)) { exchange.getResponseHeaders().put(Headers.ACCEPT_RANGES, "bytes"); } else { exchange.getResponseHeaders().put(Headers.ACCEPT_RANGES, "none"); } } } }; public ByteRangeHandler(HttpHandler next, boolean sendAcceptRanges) { this.next = next; this.sendAcceptRanges = sendAcceptRanges; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { //range requests are only support for GET requests as per the RFC if(!Methods.GET.equals(exchange.getRequestMethod()) && !Methods.HEAD.equals(exchange.getRequestMethod())) { next.handleRequest(exchange); return; } if (sendAcceptRanges) { exchange.addResponseCommitListener(ACCEPT_RANGE_LISTENER); } final ByteRange range = ByteRange.parse(exchange.getRequestHeaders().getFirst(Headers.RANGE)); if (range != null && range.getRanges() == 1) { exchange.addResponseWrapper(new ConduitWrapper() { @Override public StreamSinkConduit wrap(ConduitFactory factory, HttpServerExchange exchange) { if(exchange.getStatusCode() != StatusCodes.OK ) { return factory.create(); } String length = exchange.getResponseHeaders().getFirst(Headers.CONTENT_LENGTH); if (length == null) { return factory.create(); } long responseLength = Long.parseLong(length); String lastModified = exchange.getResponseHeaders().getFirst(Headers.LAST_MODIFIED); ByteRange.RangeResponseResult rangeResponse = range.getResponseResult(responseLength, exchange.getRequestHeaders().getFirst(Headers.IF_RANGE), lastModified == null ? null : DateUtils.parseDate(lastModified), exchange.getResponseHeaders().getFirst(Headers.ETAG)); if(rangeResponse != null){ long start = rangeResponse.getStart(); long end = rangeResponse.getEnd(); exchange.setStatusCode(rangeResponse.getStatusCode()); exchange.getResponseHeaders().put(Headers.CONTENT_RANGE, rangeResponse.getContentRange()); exchange.setResponseContentLength(rangeResponse.getContentLength()); if(rangeResponse.getStatusCode() == StatusCodes.REQUEST_RANGE_NOT_SATISFIABLE) { return new HeadStreamSinkConduit(factory.create(), null, true); } return new RangeStreamSinkConduit(factory.create(), start, end, responseLength); } else { return factory.create(); } } }); } next.handleRequest(exchange); } @Override public String toString() { return "byte-range( " + sendAcceptRanges + " )"; } public static class Wrapper implements HandlerWrapper { private final boolean sendAcceptRanges; public Wrapper(boolean sendAcceptRanges) { this.sendAcceptRanges = sendAcceptRanges; } @Override public HttpHandler wrap(HttpHandler handler) { return new ByteRangeHandler(handler, sendAcceptRanges); } } public static class Builder implements HandlerBuilder { @Override public String name() { return "byte-range"; } @Override public Map> parameters() { Map> params = new HashMap<>(); params.put("send-accept-ranges", boolean.class); return params; } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return "send-accept-ranges"; } @Override public HandlerWrapper build(Map config) { Boolean send = (Boolean) config.get("send-accept-ranges"); return new Wrapper(send != null && send); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/CanonicalPathHandler.java000066400000000000000000000053311420065311100323110ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.util.Collections; import java.util.Map; import java.util.Set; import io.undertow.Handlers; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.CanonicalPathUtils; /** * @author Stuart Douglas */ public class CanonicalPathHandler implements HttpHandler { private volatile HttpHandler next = ResponseCodeHandler.HANDLE_404; public CanonicalPathHandler() { } public CanonicalPathHandler(final HttpHandler next) { this.next = next; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.setRelativePath(CanonicalPathUtils.canonicalize(exchange.getRelativePath())); next.handleRequest(exchange); } public HttpHandler getNext() { return next; } public CanonicalPathHandler setNext(final HttpHandler next) { Handlers.handlerNotNull(next); this.next = next; return this; } @Override public String toString() { return "canonical-path()"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "canonical-path"; } @Override public Map> parameters() { return Collections.emptyMap(); } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { return new Wrapper(); } } private static class Wrapper implements HandlerWrapper { @Override public HttpHandler wrap(HttpHandler handler) { return new CanonicalPathHandler(handler); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/ChannelUpgradeHandler.java000066400000000000000000000204631420065311100324700ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.Handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpUpgradeListener; import io.undertow.util.CopyOnWriteMap; import io.undertow.util.Headers; import io.undertow.util.Methods; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.StreamConnection; import java.util.Iterator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** * An HTTP request handler which upgrades the HTTP request and hands it off as a socket to any XNIO consumer. * * @author David M. Lloyd * @author Stuart Douglas */ public final class ChannelUpgradeHandler implements HttpHandler { private final CopyOnWriteMap> handlers = new CopyOnWriteMap<>(); private volatile HttpHandler nonUpgradeHandler = ResponseCodeHandler.HANDLE_404; /** * Add a protocol to this handler. * * @param productString the product string to match * @param openListener the open listener to call * @param handshake a handshake implementation that can be used to verify the client request and modify the response */ public synchronized void addProtocol(String productString, ChannelListener openListener, final HttpUpgradeHandshake handshake) { addProtocol(productString, null, openListener, handshake); } /** * Add a protocol to this handler. * * @param productString the product string to match * @param openListener the open listener to call * @param handshake a handshake implementation that can be used to verify the client request and modify the response */ public synchronized void addProtocol(String productString, HttpUpgradeListener openListener, final HttpUpgradeHandshake handshake) { addProtocol(productString, openListener, null, handshake); } private synchronized void addProtocol(String productString, HttpUpgradeListener openListener, final ChannelListener channelListener, final HttpUpgradeHandshake handshake) { if (productString == null) { throw new IllegalArgumentException("productString is null"); } if (openListener == null && channelListener == null) { throw new IllegalArgumentException("openListener is null"); } if(openListener == null) { openListener = new HttpUpgradeListener() { @Override public void handleUpgrade(StreamConnection streamConnection, HttpServerExchange exchange) { ChannelListeners.invokeChannelListener(streamConnection, channelListener); } }; } List list = handlers.get(productString); if (list == null) { handlers.put(productString, list = new CopyOnWriteArrayList<>()); } list.add(new Holder(openListener, handshake, channelListener)); } /** * Add a protocol to this handler. * * @param productString the product string to match * @param openListener the open listener to call */ public void addProtocol(String productString, ChannelListener openListener) { addProtocol(productString, openListener, null); } /** * Add a protocol to this handler. * * @param productString the product string to match * @param openListener the open listener to call */ public void addProtocol(String productString, HttpUpgradeListener openListener) { addProtocol(productString, openListener, null); } /** * Remove a protocol from this handler. This will remove all upgrade handlers that match the product string * * @param productString the product string to match */ public synchronized void removeProtocol(String productString) { handlers.remove(productString); } /** * Remove a protocol from this handler. * * @param productString the product string to match * @param openListener The open listener */ public synchronized void removeProtocol(String productString, ChannelListener openListener) { List holders = handlers.get(productString); if (holders == null) { return; } Iterator it = holders.iterator(); while (it.hasNext()) { Holder holder = it.next(); if (holder.channelListener == openListener) { holders.remove(holder); break; } } if (holders.isEmpty()) { handlers.remove(productString); } } /** * Remove a protocol from this handler. * * @param productString the product string to match * @param upgradeListener The upgrade listener */ public synchronized void removeProtocol(String productString, HttpUpgradeListener upgradeListener) { List holders = handlers.get(productString); if (holders == null) { return; } Iterator it = holders.iterator(); while (it.hasNext()) { Holder holder = it.next(); if (holder.listener == upgradeListener) { holders.remove(holder); break; } } if (holders.isEmpty()) { handlers.remove(productString); } } /** * Get the non-upgrade delegate handler. * * @return the non-upgrade delegate handler */ public HttpHandler getNonUpgradeHandler() { return nonUpgradeHandler; } /** * Set the non-upgrade delegate handler. * * @param nonUpgradeHandler the non-upgrade delegate handler */ public ChannelUpgradeHandler setNonUpgradeHandler(final HttpHandler nonUpgradeHandler) { Handlers.handlerNotNull(nonUpgradeHandler); this.nonUpgradeHandler = nonUpgradeHandler; return this; } public void handleRequest(final HttpServerExchange exchange) throws Exception { final List upgradeStrings = exchange.getRequestHeaders().get(Headers.UPGRADE); if (upgradeStrings != null && exchange.getRequestMethod().equals(Methods.GET)) { for (String string : upgradeStrings) { final List holders = handlers.get(string); if (holders != null) { for (Holder holder : holders) { final HttpUpgradeListener listener = holder.listener; if (holder.handshake != null) { if (!holder.handshake.handleUpgrade(exchange)) { //handshake did not match, try again continue; } } exchange.upgradeChannel(string,listener); exchange.endExchange(); return; } } } } nonUpgradeHandler.handleRequest(exchange); } private static final class Holder { final HttpUpgradeListener listener; final HttpUpgradeHandshake handshake; final ChannelListener channelListener; private Holder(final HttpUpgradeListener listener, final HttpUpgradeHandshake handshake, ChannelListener channelListener) { this.listener = listener; this.handshake = handshake; this.channelListener = channelListener; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/ConfiguredPushHandler.java000066400000000000000000000046641420065311100325420ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.HeaderMap; import io.undertow.util.HttpString; import io.undertow.util.Methods; import io.undertow.util.PathMatcher; /** * Handler that pushes resources based on a provided URL * * @author Stuart Douglas */ public class ConfiguredPushHandler implements HttpHandler { private final PathMatcher pathMatcher = new PathMatcher<>(); private final HttpHandler next; private final HeaderMap requestHeaders = new HeaderMap(); public ConfiguredPushHandler(HttpHandler next) { this.next = next; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if(exchange.getConnection().isPushSupported()) { PathMatcher.PathMatch result = pathMatcher.match(exchange.getRelativePath()); if(result != null) { String[] value = result.getValue(); for(int i = 0; i < value.length; ++i) { exchange.getConnection().pushResource(value[i], Methods.GET, requestHeaders); } } } next.handleRequest(exchange); } public ConfiguredPushHandler addRequestHeader(HttpString name, String value) { requestHeaders.put(name, value); return this; } public ConfiguredPushHandler addRoute(String url, String ... resourcesToPush) { if(url.endsWith("/*")) { String partial = url.substring(0, url.length() - 1); pathMatcher.addPrefixPath(partial, resourcesToPush); } else { pathMatcher.addExactPath(url, resourcesToPush); } return this; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/ConnectHandler.java000066400000000000000000000135761420065311100312100ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.predicate.Predicate; import io.undertow.predicate.Predicates; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpUpgradeListener; import io.undertow.util.Methods; import io.undertow.util.SameThreadExecutor; import io.undertow.util.StatusCodes; import io.undertow.util.Transfer; import org.xnio.ChannelExceptionHandler; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoFuture; import org.xnio.IoUtils; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.StreamConnection; import org.xnio.channels.StreamSinkChannel; import java.io.Closeable; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.Channel; /** * * Handlers HTTP CONNECT requests, allowing the server to act as a forward proxy. * * WARNING: Do not enable this without some kind of authentication / IP based restriction scheme * in place, as this will allow malicious actors to use your server as an open relay. * * @author Stuart Douglas */ public class ConnectHandler implements HttpHandler { private final HttpHandler next; private final Predicate allowed; public ConnectHandler(HttpHandler next) { this(next, Predicates.truePredicate()); } public ConnectHandler(HttpHandler next, Predicate allowed) { this.next = next; this.allowed = allowed; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { if(exchange.getRequestMethod().equals(Methods.CONNECT)) { if(!allowed.resolve(exchange)) { exchange.setStatusCode(StatusCodes.METHOD_NOT_ALLOWED);//not sure if this is the best response return; } String[] parts = exchange.getRequestPath().split(":"); if(parts.length != 2) { exchange.setStatusCode(StatusCodes.BAD_REQUEST);//not sure if this is the best response return; } final String host = parts[0]; final Integer port = Integer.parseInt(parts[1]); exchange.dispatch(SameThreadExecutor.INSTANCE, new Runnable() { @Override public void run() { exchange.getConnection().getIoThread().openStreamConnection(new InetSocketAddress(host, port), new ChannelListener() { @Override public void handleEvent(final StreamConnection clientChannel) { exchange.acceptConnectRequest(new HttpUpgradeListener() { @Override public void handleUpgrade(StreamConnection streamConnection, HttpServerExchange exchange) { final ClosingExceptionHandler handler = new ClosingExceptionHandler(streamConnection, clientChannel); Transfer.initiateTransfer(clientChannel.getSourceChannel(), streamConnection.getSinkChannel(), ChannelListeners.closingChannelListener(), ChannelListeners.writeShutdownChannelListener(ChannelListeners.flushingChannelListener(ChannelListeners.closingChannelListener(), ChannelListeners.closingChannelExceptionHandler()), ChannelListeners.closingChannelExceptionHandler()), handler, handler, exchange.getConnection().getByteBufferPool()); Transfer.initiateTransfer(streamConnection.getSourceChannel(), clientChannel.getSinkChannel(), ChannelListeners.closingChannelListener(), ChannelListeners.writeShutdownChannelListener(ChannelListeners.flushingChannelListener(ChannelListeners.closingChannelListener(), ChannelListeners.closingChannelExceptionHandler()), ChannelListeners.closingChannelExceptionHandler()), handler, handler, exchange.getConnection().getByteBufferPool()); } }); exchange.setStatusCode(200); exchange.endExchange(); } }, OptionMap.create(Options.TCP_NODELAY, true)).addNotifier(new IoFuture.Notifier() { @Override public void notify(IoFuture ioFuture, Object attachment) { if(ioFuture.getStatus() == IoFuture.Status.FAILED) { exchange.setStatusCode(503); exchange.endExchange(); } } },null); } }); } else { next.handleRequest(exchange); } } private static final class ClosingExceptionHandler implements ChannelExceptionHandler { private final Closeable[] toClose; private ClosingExceptionHandler(Closeable... toClose) { this.toClose = toClose; } @Override public void handleException(Channel channel, IOException exception) { IoUtils.safeClose(channel); IoUtils.safeClose(toClose); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/Cookie.java000066400000000000000000000060241420065311100275200ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.util.Date; /** * A HTTP cookie. * * @see io.undertow.server.Connectors * @author Stuart Douglas */ public interface Cookie extends Comparable { String getName(); String getValue(); Cookie setValue(final String value); String getPath(); Cookie setPath(final String path); String getDomain(); Cookie setDomain(final String domain); Integer getMaxAge(); Cookie setMaxAge(final Integer maxAge); boolean isDiscard(); Cookie setDiscard(final boolean discard); boolean isSecure(); Cookie setSecure(final boolean secure); int getVersion(); Cookie setVersion(final int version); boolean isHttpOnly(); Cookie setHttpOnly(final boolean httpOnly); Date getExpires(); Cookie setExpires(final Date expires); String getComment(); Cookie setComment(final String comment); default boolean isSameSite() { return false; } default Cookie setSameSite(final boolean sameSite) { throw new UnsupportedOperationException("Not implemented"); } default String getSameSiteMode() { return null; } default Cookie setSameSiteMode(final String mode) { throw new UnsupportedOperationException("Not implemented"); } @Override default int compareTo(final Object other) { final Cookie o = (Cookie) other; int retVal = 0; // compare names if (getName() == null && o.getName() != null) return -1; if (getName() != null && o.getName() == null) return 1; retVal = (getName() == null && o.getName() == null) ? 0 : getName().compareTo(o.getName()); if (retVal != 0) return retVal; // compare paths if (getPath() == null && o.getPath() != null) return -1; if (getPath() != null && o.getPath() == null) return 1; retVal = (getPath() == null && o.getPath() == null) ? 0 : getPath().compareTo(o.getPath()); if (retVal != 0) return retVal; // compare domains if (getDomain() == null && o.getDomain() != null) return -1; if (getDomain() != null && o.getDomain() == null) return 1; retVal = (getDomain() == null && o.getDomain() == null) ? 0 : getDomain().compareTo(o.getDomain()); if (retVal != 0) return retVal; return 0; // equal } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/CookieImpl.java000066400000000000000000000132211420065311100303370ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.util.Arrays; import java.util.Date; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; /** * @author Stuart Douglas * @author Richard Opalka */ public class CookieImpl implements Cookie { private final String name; private String value; private String path; private String domain; private Integer maxAge; private Date expires; private boolean discard; private boolean secure; private boolean httpOnly; private int version = 0; private String comment; private boolean sameSite; private String sameSiteMode; public CookieImpl(final String name, final String value) { this.name = name; this.value = value; } public CookieImpl(final String name) { this.name = name; } public String getName() { return name; } public String getValue() { return value; } public CookieImpl setValue(final String value) { this.value = value; return this; } public String getPath() { return path; } public CookieImpl setPath(final String path) { this.path = path; return this; } public String getDomain() { return domain; } public CookieImpl setDomain(final String domain) { this.domain = domain; return this; } public Integer getMaxAge() { return maxAge; } public CookieImpl setMaxAge(final Integer maxAge) { this.maxAge = maxAge; return this; } public boolean isDiscard() { return discard; } public CookieImpl setDiscard(final boolean discard) { this.discard = discard; return this; } public boolean isSecure() { return secure; } public CookieImpl setSecure(final boolean secure) { this.secure = secure; return this; } public int getVersion() { return version; } public CookieImpl setVersion(final int version) { this.version = version; return this; } public boolean isHttpOnly() { return httpOnly; } public CookieImpl setHttpOnly(final boolean httpOnly) { this.httpOnly = httpOnly; return this; } public Date getExpires() { return expires; } public CookieImpl setExpires(final Date expires) { this.expires = expires; return this; } public String getComment() { return comment; } public Cookie setComment(final String comment) { this.comment = comment; return this; } @Override public boolean isSameSite() { return sameSite; } @Override public Cookie setSameSite(final boolean sameSite) { this.sameSite = sameSite; return this; } @Override public String getSameSiteMode() { return sameSiteMode; } @Override public Cookie setSameSiteMode(final String mode) { final String m = CookieSameSiteMode.lookupModeString(mode); if (m != null) { UndertowLogger.REQUEST_LOGGER.tracef("Setting SameSite mode to [%s] for cookie [%s]", m, this.name); this.sameSiteMode = m; this.setSameSite(true); } else { UndertowLogger.REQUEST_LOGGER.warnf(UndertowMessages.MESSAGES.invalidSameSiteMode(mode, Arrays.toString(CookieSameSiteMode.values())), "Ignoring specified SameSite mode [%s] for cookie [%s]", mode, this.name); } return this; } @Override public final int hashCode() { int result = 17; result = 37 * result + (getName() == null ? 0 : getName().hashCode()); result = 37 * result + (getPath() == null ? 0 : getPath().hashCode()); result = 37 * result + (getDomain() == null ? 0 : getDomain().hashCode()); return result; } @Override public final boolean equals(final Object other) { if (other == this) return true; if (!(other instanceof Cookie)) return false; final Cookie o = (Cookie) other; // compare names if (getName() == null && o.getName() != null) return false; if (getName() != null && !getName().equals(o.getName())) return false; // compare paths if (getPath() == null && o.getPath() != null) return false; if (getPath() != null && !getPath().equals(o.getPath())) return false; // compare domains if (getDomain() == null && o.getDomain() != null) return false; if (getDomain() != null && !getDomain().equals(o.getDomain())) return false; // same cookie return true; } @Override public final int compareTo(final Object other) { return Cookie.super.compareTo(other); } @Override public final String toString() { return "{CookieImpl@" + System.identityHashCode(this) + " name=" + getName() + " path=" + getPath() + " domain=" + getDomain() + "}"; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/CookieSameSiteMode.java000066400000000000000000000044151420065311100317620ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; public enum CookieSameSiteMode { /** * The browser will only send cookies for same-site requests (requests originating from the site that set the cookie). * If the request originated from a different URL than the URL of the current location, none of the cookies tagged * with the Strict attribute will be included. */ STRICT("Strict"), /** * Same-site cookies are withheld on cross-site subrequests, such as calls to load images or frames, but will be sent * when a user navigates to the URL from an external site; for example, by following a link. */ LAX("Lax"), /** * The browser will send cookies with both cross-site requests and same-site requests. */ NONE("None"); private static final CookieSameSiteMode[] SAMESITE_MODES = values(); /** * Just use a human friendly label instead of the capitalized name. */ private final String label; CookieSameSiteMode(String label) { this.label = label; } /** * lookup from the specified value and return a correct SameSiteMode string. * * @param mode * @return A name of SameSite mode. Returns {@code null} if an invalid SameSite mode is specified. */ public static String lookupModeString(String mode) { for (CookieSameSiteMode m : SAMESITE_MODES) { if (m.name().equalsIgnoreCase(mode)) { return m.toString(); } } return null; } @Override public String toString() { return label; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/DateHandler.java000066400000000000000000000044471420065311100304710ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.util.Date; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.DateUtils; import io.undertow.util.Headers; /** * Class that adds the Date: header to a HTTP response. * * The current date string is cached, and is updated every second in a racey * manner (i.e. it is possible for two thread to update it at once). *

* This handler is deprecated, the same functionality is achieved by using the * server option {@link io.undertow.UndertowOptions#ALWAYS_SET_DATE ALWAYS_SET_DATE}. * It is enabled by default. * * @author Stuart Douglas */ @Deprecated() public class DateHandler implements HttpHandler { private final HttpHandler next; private volatile String cachedDateString; private volatile long nextUpdateTime = -1; public DateHandler(final HttpHandler next) { this.next = next; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { // better method is used in DateUtils#addDateHeaderIfRequired long time = System.nanoTime(); if(time < nextUpdateTime) { exchange.getResponseHeaders().put(Headers.DATE, cachedDateString); } else { long realTime = System.currentTimeMillis(); String dateString = DateUtils.toDateString(new Date(realTime)); cachedDateString = dateString; nextUpdateTime = time + 1000000000; exchange.getResponseHeaders().put(Headers.DATE, dateString); } next.handleRequest(exchange); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/DisableCacheHandler.java000066400000000000000000000036101420065311100320720ustar00rootroot00000000000000package io.undertow.server.handlers; import java.util.Collections; import java.util.Map; import java.util.Set; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.Headers; /** * * Handler that disables response caching by browsers and proxies. * * * @author Stuart Douglas */ public class DisableCacheHandler implements HttpHandler { private final HttpHandler next; public DisableCacheHandler(HttpHandler next) { this.next = next; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseHeaders().add(Headers.CACHE_CONTROL, "no-cache, no-store, must-revalidate"); exchange.getResponseHeaders().add(Headers.PRAGMA, "no-cache"); exchange.getResponseHeaders().add(Headers.EXPIRES, "0"); next.handleRequest(exchange); } @Override public String toString() { return "disable-cache()"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "disable-cache"; } @Override public Map> parameters() { return Collections.emptyMap(); } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { return new Wrapper(); } } private static class Wrapper implements HandlerWrapper { @Override public HttpHandler wrap(HttpHandler handler) { return new DisableCacheHandler(handler); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/DisallowedMethodsHandler.java000066400000000000000000000073371420065311100332300ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.HttpString; import io.undertow.util.StatusCodes; import java.util.stream.Collectors; /** * Handler that blacklists certain HTTP methods. * * @author Stuart Douglas */ public class DisallowedMethodsHandler implements HttpHandler { private final Set disallowedMethods; private final HttpHandler next; public DisallowedMethodsHandler(final HttpHandler next, final Set disallowedMethods) { this.disallowedMethods = new HashSet<>(disallowedMethods); this.next = next; } public DisallowedMethodsHandler(final HttpHandler next, final HttpString... disallowedMethods) { this.disallowedMethods = new HashSet<>(Arrays.asList(disallowedMethods)); this.next = next; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { if (disallowedMethods.contains(exchange.getRequestMethod())) { exchange.setStatusCode(StatusCodes.METHOD_NOT_ALLOWED); exchange.endExchange(); } else { next.handleRequest(exchange); } } @Override public String toString() { if (disallowedMethods.size() == 1) { return "disallowed-methods( " + disallowedMethods.toArray()[0] + " )"; } else { return "disallowed-methods( {" + disallowedMethods.stream().map(s -> s.toString()).collect(Collectors.joining(", ")) + "} )"; } } public static class Builder implements HandlerBuilder { @Override public String name() { return "disallowed-methods"; } @Override public Map> parameters() { return Collections.>singletonMap("methods", String[].class); } @Override public Set requiredParameters() { return Collections.singleton("methods"); } @Override public String defaultParameter() { return "methods"; } @Override public HandlerWrapper build(Map config) { return new Wrapper((String[]) config.get("methods")); } } private static class Wrapper implements HandlerWrapper { private final String[] methods; private Wrapper(String[] methods) { this.methods = methods; } @Override public HttpHandler wrap(HttpHandler handler) { HttpString[] strings = new HttpString[methods.length]; for(int i = 0; i < methods.length; ++i) { strings[i] = new HttpString(methods[i]); } return new DisallowedMethodsHandler(handler, strings); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/ExceptionHandler.java000066400000000000000000000041771420065311100315520ustar00rootroot00000000000000package io.undertow.server.handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.AttachmentKey; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** * Handler that dispatches to a given handler and allows mapping exceptions * to be handled by additional handlers. The order the exception handlers are * added is important because of inheritance. Add all child classes before their * parents in order to use different handlers. */ public class ExceptionHandler implements HttpHandler { public static final AttachmentKey THROWABLE = AttachmentKey.create(Throwable.class); private final HttpHandler handler; private final List> exceptionHandlers = new CopyOnWriteArrayList<>(); public ExceptionHandler(HttpHandler handler) { this.handler = handler; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { try { handler.handleRequest(exchange); } catch (Throwable throwable) { for (ExceptionHandlerHolder holder : exceptionHandlers) { if (holder.getClazz().isInstance(throwable)) { exchange.putAttachment(THROWABLE, throwable); holder.getHandler().handleRequest(exchange); return; } } throw throwable; } } public ExceptionHandler addExceptionHandler(Class clazz, HttpHandler handler) { exceptionHandlers.add(new ExceptionHandlerHolder<>(clazz, handler)); return this; } private static class ExceptionHandlerHolder { private final Class clazz; private final HttpHandler handler; ExceptionHandlerHolder(Class clazz, HttpHandler handler) { super(); this.clazz = clazz; this.handler = handler; } public Class getClazz() { return clazz; } public HttpHandler getHandler() { return handler; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/ForwardedHandler.java000066400000000000000000000260731420065311100315300ustar00rootroot00000000000000package io.undertow.server.handlers; import io.undertow.UndertowLogger; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.HeaderValues; import io.undertow.util.Headers; import io.undertow.util.NetworkUtils; import java.net.InetSocketAddress; import java.util.Collections; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Set; import static io.undertow.UndertowMessages.MESSAGES; /** * Handler that implements rfc7239 Forwarded header * * @author Stuart Douglas */ public class ForwardedHandler implements HttpHandler { public static final String BY = "by"; public static final String FOR = "for"; public static final String HOST = "host"; public static final String PROTO = "proto"; private static final String UNKNOWN = "unknown"; private static final boolean DEFAULT_CHANGE_LOCAL_ADDR_PORT = Boolean.getBoolean("io.undertow.forwarded.change-local-addr-port"); private static final String CHANGE_LOCAL_ADDR_PORT = "change-local-addr-port"; private final boolean isChangeLocalAddrPort; private final HttpHandler next; public ForwardedHandler(HttpHandler next) { this(next, DEFAULT_CHANGE_LOCAL_ADDR_PORT); } public ForwardedHandler(HttpHandler next, boolean isChangeLocalAddrPort) { this.next = next; this.isChangeLocalAddrPort = isChangeLocalAddrPort; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { HeaderValues forwarded = exchange.getRequestHeaders().get(Headers.FORWARDED); if (forwarded != null) { Map values = new HashMap<>(); for (String val : forwarded) { parseHeader(val, values); } String host = values.get(Token.HOST); String proto = values.get(Token.PROTO); String by = values.get(Token.BY); String forVal = values.get(Token.FOR); if (host != null) { exchange.getRequestHeaders().put(Headers.HOST, host); if (isChangeLocalAddrPort) { exchange.setDestinationAddress(InetSocketAddress.createUnresolved(exchange.getHostName(), exchange.getHostPort())); } } else if (by != null) { //we only use 'by' if the host is null InetSocketAddress destAddress = parseAddress(by); if (destAddress != null && isChangeLocalAddrPort) { exchange.setDestinationAddress(destAddress); } } if (proto != null) { exchange.setRequestScheme(proto); } if (forVal != null) { InetSocketAddress sourceAddress = parseAddress(forVal); if (sourceAddress != null) { exchange.setSourceAddress(sourceAddress); } } } next.handleRequest(exchange); } static InetSocketAddress parseAddress(String address) { try { if (address.equals(UNKNOWN)) { return null; } if (address.startsWith("_")) { //obfnode, not much we can do with it //if a client cares about it they will need to parse the header themselves return null; } if (address.startsWith("[")) { //ipv6 address int index = address.indexOf("]"); String ipPart = address.substring(1, index); int pos = address.indexOf(':', index); if (pos == -1) { return new InetSocketAddress(NetworkUtils.parseIpv6Address(ipPart), 0); } else { return new InetSocketAddress(NetworkUtils.parseIpv6Address(ipPart), parsePort(address.substring(pos + 1))); } } else { int pos = address.indexOf(':'); if (pos == -1) { return new InetSocketAddress(NetworkUtils.parseIpv4Address(address), 0); } else { return new InetSocketAddress(NetworkUtils.parseIpv4Address(address.substring(0, pos)), parsePort(address.substring(pos + 1))); } } } catch (Exception e) { UndertowLogger.REQUEST_IO_LOGGER.debug("Failed to parse address", e); return null; } } private static int parsePort(String substring) { if (substring.startsWith("_")) { return 0; } return Integer.parseInt(substring); } //package private for testing static void parseHeader(final String header, Map response) { if (response.size() == Token.values().length) { //already parsed everything return; } char[] headerChars = header.toCharArray(); SearchingFor searchingFor = SearchingFor.START_OF_NAME; int nameStart = 0; Token currentToken = null; int valueStart = 0; int escapeCount = 0; boolean containsEscapes = false; for (int i = 0; i < headerChars.length; i++) { switch (searchingFor) { case START_OF_NAME: // Eliminate any white space before the name of the parameter. if (headerChars[i] != ';' && !Character.isWhitespace(headerChars[i])) { nameStart = i; searchingFor = SearchingFor.EQUALS_SIGN; } break; case EQUALS_SIGN: if (headerChars[i] == '=') { String paramName = String.valueOf(headerChars, nameStart, i - nameStart); currentToken = TOKENS.get(paramName.toLowerCase(Locale.ENGLISH)); //we allow unkown tokens, but just ignore them searchingFor = SearchingFor.START_OF_VALUE; } break; case START_OF_VALUE: if (!Character.isWhitespace(headerChars[i])) { if (headerChars[i] == '"') { valueStart = i + 1; searchingFor = SearchingFor.LAST_QUOTE; } else { valueStart = i; searchingFor = SearchingFor.END_OF_VALUE; } } break; case LAST_QUOTE: if (headerChars[i] == '\\') { escapeCount++; containsEscapes = true; } else if (headerChars[i] == '"' && (escapeCount % 2 == 0)) { String value = String.valueOf(headerChars, valueStart, i - valueStart); if (containsEscapes) { StringBuilder sb = new StringBuilder(); boolean lastEscape = false; for (int j = 0; j < value.length(); ++j) { char c = value.charAt(j); if (c == '\\' && !lastEscape) { lastEscape = true; } else { lastEscape = false; sb.append(c); } } value = sb.toString(); containsEscapes = false; } if (currentToken != null && !response.containsKey(currentToken)) { response.put(currentToken, value); } searchingFor = SearchingFor.START_OF_NAME; escapeCount = 0; } else { escapeCount = 0; } break; case END_OF_VALUE: if (headerChars[i] == ';' || headerChars[i] == ',' || Character.isWhitespace(headerChars[i])) { String value = String.valueOf(headerChars, valueStart, i - valueStart); if (currentToken != null && !response.containsKey(currentToken)) { response.put(currentToken, value); } searchingFor = SearchingFor.START_OF_NAME; } break; } } if (searchingFor == SearchingFor.END_OF_VALUE) { // Special case where we reached the end of the array containing the header values. String value = String.valueOf(headerChars, valueStart, headerChars.length - valueStart); if (currentToken != null && !response.containsKey(currentToken)) { response.put(currentToken, value); } } else if (searchingFor != SearchingFor.START_OF_NAME) { // Somehow we are still in the middle of searching for a current value. throw MESSAGES.invalidHeader(); } } enum Token { BY, FOR, HOST, PROTO } private static final Map TOKENS; static { Map map = new HashMap<>(); for (Token token : Token.values()) { map.put(token.name().toLowerCase(), token); } TOKENS = Collections.unmodifiableMap(map); } private enum SearchingFor { START_OF_NAME, EQUALS_SIGN, START_OF_VALUE, LAST_QUOTE, END_OF_VALUE; } @Override public String toString() { return "forwarded()"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "forwarded"; } @Override public Map> parameters() { Map> params = new HashMap<>(); params.put(CHANGE_LOCAL_ADDR_PORT, boolean.class); return params; } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return CHANGE_LOCAL_ADDR_PORT; } @Override public HandlerWrapper build(Map config) { Boolean isChangeLocalAddrPort = (Boolean) config.get(CHANGE_LOCAL_ADDR_PORT); return new Wrapper(isChangeLocalAddrPort == null ? DEFAULT_CHANGE_LOCAL_ADDR_PORT : isChangeLocalAddrPort); } } private static class Wrapper implements HandlerWrapper { private final boolean isChangeLocalAddrPort; private Wrapper(boolean isChangeLocalAddrPort) { this.isChangeLocalAddrPort = isChangeLocalAddrPort; } @Override public HttpHandler wrap(HttpHandler handler) { return new ForwardedHandler(handler, isChangeLocalAddrPort); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/GracefulShutdownHandler.java000066400000000000000000000162451420065311100330770ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.UndertowMessages; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.StatusCodes; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLongFieldUpdater; import java.util.function.LongUnaryOperator; /** * Handler that allows for graceful server shutdown. Basically it provides a way to prevent the server from * accepting new requests, and wait for existing requests to complete. *

* The handler itself does not shut anything down. *

* Import: The thread safety semantics of the handler are very important. Don't touch anything unless you know * what you are doing. * * @author Stuart Douglas */ public class GracefulShutdownHandler implements HttpHandler { private static final long SHUTDOWN_MASK = 1L << 63; private static final long ACTIVE_COUNT_MASK = (1L << 63) - 1; private static final LongUnaryOperator incrementActive = current -> { long incrementedActiveCount = activeCount(current) + 1; return incrementedActiveCount | (current & ~ACTIVE_COUNT_MASK); }; private static final LongUnaryOperator incrementActiveAndShutdown = incrementActive.andThen(current -> current | SHUTDOWN_MASK); private static final LongUnaryOperator decrementActive = current -> { long decrementedActiveCount = activeCount(current) - 1; return decrementedActiveCount | (current & ~ACTIVE_COUNT_MASK); }; private final GracefulShutdownListener listener = new GracefulShutdownListener(); private final List shutdownListeners = new ArrayList<>(); private final Object lock = new Object(); private volatile long state = 0; private static final AtomicLongFieldUpdater stateUpdater = AtomicLongFieldUpdater.newUpdater(GracefulShutdownHandler.class, "state"); private final HttpHandler next; public GracefulShutdownHandler(HttpHandler next) { this.next = next; } private static boolean isShutdown(long state) { return (state & SHUTDOWN_MASK) != 0; } private static long activeCount(long state) { return state & ACTIVE_COUNT_MASK; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { long snapshot = stateUpdater.updateAndGet(this, incrementActive); if (isShutdown(snapshot)) { decrementRequests(); exchange.setStatusCode(StatusCodes.SERVICE_UNAVAILABLE); exchange.endExchange(); return; } exchange.addExchangeCompleteListener(listener); next.handleRequest(exchange); } public void shutdown() { //the request count is never zero when shutdown is set to true stateUpdater.updateAndGet(this, incrementActiveAndShutdown); decrementRequests(); } public void start() { synchronized (lock) { stateUpdater.updateAndGet(this, current -> current & ACTIVE_COUNT_MASK); for (ShutdownListener listener : shutdownListeners) { listener.shutdown(false); } shutdownListeners.clear(); } } private void shutdownComplete() { synchronized (lock) { lock.notifyAll(); for (ShutdownListener listener : shutdownListeners) { listener.shutdown(true); } shutdownListeners.clear(); } } /** * Waits for the handler to shutdown. */ public void awaitShutdown() throws InterruptedException { synchronized (lock) { if (!isShutdown(stateUpdater.get(this))) { throw UndertowMessages.MESSAGES.handlerNotShutdown(); } while (activeCount(stateUpdater.get(this)) > 0) { lock.wait(); } } } /** * Waits a set length of time for the handler to shut down * * @param millis The length of time * @return true If the handler successfully shut down */ public boolean awaitShutdown(long millis) throws InterruptedException { synchronized (lock) { if (!isShutdown(stateUpdater.get(this))) { throw UndertowMessages.MESSAGES.handlerNotShutdown(); } long end = System.currentTimeMillis() + millis; while (activeCount(stateUpdater.get(this)) != 0) { long left = end - System.currentTimeMillis(); if (left <= 0) { return false; } lock.wait(left); } return true; } } /** * Adds a shutdown listener that will be invoked when all requests have finished. If all requests have already been finished * the listener will be invoked immediately. * * @param shutdownListener The shutdown listener */ public void addShutdownListener(final ShutdownListener shutdownListener) { synchronized (lock) { if (!isShutdown(stateUpdater.get(this))) { throw UndertowMessages.MESSAGES.handlerNotShutdown(); } long count = activeCount(stateUpdater.get(this)); if (count == 0) { shutdownListener.shutdown(true); } else { shutdownListeners.add(shutdownListener); } } } private void decrementRequests() { long snapshot = stateUpdater.updateAndGet(this, decrementActive); // Shutdown has completed when the activeCount portion is zero, and shutdown is set. if (snapshot == SHUTDOWN_MASK) { shutdownComplete(); } } private final class GracefulShutdownListener implements ExchangeCompletionListener { @Override public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { try { decrementRequests(); } finally { nextListener.proceed(); } } } /** * A listener which can be registered with the handler to be notified when all pending requests have finished. */ public interface ShutdownListener { /** * Notification that the container has shutdown. * * @param shutdownSuccessful If the shutdown succeeded or not */ void shutdown(boolean shutdownSuccessful); } } HttpContinueAcceptingHandler.java000066400000000000000000000100411420065311100337620ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.IOException; import java.util.Collections; import java.util.Map; import java.util.Set; import io.undertow.UndertowLogger; import io.undertow.io.IoCallback; import io.undertow.io.Sender; import io.undertow.predicate.Predicate; import io.undertow.predicate.Predicates; import io.undertow.server.HandlerWrapper; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.server.protocol.http.HttpContinue; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; /** * Handler that provides support for HTTP/1.1 continue responses. *

* If the provided predicate returns true then the request will be * accepted, otherwise it will be rejected. * * If no predicate is supplied then all requests will be accepted. * * @see io.undertow.server.protocol.http.HttpContinue * @author Stuart Douglas */ public class HttpContinueAcceptingHandler implements HttpHandler { private final HttpHandler next; private final Predicate accept; public HttpContinueAcceptingHandler(HttpHandler next, Predicate accept) { this.next = next; this.accept = accept; } public HttpContinueAcceptingHandler(HttpHandler next) { this(next, Predicates.truePredicate()); } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { if(HttpContinue.requiresContinueResponse(exchange)) { if(accept.resolve(exchange)) { HttpContinue.sendContinueResponse(exchange, new IoCallback() { @Override public void onComplete(final HttpServerExchange exchange, final Sender sender) { exchange.dispatch(next); } @Override public void onException(final HttpServerExchange exchange, final Sender sender, final IOException exception) { UndertowLogger.REQUEST_IO_LOGGER.ioException(exception); exchange.endExchange(); } }); } else { HttpContinue.rejectExchange(exchange); } } else { next.handleRequest(exchange); } } public static final class Wrapper implements HandlerWrapper { private final Predicate predicate; public Wrapper(Predicate predicate) { this.predicate = predicate; } @Override public HttpHandler wrap(HttpHandler handler) { return new HttpContinueAcceptingHandler(handler, predicate); } } @Override public String toString() { return "http-continue-accept()"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "http-continue-accept"; } @Override public Map> parameters() { return Collections.emptyMap(); } @Override public Set requiredParameters() { return null; } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { return new Wrapper(Predicates.truePredicate()); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/HttpContinueReadHandler.java000066400000000000000000000201351420065311100330240ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.concurrent.TimeUnit; import org.xnio.channels.StreamSinkChannel; import org.xnio.conduits.AbstractStreamSourceConduit; import org.xnio.conduits.StreamSourceConduit; import io.undertow.server.ConduitWrapper; import io.undertow.server.Connectors; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.ResponseCommitListener; import io.undertow.server.protocol.http.HttpContinue; import io.undertow.util.ConduitFactory; import io.undertow.util.StatusCodes; /** * Handler for requests that require 100-continue responses. If an attempt is made to read from the source * channel then a 100 continue response is sent. * * @author Stuart Douglas */ public class HttpContinueReadHandler implements HttpHandler { private static final ConduitWrapper WRAPPER = new ConduitWrapper() { @Override public StreamSourceConduit wrap(final ConduitFactory factory, final HttpServerExchange exchange) { if (exchange.isRequestChannelAvailable() && !exchange.isResponseStarted()) { return new ContinueConduit(factory.create(), exchange); } return factory.create(); } }; private final HttpHandler handler; public HttpContinueReadHandler(final HttpHandler handler) { this.handler = handler; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { if (HttpContinue.requiresContinueResponse(exchange)) { exchange.addRequestWrapper(WRAPPER); exchange.addResponseCommitListener(ContinueResponseCommitListener.INSTANCE); } handler.handleRequest(exchange); } private enum ContinueResponseCommitListener implements ResponseCommitListener { INSTANCE; @Override public void beforeCommit(HttpServerExchange exchange) { //we are writing the response, and have not read the request then we mark this as non-persistent if (!HttpContinue.isContinueResponseSent(exchange)) { exchange.setPersistent(false); //we also kill the request channel, because it is unusable now if (!exchange.isRequestComplete()) { exchange.getConnection().terminateRequestChannel(exchange); } else { Connectors.terminateRequest(exchange); } } } } private static final class ContinueConduit extends AbstractStreamSourceConduit implements StreamSourceConduit { private boolean sent = false; private HttpContinue.ContinueResponseSender response = null; private final HttpServerExchange exchange; protected ContinueConduit(final StreamSourceConduit next, final HttpServerExchange exchange) { super(next); this.exchange = exchange; } @Override public long transferTo(final long position, final long count, final FileChannel target) throws IOException { if (exchange.getStatusCode() == StatusCodes.EXPECTATION_FAILED) { //rejected Connectors.terminateRequest(exchange); return -1; } if (!sent) { sent = true; response = HttpContinue.createResponseSender(exchange); } if (response != null) { if (!response.send()) { return 0; } response = null; } return super.transferTo(position, count, target); } @Override public long transferTo(final long count, final ByteBuffer throughBuffer, final StreamSinkChannel target) throws IOException { if (exchange.getStatusCode() == StatusCodes.EXPECTATION_FAILED) { //rejected Connectors.terminateRequest(exchange); return -1; } if (!sent) { sent = true; response = HttpContinue.createResponseSender(exchange); } if (response != null) { if (!response.send()) { return 0; } response = null; } return super.transferTo(count, throughBuffer, target); } @Override public int read(final ByteBuffer dst) throws IOException { if (exchange.getStatusCode() == StatusCodes.EXPECTATION_FAILED) { //rejected Connectors.terminateRequest(exchange); return -1; } if (!sent) { sent = true; response = HttpContinue.createResponseSender(exchange); } if (response != null) { if (!response.send()) { return 0; } response = null; } return super.read(dst); } @Override public long read(final ByteBuffer[] dsts, final int offs, final int len) throws IOException { if (exchange.getStatusCode() == StatusCodes.EXPECTATION_FAILED) { //rejected Connectors.terminateRequest(exchange); return -1; } if (!sent) { sent = true; response = HttpContinue.createResponseSender(exchange); } if (response != null) { if (!response.send()) { return 0; } response = null; } return super.read(dsts, offs, len); } @Override public void awaitReadable(final long time, final TimeUnit timeUnit) throws IOException { if (exchange.getStatusCode() == StatusCodes.EXPECTATION_FAILED) { //rejected return; } if (!sent) { sent = true; response = HttpContinue.createResponseSender(exchange); } long exitTime = System.currentTimeMillis() + timeUnit.toMillis(time); if (response != null) { while (!response.send()) { long currentTime = System.currentTimeMillis(); if (currentTime > exitTime) { return; } response.awaitWritable(exitTime - currentTime, TimeUnit.MILLISECONDS); } response = null; } long currentTime = System.currentTimeMillis(); super.awaitReadable(exitTime - currentTime, TimeUnit.MILLISECONDS); } @Override public void awaitReadable() throws IOException { if (exchange.getStatusCode() == StatusCodes.EXPECTATION_FAILED) { //rejected return; } if (!sent) { sent = true; response = HttpContinue.createResponseSender(exchange); } if (response != null) { while (!response.send()) { response.awaitWritable(); } response = null; } super.awaitReadable(); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/HttpTraceHandler.java000066400000000000000000000065631420065311100315130ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.util.Collections; import java.util.Map; import java.util.Set; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.HeaderValues; import io.undertow.util.Headers; import io.undertow.util.Methods; /** * A handler that handles HTTP trace requests * * @author Stuart Douglas */ public class HttpTraceHandler implements HttpHandler { private final HttpHandler handler; public HttpTraceHandler(final HttpHandler handler) { this.handler = handler; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { if(exchange.getRequestMethod().equals(Methods.TRACE)) { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "message/http"); StringBuilder body = new StringBuilder("TRACE "); body.append(exchange.getRequestURI()); if(!exchange.getQueryString().isEmpty()) { body.append('?'); body.append(exchange.getQueryString()); } body.append(' '); body.append(exchange.getProtocol().toString()); body.append("\r\n"); for(HeaderValues header : exchange.getRequestHeaders()) { for(String value : header) { body.append(header.getHeaderName()); body.append(": "); body.append(value); body.append("\r\n"); } } body.append("\r\n"); exchange.getResponseSender().send(body.toString()); } else { handler.handleRequest(exchange); } } @Override public String toString() { return "trace()"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "trace"; } @Override public Map> parameters() { return Collections.emptyMap(); } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { return new Wrapper(); } } private static class Wrapper implements HandlerWrapper { @Override public HttpHandler wrap(HttpHandler handler) { return new HttpTraceHandler(handler); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/HttpUpgradeHandshake.java000066400000000000000000000032751420065311100323520ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.IOException; import io.undertow.server.HttpServerExchange; /** * Server side upgrade handler. This handler can inspect the request and modify the response. *

* If the request does not meet this handlers requirements it should return false to allow * other upgrade handlers to inspect the request. *

* If the request is invalid (e.g. security information is invalid) this should thrown an IoException. * if this occurs no further handlers will be tried. * * @author Stuart Douglas */ public interface HttpUpgradeHandshake { /** * Validates an upgrade request and returns any extra headers that should be added to the response. * * @param exchange the server exchange * @return true if the handshake is valid and should be upgraded. False if it is invalid * @throws IOException If the handshake is invalid */ boolean handleUpgrade(final HttpServerExchange exchange) throws IOException; } IPAddressAccessControlHandler.java000066400000000000000000000436411420065311100340350ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.UndertowLogger; import java.io.IOException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.regex.Pattern; import io.undertow.UndertowMessages; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.NetworkUtils; import io.undertow.util.StatusCodes; import java.util.stream.Collectors; import org.xnio.Bits; /** * Handler that can accept or reject a request based on the IP address of the remote peer. * * @author Stuart Douglas */ public class IPAddressAccessControlHandler implements HttpHandler { /** * Standard IP address */ private static final Pattern IP4_EXACT = Pattern.compile(NetworkUtils.IP4_EXACT); /** * Standard IP address, with some octets replaced by a '*' */ private static final Pattern IP4_WILDCARD = Pattern.compile("(?:(?:\\d{1,3}|\\*)\\.){3}(?:\\d{1,3}|\\*)"); /** * IPv4 address with subnet specified via slash notation */ private static final Pattern IP4_SLASH = Pattern.compile("(?:\\d{1,3}\\.){3}\\d{1,3}\\/\\d\\d?"); /** * Standard full IPv6 address */ private static final Pattern IP6_EXACT = Pattern.compile(NetworkUtils.IP6_EXACT); /** * Standard full IPv6 address, with some parts replaced by a '*' */ private static final Pattern IP6_WILDCARD = Pattern.compile("(?:(?:[a-zA-Z0-9]{1,4}|\\*):){7}(?:[a-zA-Z0-9]{1,4}|\\*)"); /** * Standard full IPv6 address with subnet specified via slash notation */ private static final Pattern IP6_SLASH = Pattern.compile("(?:[a-zA-Z0-9]{1,4}:){7}[a-zA-Z0-9]{1,4}\\/\\d{1,3}"); private volatile HttpHandler next; private volatile boolean defaultAllow = false; private final int denyResponseCode; private final List ipv6acl = new CopyOnWriteArrayList<>(); private final List ipv4acl = new CopyOnWriteArrayList<>(); private static final boolean traceEnabled; private static final boolean debugEnabled; static { traceEnabled = UndertowLogger.PREDICATE_LOGGER.isTraceEnabled(); debugEnabled = UndertowLogger.PREDICATE_LOGGER.isDebugEnabled(); } public IPAddressAccessControlHandler(final HttpHandler next) { this(next, StatusCodes.FORBIDDEN); } public IPAddressAccessControlHandler(final HttpHandler next, final int denyResponseCode) { this.next = next; this.denyResponseCode = denyResponseCode; } public IPAddressAccessControlHandler() { this.next = ResponseCodeHandler.HANDLE_404; this.denyResponseCode = StatusCodes.FORBIDDEN; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { InetSocketAddress peer = exchange.getSourceAddress(); if (isAllowed(peer.getAddress())) { next.handleRequest(exchange); } else { if (debugEnabled) { UndertowLogger.PREDICATE_LOGGER.debugf("Access to [%s] blocked from %s.", exchange, peer.getHostString()); } exchange.setStatusCode(denyResponseCode); exchange.endExchange(); } } boolean isAllowed(InetAddress address) { if (address instanceof Inet4Address) { for (PeerMatch rule : ipv4acl) { if (traceEnabled) { UndertowLogger.PREDICATE_LOGGER.tracef("Comparing rule [%s] to IPv4 address %s.", rule.toPredicateString(), address.getHostAddress()); } if (rule.matches(address)) { return !rule.isDeny(); } } } else if (address instanceof Inet6Address) { for (PeerMatch rule : ipv6acl) { if (traceEnabled) { UndertowLogger.PREDICATE_LOGGER.tracef("Comparing rule [%s] to IPv6 address %s.", rule.toPredicateString(), address.getHostAddress()); } if (rule.matches(address)) { return !rule.isDeny(); } } } return defaultAllow; } public int getDenyResponseCode() { return denyResponseCode; } public boolean isDefaultAllow() { return defaultAllow; } public IPAddressAccessControlHandler setDefaultAllow(final boolean defaultAllow) { this.defaultAllow = defaultAllow; return this; } public HttpHandler getNext() { return next; } public IPAddressAccessControlHandler setNext(final HttpHandler next) { this.next = next; return this; } /** * Adds an allowed peer to the ACL list *

* Peer can take several forms: *

* a.b.c.d = Literal IPv4 Address * a:b:c:d:e:f:g:h = Literal IPv6 Address * a.b.* = Wildcard IPv4 Address * a:b:* = Wildcard IPv6 Address * a.b.c.0/24 = Classless wildcard IPv4 address * a:b:c:d:e:f:g:0/120 = Classless wildcard IPv6 address * * @param peer The peer to add to the ACL */ public IPAddressAccessControlHandler addAllow(final String peer) { return addRule(peer, false); } /** * Adds an denied peer to the ACL list *

* Peer can take several forms: *

* a.b.c.d = Literal IPv4 Address * a:b:c:d:e:f:g:h = Literal IPv6 Address * a.b.* = Wildcard IPv4 Address * a:b:* = Wildcard IPv6 Address * a.b.c.0/24 = Classless wildcard IPv4 address * a:b:c:d:e:f:g:0/120 = Classless wildcard IPv6 address * * @param peer The peer to add to the ACL */ public IPAddressAccessControlHandler addDeny(final String peer) { return addRule(peer, true); } public IPAddressAccessControlHandler clearRules() { this.ipv4acl.clear(); this.ipv6acl.clear(); return this; } private IPAddressAccessControlHandler addRule(final String peer, final boolean deny) { if (IP4_EXACT.matcher(peer).matches()) { addIpV4ExactMatch(peer, deny); } else if (IP4_WILDCARD.matcher(peer).matches()) { addIpV4WildcardMatch(peer, deny); } else if (IP4_SLASH.matcher(peer).matches()) { addIpV4SlashPrefix(peer, deny); } else if (IP6_EXACT.matcher(peer).matches()) { addIpV6ExactMatch(peer, deny); } else if (IP6_WILDCARD.matcher(peer).matches()) { addIpV6WildcardMatch(peer, deny); } else if (IP6_SLASH.matcher(peer).matches()) { addIpV6SlashPrefix(peer, deny); } else { throw UndertowMessages.MESSAGES.notAValidIpPattern(peer); } return this; } private void addIpV6SlashPrefix(final String peer, final boolean deny) { String[] components = peer.split("\\/"); String[] parts = components[0].split("\\:"); int maskLen = Integer.parseInt(components[1]); assert parts.length == 8; byte[] pattern = new byte[16]; byte[] mask = new byte[16]; for (int i = 0; i < 8; ++i) { int val = Integer.parseInt(parts[i], 16); pattern[i * 2] = (byte) (val >> 8); pattern[i * 2 + 1] = (byte) (val & 0xFF); } for (int i = 0; i < 16; ++i) { if (maskLen > 8) { mask[i] = (byte) (0xFF); maskLen -= 8; } else if (maskLen != 0) { mask[i] = (byte) (Bits.intBitMask(8 - maskLen, 7) & 0xFF); maskLen = 0; } else { break; } } ipv6acl.add(new PrefixIpV6PeerMatch(deny, peer, mask, pattern)); } private void addIpV4SlashPrefix(final String peer, final boolean deny) { String[] components = peer.split("\\/"); String[] parts = components[0].split("\\."); int maskLen = Integer.parseInt(components[1]); final int mask = Bits.intBitMask(32 - maskLen, 31); int prefix = 0; for (int i = 0; i < 4; ++i) { prefix <<= 8; String part = parts[i]; int no = Integer.parseInt(part); prefix |= no; } ipv4acl.add(new PrefixIpV4PeerMatch(deny, peer, mask, prefix)); } private void addIpV6WildcardMatch(final String peer, final boolean deny) { byte[] pattern = new byte[16]; byte[] mask = new byte[16]; String[] parts = peer.split("\\:"); assert parts.length == 8; for (int i = 0; i < 8; ++i) { if (!parts[i].equals("*")) { int val = Integer.parseInt(parts[i], 16); pattern[i * 2] = (byte) (val >> 8); pattern[i * 2 + 1] = (byte) (val & 0xFF); mask[i * 2] = (byte) (0xFF); mask[i * 2 + 1] = (byte) (0xFF); } } ipv6acl.add(new PrefixIpV6PeerMatch(deny, peer, mask, pattern)); } private void addIpV4WildcardMatch(final String peer, final boolean deny) { String[] parts = peer.split("\\."); int mask = 0; int prefix = 0; for (int i = 0; i < 4; ++i) { mask <<= 8; prefix <<= 8; String part = parts[i]; if (!part.equals("*")) { int no = Integer.parseInt(part); mask |= 0xFF; prefix |= no; } } ipv4acl.add(new PrefixIpV4PeerMatch(deny, peer, mask, prefix)); } private void addIpV6ExactMatch(final String peer, final boolean deny) { byte[] bytes; try { bytes = NetworkUtils.parseIpv6AddressToBytes(peer); ipv6acl.add(new ExactIpV6PeerMatch(deny, peer, bytes)); } catch (IOException e) { throw UndertowMessages.MESSAGES.invalidACLAddress(e); } } private void addIpV4ExactMatch(final String peer, final boolean deny) { String[] parts = peer.split("\\."); byte[] bytes = {(byte) Integer.parseInt(parts[0]), (byte) Integer.parseInt(parts[1]), (byte) Integer.parseInt(parts[2]), (byte) Integer.parseInt(parts[3])}; ipv4acl.add(new ExactIpV4PeerMatch(deny, peer, bytes)); } @Override public String toString() { //ip-access-control( default-allow=false, acl={'127.0.0.* allow', '192.168.1.123 deny'}, failure-status=404 ) String predicate = "ip-access-control( default-allow=" + defaultAllow + ", acl={ "; List acl = new ArrayList<>(); acl.addAll(ipv4acl); acl.addAll(ipv6acl); predicate += acl.stream().map(s -> "'" + s.toPredicateString() + "'").collect(Collectors.joining(", ")); predicate += " }"; if (denyResponseCode != StatusCodes.FORBIDDEN) { predicate += ", failure-status=" + denyResponseCode; } predicate += " )"; return predicate; } abstract static class PeerMatch { private final boolean deny; private final String pattern; protected PeerMatch(final boolean deny, final String pattern) { this.deny = deny; this.pattern = pattern; } abstract boolean matches(final InetAddress address); boolean isDeny() { return deny; } @Override public String toString() { return getClass().getSimpleName() + "{" + "deny=" + deny + ", pattern='" + pattern + '\'' + '}'; } public String toPredicateString() { return pattern + " " + (deny ? "deny" : "allow"); } } static class ExactIpV4PeerMatch extends PeerMatch { private final byte[] address; protected ExactIpV4PeerMatch(final boolean deny, final String pattern, final byte[] address) { super(deny, pattern); this.address = address; } @Override boolean matches(final InetAddress address) { return Arrays.equals(address.getAddress(), this.address); } } static class ExactIpV6PeerMatch extends PeerMatch { private final byte[] address; protected ExactIpV6PeerMatch(final boolean deny, final String pattern, final byte[] address) { super(deny, pattern); this.address = address; } @Override boolean matches(final InetAddress address) { return Arrays.equals(address.getAddress(), this.address); } } private static class PrefixIpV4PeerMatch extends PeerMatch { private final int mask; private final int prefix; protected PrefixIpV4PeerMatch(final boolean deny, final String pattern, final int mask, final int prefix) { super(deny, pattern); this.mask = mask; this.prefix = prefix; } @Override boolean matches(final InetAddress address) { byte[] bytes = address.getAddress(); if (bytes == null) { return false; } int addressInt = ((bytes[0] & 0xFF) << 24) | ((bytes[1] & 0xFF) << 16) | ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF); return (addressInt & mask) == prefix; } } static class PrefixIpV6PeerMatch extends PeerMatch { private final byte[] mask; private final byte[] prefix; protected PrefixIpV6PeerMatch(final boolean deny, final String pattern, final byte[] mask, final byte[] prefix) { super(deny, pattern); this.mask = mask; this.prefix = prefix; assert mask.length == prefix.length; } @Override boolean matches(final InetAddress address) { byte[] bytes = address.getAddress(); if (bytes == null) { return false; } if (bytes.length != mask.length) { return false; } for (int i = 0; i < mask.length; ++i) { if ((bytes[i] & mask[i]) != prefix[i]) { return false; } } return true; } } public static class Builder implements HandlerBuilder { @Override public String name() { return "ip-access-control"; } @Override public Map> parameters() { Map> params = new HashMap<>(); params.put("acl", String[].class); params.put("failure-status", int.class); params.put("default-allow", boolean.class); return params; } @Override public Set requiredParameters() { return Collections.singleton("acl"); } @Override public String defaultParameter() { return "acl"; } @Override public HandlerWrapper build(Map config) { String[] acl = (String[]) config.get("acl"); Boolean defaultAllow = (Boolean) config.get("default-allow"); Integer failureStatus = (Integer) config.get("failure-status"); List peerMatches = new ArrayList<>(); for (String rule : acl) { String[] parts = rule.split(" "); if (parts.length != 2) { throw UndertowMessages.MESSAGES.invalidAclRule(rule); } if (parts[1].trim().equals("allow")) { peerMatches.add(new Holder(parts[0].trim(), false)); } else if (parts[1].trim().equals("deny")) { peerMatches.add(new Holder(parts[0].trim(), true)); } else { throw UndertowMessages.MESSAGES.invalidAclRule(rule); } } return new Wrapper(peerMatches, defaultAllow == null ? false : defaultAllow, failureStatus == null ? StatusCodes.FORBIDDEN : failureStatus); } } private static class Wrapper implements HandlerWrapper { private final List peerMatches; private final boolean defaultAllow; private final int failureStatus; private Wrapper(List peerMatches, boolean defaultAllow, int failureStatus) { this.peerMatches = peerMatches; this.defaultAllow = defaultAllow; this.failureStatus = failureStatus; } @Override public HttpHandler wrap(HttpHandler handler) { IPAddressAccessControlHandler res = new IPAddressAccessControlHandler(handler, failureStatus); for (Holder match : peerMatches) { if (match.deny) { res.addDeny(match.rule); } else { res.addAllow(match.rule); } } res.setDefaultAllow(defaultAllow); return res; } } private static class Holder { final String rule; final boolean deny; private Holder(String rule, boolean deny) { this.rule = rule; this.deny = deny; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/JDBCLogHandler.java000066400000000000000000000437431420065311100307620ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.security.api.SecurityContext; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.Headers; import java.net.InetSocketAddress; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.sql.DataSource; public class JDBCLogHandler implements HttpHandler, Runnable { private final HttpHandler next; private final String formatString; private final ExchangeCompletionListener exchangeCompletionListener = new JDBCLogCompletionListener(); private final Deque pendingMessages; //0 = not running //1 = queued //2 = running @SuppressWarnings("unused") private volatile int state = 0; @SuppressWarnings("unused") private volatile Executor executor; private static final AtomicIntegerFieldUpdater stateUpdater = AtomicIntegerFieldUpdater.newUpdater(JDBCLogHandler.class, "state"); protected boolean useLongContentLength = false; private final DataSource dataSource; private String tableName; private String remoteHostField; private String userField; private String timestampField; private String virtualHostField; private String methodField; private String queryField; private String statusField; private String bytesField; private String refererField; private String userAgentField; @Deprecated public JDBCLogHandler(final HttpHandler next, final Executor logWriteExecutor, final String formatString, DataSource dataSource) { this(next, formatString, dataSource); } public JDBCLogHandler(final HttpHandler next, final String formatString, DataSource dataSource) { this.next = next; this.formatString = formatString; this.dataSource = dataSource; tableName = "access"; remoteHostField = "remoteHost"; userField = "userName"; timestampField = "timestamp"; virtualHostField = "virtualHost"; methodField = "method"; queryField = "query"; statusField = "status"; bytesField = "bytes"; refererField = "referer"; userAgentField = "userAgent"; this.pendingMessages = new ConcurrentLinkedDeque<>(); } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.addExchangeCompleteListener(exchangeCompletionListener); next.handleRequest(exchange); } private class JDBCLogCompletionListener implements ExchangeCompletionListener { @Override public void exchangeEvent(final HttpServerExchange exchange, final NextListener nextListener) { try { logMessage(formatString, exchange); } finally { nextListener.proceed(); } } } public void logMessage(String pattern, HttpServerExchange exchange) { JDBCLogAttribute jdbcLogAttribute = new JDBCLogAttribute(); if (pattern.equals("combined")) { jdbcLogAttribute.pattern = pattern; } jdbcLogAttribute.remoteHost = ((InetSocketAddress) exchange.getConnection().getPeerAddress()).getAddress().getHostAddress(); SecurityContext sc = exchange.getSecurityContext(); if (sc == null || !sc.isAuthenticated()) { jdbcLogAttribute.user = null; } else { jdbcLogAttribute.user = sc.getAuthenticatedAccount().getPrincipal().getName(); } jdbcLogAttribute.query = exchange.getQueryString(); jdbcLogAttribute.bytes = exchange.getResponseContentLength(); if (jdbcLogAttribute.bytes < 0) { jdbcLogAttribute.bytes = 0; } jdbcLogAttribute.status = exchange.getStatusCode(); if (jdbcLogAttribute.pattern.equals("combined")) { jdbcLogAttribute.virtualHost = exchange.getRequestHeaders().getFirst(Headers.HOST); jdbcLogAttribute.method = exchange.getRequestMethod().toString(); jdbcLogAttribute.referer = exchange.getRequestHeaders().getFirst(Headers.REFERER); jdbcLogAttribute.userAgent = exchange.getRequestHeaders().getFirst(Headers.USER_AGENT); } this.pendingMessages.add(jdbcLogAttribute); int state = stateUpdater.get(this); if (state == 0) { if (stateUpdater.compareAndSet(this, 0, 1)) { this.executor = exchange.getConnection().getWorker(); this.executor.execute(this); } } } /** * insert the log record to database */ @Override public void run() { if (!stateUpdater.compareAndSet(this, 1, 2)) { return; } List messages = new ArrayList<>(); JDBCLogAttribute msg = null; //only grab at most 1000 messages at a time for (int i = 0; i < 1000; ++i) { msg = pendingMessages.poll(); if (msg == null) { break; } messages.add(msg); } try { if (!messages.isEmpty()) { writeMessage(messages); } } finally { Executor executor = this.executor; stateUpdater.set(this, 0); //check to see if there is still more messages //if so then run this again if (!pendingMessages.isEmpty()) { if (stateUpdater.compareAndSet(this, 0, 1)) { executor.execute(this); } } } } private void writeMessage(List messages) { PreparedStatement ps = null; Connection conn = null; try { conn = dataSource.getConnection(); conn.setAutoCommit(true); ps = prepareStatement(conn); for (JDBCLogAttribute jdbcLogAttribute : messages) { int numberOfTries = 2; while (numberOfTries > 0) { try { ps.clearParameters(); ps.setString(1, jdbcLogAttribute.remoteHost); ps.setString(2, jdbcLogAttribute.user); ps.setTimestamp(3, jdbcLogAttribute.timestamp); ps.setString(4, jdbcLogAttribute.query); ps.setInt(5, jdbcLogAttribute.status); if (useLongContentLength) { ps.setLong(6, jdbcLogAttribute.bytes); } else { if (jdbcLogAttribute.bytes > Integer.MAX_VALUE) { jdbcLogAttribute.bytes = -1; } ps.setInt(6, (int) jdbcLogAttribute.bytes); } ps.setString(7, jdbcLogAttribute.virtualHost); ps.setString(8, jdbcLogAttribute.method); ps.setString(9, jdbcLogAttribute.referer); ps.setString(10, jdbcLogAttribute.userAgent); ps.executeUpdate(); numberOfTries = 0; } catch (SQLException e) { UndertowLogger.ROOT_LOGGER.failedToWriteJdbcAccessLog(e); } numberOfTries--; } } ps.close(); } catch (SQLException e) { UndertowLogger.ROOT_LOGGER.errorWritingJDBCLog(e); } finally { if (ps != null) { try { ps.close(); } catch (SQLException e) { UndertowLogger.ROOT_LOGGER.debug("Exception closing prepared statement", e); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { UndertowLogger.ROOT_LOGGER.debug("Exception closing connection", e); } } } } /** * For tests only. Blocks the current thread until all messages are written Just does a busy wait. *

* DO NOT USE THIS OUTSIDE OF A TEST */ void awaitWrittenForTest() throws InterruptedException { while (!pendingMessages.isEmpty()) { Thread.sleep(10); } while (state != 0) { Thread.sleep(10); } } private PreparedStatement prepareStatement(Connection conn) throws SQLException { return conn.prepareStatement("INSERT INTO " + tableName + " (" + remoteHostField + ", " + userField + ", " + timestampField + ", " + queryField + ", " + statusField + ", " + bytesField + ", " + virtualHostField + ", " + methodField + ", " + refererField + ", " + userAgentField + ") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); } private static class JDBCLogAttribute { protected String remoteHost = ""; protected String user = ""; protected String query = ""; protected long bytes = 0; protected int status = 0; protected String virtualHost = ""; protected String method = ""; protected String referer = ""; protected String userAgent = ""; protected String pattern = "common"; protected Timestamp timestamp = new Timestamp(System.currentTimeMillis()); } public boolean isUseLongContentLength() { return useLongContentLength; } public void setUseLongContentLength(boolean useLongContentLength) { this.useLongContentLength = useLongContentLength; } public String getTableName() { return tableName; } public void setTableName(String tableName) { this.tableName = tableName; } public String getRemoteHostField() { return remoteHostField; } public void setRemoteHostField(String remoteHostField) { this.remoteHostField = remoteHostField; } public String getUserField() { return userField; } public void setUserField(String userField) { this.userField = userField; } public String getTimestampField() { return timestampField; } public void setTimestampField(String timestampField) { this.timestampField = timestampField; } public String getVirtualHostField() { return virtualHostField; } public void setVirtualHostField(String virtualHostField) { this.virtualHostField = virtualHostField; } public String getMethodField() { return methodField; } public void setMethodField(String methodField) { this.methodField = methodField; } public String getQueryField() { return queryField; } public void setQueryField(String queryField) { this.queryField = queryField; } public String getStatusField() { return statusField; } public void setStatusField(String statusField) { this.statusField = statusField; } public String getBytesField() { return bytesField; } public void setBytesField(String bytesField) { this.bytesField = bytesField; } public String getRefererField() { return refererField; } public void setRefererField(String refererField) { this.refererField = refererField; } public String getUserAgentField() { return userAgentField; } public void setUserAgentField(String userAgentField) { this.userAgentField = userAgentField; } @Override public String toString() { return "JDBCLogHandler{" + "formatString='" + formatString + '\'' + '}'; } public static class Builder implements HandlerBuilder { @Override public String name() { return "jdbc-access-log"; } @Override public Map> parameters() { Map> params = new HashMap<>(); params.put("format", String.class); params.put("datasource", String.class); params.put("tableName", String.class); params.put("remoteHostField", String.class); params.put("userField", String.class); params.put("timestampField", String.class); params.put("virtualHostField", String.class); params.put("methodField", String.class); params.put("queryField", String.class); params.put("statusField", String.class); params.put("bytesField", String.class); params.put("refererField", String.class); params.put("userAgentField", String.class); return params; } @Override public Set requiredParameters() { return Collections.singleton("datasource"); } @Override public String defaultParameter() { return "datasource"; } @Override public HandlerWrapper build(Map config) { String datasourceName = (String) config.get("datasource"); try { DataSource ds = (DataSource) new InitialContext().lookup((String) config.get("datasource")); String format = (String) config.get("format"); return new Wrapper(format, ds, (String)config.get("tableName"), (String)config.get("remoteHostField"), (String)config.get("userField"), (String)config.get("timestampField"), (String)config.get("virtualHostField"), (String)config.get("methodField"), (String)config.get("queryField"), (String)config.get("statusField"), (String)config.get("bytesField"), (String)config.get("refererField"), (String)config.get("userAgentField")); } catch (NamingException ex) { throw UndertowMessages.MESSAGES.datasourceNotFound(datasourceName); } } } private static class Wrapper implements HandlerWrapper { private final DataSource datasource; private final String format; private final String tableName; private final String remoteHostField; private final String userField; private final String timestampField; private final String virtualHostField; private final String methodField; private final String queryField; private final String statusField; private final String bytesField; private final String refererField; private final String userAgentField; private Wrapper(String format, DataSource datasource, String tableName, String remoteHostField, String userField, String timestampField, String virtualHostField, String methodField, String queryField, String statusField, String bytesField, String refererField, String userAgentField) { this.datasource = datasource; this.tableName = tableName; this.remoteHostField = remoteHostField; this.userField = userField; this.timestampField = timestampField; this.virtualHostField = virtualHostField; this.methodField = methodField; this.queryField = queryField; this.statusField = statusField; this.bytesField = bytesField; this.refererField = refererField; this.userAgentField = userAgentField; this.format = "combined".equals(format) ? "combined" : "common"; } @Override public HttpHandler wrap(HttpHandler handler) { JDBCLogHandler jdbc = new JDBCLogHandler(handler, format, datasource); if(tableName != null) { jdbc.setTableName(tableName); } if(remoteHostField != null) { jdbc.setRemoteHostField(remoteHostField); } if(userField != null) { jdbc.setUserField(userField); } if(timestampField != null) { jdbc.setTimestampField(timestampField); } if(virtualHostField != null) { jdbc.setVirtualHostField(virtualHostField); } if(methodField != null) { jdbc.setMethodField(methodField); } if(queryField != null) { jdbc.setQueryField(queryField); } if(statusField != null) { jdbc.setStatusField(statusField); } if(bytesField != null) { jdbc.setBytesField(bytesField); } if(refererField != null) { jdbc.setRefererField(refererField); } if(userAgentField != null) { jdbc.setUserAgentField(userAgentField); } return jdbc; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/LearningPushHandler.java000066400000000000000000000226031420065311100322050ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.server.handlers.cache.LRUCache; import io.undertow.server.session.Session; import io.undertow.server.session.SessionConfig; import io.undertow.server.session.SessionManager; import io.undertow.util.DateUtils; import io.undertow.util.HeaderMap; import io.undertow.util.Headers; import io.undertow.util.Methods; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * Handler that builds up a cache of resources that a browsers requests, and uses * server push to push them when supported. * * @author Stuart Douglas */ public class LearningPushHandler implements HttpHandler { private static final String SESSION_ATTRIBUTE = "io.undertow.PUSHED_RESOURCES"; private static final int DEFAULT_MAX_CACHE_ENTRIES = 1000; private static final int DEFAULT_MAX_CACHE_AGE = -1; private final LRUCache> cache; private final HttpHandler next; public LearningPushHandler(final HttpHandler next) { this(DEFAULT_MAX_CACHE_ENTRIES, DEFAULT_MAX_CACHE_AGE, next); } public LearningPushHandler(int maxEntries, int maxAge, HttpHandler next) { this.next = next; cache = new LRUCache<>(maxEntries, maxAge); } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { String fullPath; String requestPath; if(exchange.getQueryString().isEmpty()) { fullPath = exchange.getRequestURL(); requestPath = exchange.getRequestPath(); } else{ fullPath = exchange.getRequestURL() + "?" + exchange.getQueryString(); requestPath = exchange.getRequestPath() + "?" + exchange.getQueryString(); } doPush(exchange, fullPath); String referrer = exchange.getRequestHeaders().getFirst(Headers.REFERER); if (referrer != null) { String accept = exchange.getRequestHeaders().getFirst(Headers.ACCEPT); if (accept == null || !accept.contains("text/html")) { //if accept contains text/html it generally means the user has clicked //a link to move to a new page, and is not a resource load for the current page //we only care about resources for the current page exchange.addExchangeCompleteListener(new PushCompletionListener(fullPath, requestPath, referrer)); } } next.handleRequest(exchange); } private void doPush(HttpServerExchange exchange, String fullPath) { if (exchange.getConnection().isPushSupported()) { Map toPush = cache.get(fullPath); if (toPush != null) { Session session = getSession(exchange); if (session == null) { return; } Map pushed = (Map) session.getAttribute(SESSION_ATTRIBUTE); if (pushed == null) { pushed = Collections.synchronizedMap(new HashMap()); } for (Map.Entry entry : toPush.entrySet()) { PushedRequest request = entry.getValue(); Object pushedKey = pushed.get(request.getPath()); boolean doPush = pushedKey == null; if (!doPush) { if (pushedKey instanceof String && !pushedKey.equals(request.getEtag())) { doPush = true; } else if (pushedKey instanceof Long && ((Long) pushedKey) != request.getLastModified()) { doPush = true; } } if (doPush) { exchange.getConnection().pushResource(request.getPath(), Methods.GET, request.getRequestHeaders()); if(request.getEtag() != null) { pushed.put(request.getPath(), request.getEtag()); } else { pushed.put(request.getPath(), request.getLastModified()); } } } session.setAttribute(SESSION_ATTRIBUTE, pushed); } } } protected Session getSession(HttpServerExchange exchange) { SessionConfig sc = exchange.getAttachment(SessionConfig.ATTACHMENT_KEY); SessionManager sm = exchange.getAttachment(SessionManager.ATTACHMENT_KEY); if (sc == null || sm == null) { return null; } Session session = sm.getSession(exchange, sc); if (session == null) { return sm.createSession(exchange, sc); } return session; } private final class PushCompletionListener implements ExchangeCompletionListener { private final String fullPath; private final String requestPath; private final String referer; private PushCompletionListener(String fullPath, String requestPath, String referer) { this.fullPath = fullPath; this.requestPath = requestPath; this.referer = referer; } @Override public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { if (exchange.getStatusCode() == 200 && referer != null) { //for now only cache 200 response codes String lmString = exchange.getResponseHeaders().getFirst(Headers.LAST_MODIFIED); String etag = exchange.getResponseHeaders().getFirst(Headers.ETAG); long lastModified = -1; if(lmString != null) { Date dt = DateUtils.parseDate(lmString); if(dt != null) { lastModified = dt.getTime(); } } Map pushes = cache.get(referer); if(pushes == null) { synchronized (cache) { pushes = cache.get(referer); if(pushes == null) { cache.add(referer, pushes = Collections.synchronizedMap(new HashMap())); } } } pushes.put(fullPath, new PushedRequest(new HeaderMap(), requestPath, etag, lastModified)); } nextListener.proceed(); } } private static class PushedRequest { private final HeaderMap requestHeaders; private final String path; private final String etag; private final long lastModified; private PushedRequest(HeaderMap requestHeaders, String path, String etag, long lastModified) { this.requestHeaders = requestHeaders; this.path = path; this.etag = etag; this.lastModified = lastModified; } public HeaderMap getRequestHeaders() { return requestHeaders; } public String getPath() { return path; } public String getEtag() { return etag; } public long getLastModified() { return lastModified; } } public static class Builder implements HandlerBuilder { @Override public String name() { return "learning-push"; } @Override public Map> parameters() { Map> params = new HashMap<>(); params.put("max-age", Integer.class); params.put("max-entries", Integer.class); return params; } @Override public Set requiredParameters() { return null; } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { final int maxAge = config.containsKey("max-age") ? (Integer)config.get("max-age") : DEFAULT_MAX_CACHE_AGE; final int maxEntries = config.containsKey("max-entries") ? (Integer)config.get("max-entries") : DEFAULT_MAX_CACHE_ENTRIES; return new HandlerWrapper() { @Override public HttpHandler wrap(HttpHandler handler) { return new LearningPushHandler(maxEntries, maxAge, handler); } }; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/LocalNameResolvingHandler.java000066400000000000000000000120671420065311100333350ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.UndertowLogger; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; import java.util.Collections; import java.util.Map; import java.util.Set; /** * A handler that performs DNS lookup to resolve a local address. Unresolved local address may be created when a front * end server has sent a X-forwarded-host header or AJP is in use * * @author Stuart Douglas */ public class LocalNameResolvingHandler implements HttpHandler { private final HttpHandler next; private final ResolveType resolveType; public LocalNameResolvingHandler(HttpHandler next) { this.next = next; this.resolveType = ResolveType.FORWARD_AND_REVERSE; } public LocalNameResolvingHandler(HttpHandler next, ResolveType resolveType) { this.next = next; this.resolveType = resolveType; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final InetSocketAddress address = exchange.getDestinationAddress(); if (address != null) { if ((resolveType == ResolveType.FORWARD || resolveType == ResolveType.FORWARD_AND_REVERSE) && address.isUnresolved()) { try { if (System.getSecurityManager() == null) { final InetSocketAddress resolvedAddress = new InetSocketAddress(InetAddress.getByName(address.getHostName()), address.getPort()); exchange.setDestinationAddress(resolvedAddress); } else { AccessController.doPrivileged(new PrivilegedExceptionAction() { @Override public Object run() throws UnknownHostException { final InetSocketAddress resolvedAddress = new InetSocketAddress(InetAddress.getByName(address.getHostName()), address.getPort()); exchange.setDestinationAddress(resolvedAddress); return null; } }); } } catch (UnknownHostException e) { UndertowLogger.REQUEST_LOGGER.debugf(e, "Could not resolve hostname %s", address.getHostString()); } } else if (resolveType == ResolveType.REVERSE || resolveType == ResolveType.FORWARD_AND_REVERSE) { if (System.getSecurityManager() == null) { address.getHostName(); } else { AccessController.doPrivileged(new PrivilegedAction() { @Override public Object run() { address.getHostName(); return null; } }); } //we call set source address because otherwise the underlying channel could just return a new address exchange.setDestinationAddress(address); } } next.handleRequest(exchange); } public enum ResolveType { FORWARD, REVERSE, FORWARD_AND_REVERSE } public static class Builder implements HandlerBuilder { @Override public String name() { return "resolve-local-name"; } @Override public Map> parameters() { return Collections.emptyMap(); } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { return new Wrapper(); } } private static class Wrapper implements HandlerWrapper { @Override public HttpHandler wrap(HttpHandler handler) { return new LocalNameResolvingHandler(handler); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/MetricsHandler.java000066400000000000000000000126731420065311100312220ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import java.util.Date; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicLongFieldUpdater; /** * Handler that records some metrics * * @author Stuart Douglas */ public class MetricsHandler implements HttpHandler { public static final HandlerWrapper WRAPPER = new HandlerWrapper() { @Override public HttpHandler wrap(HttpHandler handler) { return new MetricsHandler(handler); } }; private volatile MetricResult totalResult = new MetricResult(new Date()); private final HttpHandler next; public MetricsHandler(HttpHandler next) { this.next = next; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if(!exchange.isComplete()) { final long start = System.currentTimeMillis(); exchange.addExchangeCompleteListener(new ExchangeCompletionListener() { @Override public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { long time = System.currentTimeMillis() - start; totalResult.update((int) time, exchange.getStatusCode()); nextListener.proceed(); } }); } next.handleRequest(exchange); } public void reset() { this.totalResult = new MetricResult(new Date()); } public MetricResult getMetrics() { return new MetricResult(this.totalResult); } public static class MetricResult { private static final AtomicLongFieldUpdater totalRequestTimeUpdater = AtomicLongFieldUpdater.newUpdater(MetricResult.class, "totalRequestTime"); private static final AtomicIntegerFieldUpdater maxRequestTimeUpdater = AtomicIntegerFieldUpdater.newUpdater(MetricResult.class, "maxRequestTime"); private static final AtomicIntegerFieldUpdater minRequestTimeUpdater = AtomicIntegerFieldUpdater.newUpdater(MetricResult.class, "minRequestTime"); private static final AtomicLongFieldUpdater invocationsUpdater = AtomicLongFieldUpdater.newUpdater(MetricResult.class, "totalRequests"); private static final AtomicLongFieldUpdater errorsUpdater = AtomicLongFieldUpdater.newUpdater(MetricResult.class, "totalErrors"); private final Date metricsStartDate; private volatile long totalRequestTime; private volatile int maxRequestTime; private volatile int minRequestTime = -1; private volatile long totalRequests; private volatile long totalErrors; public MetricResult(Date metricsStartDate) { this.metricsStartDate = metricsStartDate; } public MetricResult(MetricResult copy) { this.metricsStartDate = copy.metricsStartDate; this.totalRequestTime = copy.totalRequestTime; this.maxRequestTime = copy.maxRequestTime; this.minRequestTime = copy.minRequestTime; this.totalRequests = copy.totalRequests; this.totalErrors = copy.totalErrors; } void update(final int requestTime, int statusCode) { totalRequestTimeUpdater.addAndGet(this, requestTime); int maxRequestTime; do { maxRequestTime = this.maxRequestTime; if (requestTime < maxRequestTime) { break; } } while (!maxRequestTimeUpdater.compareAndSet(this, maxRequestTime, requestTime)); int minRequestTime; do { minRequestTime = this.minRequestTime; if (requestTime > minRequestTime && minRequestTime != -1) { break; } } while (!minRequestTimeUpdater.compareAndSet(this, minRequestTime, requestTime)); invocationsUpdater.incrementAndGet(this); if(statusCode >= 400) { errorsUpdater.incrementAndGet(this); } } public Date getMetricsStartDate() { return metricsStartDate; } public long getTotalRequestTime() { return totalRequestTime; } public int getMaxRequestTime() { return maxRequestTime; } public int getMinRequestTime() { return minRequestTime; } public long getTotalRequests() { return totalRequests; } public long getTotalErrors() { return totalErrors; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/NameVirtualHostHandler.java000066400000000000000000000060701420065311100326730ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.util.Locale; import java.util.Map; import io.undertow.Handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.CopyOnWriteMap; import io.undertow.util.Headers; /** * A {@link HttpHandler} that implements virtual hosts based on the Host: http header * header. * * @author Stuart Douglas */ public class NameVirtualHostHandler implements HttpHandler { private volatile HttpHandler defaultHandler = ResponseCodeHandler.HANDLE_404; private final Map hosts = new CopyOnWriteMap<>(); @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final String hostHeader = exchange.getRequestHeaders().getFirst(Headers.HOST); if (hostHeader != null) { String host; if (hostHeader.contains(":")) { //header can be in host:port format host = hostHeader.substring(0, hostHeader.lastIndexOf(":")); } else { host = hostHeader; } //most hosts will be lowercase, so we do the host HttpHandler handler = hosts.get(host); if (handler != null) { handler.handleRequest(exchange); return; } //do a cache insensitive match handler = hosts.get(host.toLowerCase(Locale.ENGLISH)); if (handler != null) { handler.handleRequest(exchange); return; } } defaultHandler.handleRequest(exchange); } public HttpHandler getDefaultHandler() { return defaultHandler; } public Map getHosts() { return hosts; } public NameVirtualHostHandler setDefaultHandler(final HttpHandler defaultHandler) { Handlers.handlerNotNull(defaultHandler); this.defaultHandler = defaultHandler; return this; } public synchronized NameVirtualHostHandler addHost(final String host, final HttpHandler handler) { Handlers.handlerNotNull(handler); hosts.put(host.toLowerCase(Locale.ENGLISH), handler); return this; } public synchronized NameVirtualHostHandler removeHost(final String host) { hosts.remove(host.toLowerCase(Locale.ENGLISH)); return this; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/OriginHandler.java000066400000000000000000000130661420065311100310400ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import io.undertow.Handlers; import io.undertow.UndertowLogger; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; /** * A handler for the HTTP Origin (RFC 6454) header. * * @author Stuart Douglas */ public class OriginHandler implements HttpHandler { private volatile HttpHandler originFailedHandler = ResponseCodeHandler.HANDLE_403; private volatile Set allowedOrigins = new HashSet<>(); private volatile boolean requireAllOrigins = true; private volatile boolean requireOriginHeader = true; private volatile HttpHandler next = ResponseCodeHandler.HANDLE_404; @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final List origin = exchange.getRequestHeaders().get(Headers.ORIGIN); if (origin == null) { if (requireOriginHeader) { //TODO: Is 403 (Forbidden) the best response code if (UndertowLogger.REQUEST_LOGGER.isDebugEnabled()) { UndertowLogger.REQUEST_LOGGER.debugf("Refusing request for %s due to lack of Origin: header", exchange.getRequestPath()); } originFailedHandler.handleRequest(exchange); return; } } else { boolean found = false; final boolean requireAllOrigins = this.requireAllOrigins; for (final String header : origin) { if (allowedOrigins.contains(header)) { found = true; if (!requireAllOrigins) { break; } } else if (requireAllOrigins) { if (UndertowLogger.REQUEST_LOGGER.isDebugEnabled()) { UndertowLogger.REQUEST_LOGGER.debugf("Refusing request for %s due to Origin %s not being in the allowed origins list", exchange.getRequestPath(), header); } originFailedHandler.handleRequest(exchange); return; } } if (!found) { if (UndertowLogger.REQUEST_LOGGER.isDebugEnabled()) { UndertowLogger.REQUEST_LOGGER.debugf("Refusing request for %s as none of the specified origins %s were in the allowed origins list", exchange.getRequestPath(), origin); } originFailedHandler.handleRequest(exchange); return; } } next.handleRequest(exchange); } public synchronized OriginHandler addAllowedOrigin(final String origin) { final Set allowedOrigins = new HashSet<>(this.allowedOrigins); allowedOrigins.add(origin); this.allowedOrigins = Collections.unmodifiableSet(allowedOrigins); return this; } public synchronized OriginHandler addAllowedOrigins(final Collection origins) { final Set allowedOrigins = new HashSet<>(this.allowedOrigins); allowedOrigins.addAll(origins); this.allowedOrigins = Collections.unmodifiableSet(allowedOrigins); return this; } public synchronized OriginHandler addAllowedOrigins(final String... origins) { final Set allowedOrigins = new HashSet<>(this.allowedOrigins); allowedOrigins.addAll(Arrays.asList(origins)); this.allowedOrigins = Collections.unmodifiableSet(allowedOrigins); return this; } public synchronized Set getAllowedOrigins() { return allowedOrigins; } public synchronized OriginHandler clearAllowedOrigins() { this.allowedOrigins = Collections.emptySet(); return this; } public boolean isRequireAllOrigins() { return requireAllOrigins; } public OriginHandler setRequireAllOrigins(final boolean requireAllOrigins) { this.requireAllOrigins = requireAllOrigins; return this; } public boolean isRequireOriginHeader() { return requireOriginHeader; } public OriginHandler setRequireOriginHeader(final boolean requireOriginHeader) { this.requireOriginHeader = requireOriginHeader; return this; } public HttpHandler getNext() { return next; } public OriginHandler setNext(final HttpHandler next) { Handlers.handlerNotNull(next); this.next = next; return this; } public HttpHandler getOriginFailedHandler() { return originFailedHandler; } public OriginHandler setOriginFailedHandler(HttpHandler originFailedHandler) { Handlers.handlerNotNull(originFailedHandler); this.originFailedHandler = originFailedHandler; return this; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/PathHandler.java000066400000000000000000000143021420065311100304770ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.Handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.cache.LRUCache; import io.undertow.util.PathMatcher; import java.util.Map.Entry; import java.util.Set; import java.util.stream.Collectors; /** * Handler that dispatches to a given handler based of a prefix match of the path. *

* This only matches a single level of a request, e.g if you have a request that takes the form: *

* /foo/bar *

* * @author Stuart Douglas */ public class PathHandler implements HttpHandler { private final PathMatcher pathMatcher = new PathMatcher<>(); private final LRUCache> cache; public PathHandler(final HttpHandler defaultHandler) { this(0); pathMatcher.addPrefixPath("/", defaultHandler); } public PathHandler(final HttpHandler defaultHandler, int cacheSize) { this(cacheSize); pathMatcher.addPrefixPath("/", defaultHandler); } public PathHandler() { this(0); } public PathHandler(int cacheSize) { if(cacheSize > 0) { cache = new LRUCache<>(cacheSize, -1, true); } else { cache = null; } } @Override public String toString() { Set> paths = pathMatcher.getPaths().entrySet(); if (paths.size() == 1) { return "path( " + paths.toArray()[0] + " )"; } else { return "path( {" + paths.stream().map(s -> s.getValue().toString()).collect(Collectors.joining(", ")) + "} )"; } } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { PathMatcher.PathMatch match = null; boolean hit = false; if(cache != null) { match = cache.get(exchange.getRelativePath()); hit = true; } if(match == null) { match = pathMatcher.match(exchange.getRelativePath()); } if (match.getValue() == null) { ResponseCodeHandler.HANDLE_404.handleRequest(exchange); return; } if(hit) { cache.add(exchange.getRelativePath(), match); } exchange.setRelativePath(match.getRemaining()); if(exchange.getResolvedPath().isEmpty()) { //first path handler, we can just use the matched part exchange.setResolvedPath(match.getMatched()); } else { //already something in the resolved path exchange.setResolvedPath(exchange.getResolvedPath() + match.getMatched()); } match.getValue().handleRequest(exchange); } /** * Adds a path prefix and a handler for that path. If the path does not start * with a / then one will be prepended. *

* The match is done on a prefix bases, so registering /foo will also match /foo/bar. Exact * path matches are taken into account first. *

* If / is specified as the path then it will replace the default handler. * * @param path The path * @param handler The handler * @see #addPrefixPath(String, io.undertow.server.HttpHandler) * @deprecated Superseded by {@link #addPrefixPath(String, io.undertow.server.HttpHandler)}. */ @Deprecated public synchronized PathHandler addPath(final String path, final HttpHandler handler) { return addPrefixPath(path, handler); } /** * Adds a path prefix and a handler for that path. If the path does not start * with a / then one will be prepended. *

* The match is done on a prefix bases, so registering /foo will also match /foo/bar. * Though exact path matches are taken into account before prefix path matches. So * if an exact path match exists its handler will be triggered. *

* If / is specified as the path then it will replace the default handler. * * @param path If the request contains this prefix, run handler. * @param handler The handler which is activated upon match. * @return The resulting PathHandler after this path has been added to it. */ public synchronized PathHandler addPrefixPath(final String path, final HttpHandler handler) { Handlers.handlerNotNull(handler); pathMatcher.addPrefixPath(path, handler); return this; } /** * If the request path is exactly equal to the given path, run the handler. *

* Exact paths are prioritized higher than prefix paths. * * @param path If the request path is exactly this, run handler. * @param handler Handler run upon exact path match. * @return The resulting PathHandler after this path has been added to it. */ public synchronized PathHandler addExactPath(final String path, final HttpHandler handler) { Handlers.handlerNotNull(handler); pathMatcher.addExactPath(path, handler); return this; } @Deprecated public synchronized PathHandler removePath(final String path) { return removePrefixPath(path); } public synchronized PathHandler removePrefixPath(final String path) { pathMatcher.removePrefixPath(path); return this; } public synchronized PathHandler removeExactPath(final String path) { pathMatcher.removeExactPath(path); return this; } public synchronized PathHandler clearPaths() { pathMatcher.clearPaths(); return this; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/PathSeparatorHandler.java000066400000000000000000000056721420065311100323720ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import java.io.File; import java.util.Collections; import java.util.Map; import java.util.Set; import static io.undertow.util.CanonicalPathUtils.canonicalize; /** * A handler that translates non slash separator characters in the URL into a slash. * * In general this will translate backslash into slash on windows systems. * * @author Stuart Douglas */ public class PathSeparatorHandler implements HttpHandler { private final HttpHandler next; public PathSeparatorHandler(final HttpHandler next) { this.next = next; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { boolean handlingRequired = File.separatorChar != '/'; if (handlingRequired) { exchange.setRequestPath(canonicalize(exchange.getRequestPath().replace(File.separatorChar, '/'))); exchange.setRelativePath(canonicalize(exchange.getRelativePath().replace(File.separatorChar, '/'))); exchange.setResolvedPath(canonicalize(exchange.getResolvedPath().replace(File.separatorChar, '/'))); } next.handleRequest(exchange); } @Override public String toString() { return "path-separator()"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "path-separator"; } @Override public Map> parameters() { return Collections.emptyMap(); } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { return new Wrapper(); } } private static class Wrapper implements HandlerWrapper { @Override public HttpHandler wrap(HttpHandler handler) { return new PathSeparatorHandler(handler); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/PathTemplateHandler.java000066400000000000000000000103211420065311100321700ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.AttachmentKey; import io.undertow.util.PathTemplate; import io.undertow.util.PathTemplateMatcher; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * A handler that matches URI templates * * @author Stuart Douglas * @see PathTemplateMatcher */ public class PathTemplateHandler implements HttpHandler { private final boolean rewriteQueryParameters; private final HttpHandler next; /** * @see io.undertow.util.PathTemplateMatch#ATTACHMENT_KEY */ @Deprecated public static final AttachmentKey PATH_TEMPLATE_MATCH = AttachmentKey.create(PathTemplateMatch.class); private final PathTemplateMatcher pathTemplateMatcher = new PathTemplateMatcher<>(); public PathTemplateHandler() { this(true); } public PathTemplateHandler(boolean rewriteQueryParameters) { this(ResponseCodeHandler.HANDLE_404, rewriteQueryParameters); } public PathTemplateHandler(HttpHandler next) { this(next, true); } public PathTemplateHandler(HttpHandler next, boolean rewriteQueryParameters) { this.rewriteQueryParameters = rewriteQueryParameters; this.next = next; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { PathTemplateMatcher.PathMatchResult match = pathTemplateMatcher.match(exchange.getRelativePath()); if (match == null) { next.handleRequest(exchange); return; } exchange.putAttachment(PATH_TEMPLATE_MATCH, new PathTemplateMatch(match.getMatchedTemplate(), match.getParameters())); exchange.putAttachment(io.undertow.util.PathTemplateMatch.ATTACHMENT_KEY, new io.undertow.util.PathTemplateMatch(match.getMatchedTemplate(), match.getParameters())); if (rewriteQueryParameters) { for (Map.Entry entry : match.getParameters().entrySet()) { exchange.addQueryParam(entry.getKey(), entry.getValue()); } } match.getValue().handleRequest(exchange); } public PathTemplateHandler add(final String uriTemplate, final HttpHandler handler) { pathTemplateMatcher.add(uriTemplate, handler); return this; } public PathTemplateHandler remove(final String uriTemplate) { pathTemplateMatcher.remove(uriTemplate); return this; } @Override public String toString() { Set paths = pathTemplateMatcher.getPathTemplates(); if (paths.size() == 1) { return "path-template( " + paths.toArray()[0] + " )"; } else { return "path-template( {" + paths.stream().map(s -> s.getTemplateString().toString()).collect(Collectors.joining(", ")) + "} )"; } } /** * @see io.undertow.util.PathTemplateMatch */ @Deprecated public static final class PathTemplateMatch { private final String matchedTemplate; private final Map parameters; public PathTemplateMatch(String matchedTemplate, Map parameters) { this.matchedTemplate = matchedTemplate; this.parameters = parameters; } public String getMatchedTemplate() { return matchedTemplate; } public Map getParameters() { return parameters; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/PeerNameResolvingHandler.java000066400000000000000000000120071420065311100331700ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.UndertowLogger; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; import java.util.Collections; import java.util.Map; import java.util.Set; /** * A handler that performs reverse DNS lookup to resolve a peer address * * @author Stuart Douglas */ public class PeerNameResolvingHandler implements HttpHandler { private final HttpHandler next; private final ResolveType resolveType; public PeerNameResolvingHandler(HttpHandler next) { this.next = next; this.resolveType = ResolveType.FORWARD_AND_REVERSE; } public PeerNameResolvingHandler(HttpHandler next, ResolveType resolveType) { this.next = next; this.resolveType = resolveType; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final InetSocketAddress address = exchange.getSourceAddress(); if (address != null) { if ((resolveType == ResolveType.FORWARD || resolveType == ResolveType.FORWARD_AND_REVERSE) && address.isUnresolved()) { try { if (System.getSecurityManager() == null) { final InetSocketAddress resolvedAddress = new InetSocketAddress(InetAddress.getByName(address.getHostName()), address.getPort()); exchange.setSourceAddress(resolvedAddress); } else { AccessController.doPrivileged(new PrivilegedExceptionAction() { @Override public Object run() throws UnknownHostException { final InetSocketAddress resolvedAddress = new InetSocketAddress(InetAddress.getByName(address.getHostName()), address.getPort()); exchange.setSourceAddress(resolvedAddress); return null; } }); } } catch (UnknownHostException e) { UndertowLogger.REQUEST_LOGGER.debugf(e, "Could not resolve hostname %s", address.getHostString()); } } else if (resolveType == ResolveType.REVERSE || resolveType == ResolveType.FORWARD_AND_REVERSE) { if (System.getSecurityManager() == null) { address.getHostName(); } else { AccessController.doPrivileged(new PrivilegedAction() { @Override public Object run() { address.getHostName(); return null; } }); } //we call set source address because otherwise the underlying channel could just return a new address exchange.setSourceAddress(address); } } next.handleRequest(exchange); } public enum ResolveType { FORWARD, REVERSE, FORWARD_AND_REVERSE } @Override public String toString() { return "resolve-peer-name()"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "resolve-peer-name"; } @Override public Map> parameters() { return Collections.emptyMap(); } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { return new Wrapper(); } } private static class Wrapper implements HandlerWrapper { @Override public HttpHandler wrap(HttpHandler handler) { return new PeerNameResolvingHandler(handler); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/PredicateContextHandler.java000066400000000000000000000025751420065311100330610ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.predicate.Predicate; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import java.util.TreeMap; /** * Handler that sets up the predicate context * * @author Stuart Douglas */ public class PredicateContextHandler implements HttpHandler { private final HttpHandler next; public PredicateContextHandler(HttpHandler next) { this.next = next; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.putAttachment(Predicate.PREDICATE_CONTEXT, new TreeMap()); next.handleRequest(exchange); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/PredicateHandler.java000066400000000000000000000042661420065311100315130ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.predicate.Predicate; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; /** * @author Stuart Douglas */ public class PredicateHandler implements HttpHandler { private volatile Predicate predicate; private volatile HttpHandler trueHandler; private volatile HttpHandler falseHandler; public PredicateHandler(final Predicate predicate, final HttpHandler trueHandler, final HttpHandler falseHandler) { this.predicate = predicate; this.trueHandler = trueHandler; this.falseHandler = falseHandler; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { HttpHandler next = predicate.resolve(exchange) ? trueHandler : falseHandler; next.handleRequest(exchange); } public Predicate getPredicate() { return predicate; } public PredicateHandler setPredicate(final Predicate predicate) { this.predicate = predicate; return this; } public HttpHandler getTrueHandler() { return trueHandler; } public PredicateHandler setTrueHandler(final HttpHandler trueHandler) { this.trueHandler = trueHandler; return this; } public HttpHandler getFalseHandler() { return falseHandler; } public PredicateHandler setFalseHandler(final HttpHandler falseHandler) { this.falseHandler = falseHandler; return this; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/ProxyPeerAddressHandler.java000066400000000000000000000161221420065311100330500ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.UndertowLogger; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.Headers; import io.undertow.util.NetworkUtils; import java.net.InetSocketAddress; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; /** * Handler that sets the peer address to the value of the X-Forwarded-For header. *

* This should only be used behind a proxy that always sets this header, otherwise it * is possible for an attacker to forge their peer address; * * @author Stuart Douglas */ public class ProxyPeerAddressHandler implements HttpHandler { private static final Pattern IP4_EXACT = Pattern.compile(NetworkUtils.IP4_EXACT); private static final Pattern IP6_EXACT = Pattern.compile(NetworkUtils.IP6_EXACT); private final HttpHandler next; private static final boolean DEFAULT_CHANGE_LOCAL_ADDR_PORT = Boolean.getBoolean("io.undertow.forwarded.change-local-addr-port"); private static final String CHANGE_LOCAL_ADDR_PORT = "change-local-addr-port"; private final boolean isChangeLocalAddrPort; public ProxyPeerAddressHandler(HttpHandler next) { this(next, DEFAULT_CHANGE_LOCAL_ADDR_PORT); } public ProxyPeerAddressHandler(HttpHandler next, boolean isChangeLocalAddrPort) { this.next = next; this.isChangeLocalAddrPort = isChangeLocalAddrPort; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { String forwardedFor = exchange.getRequestHeaders().getFirst(Headers.X_FORWARDED_FOR); if (forwardedFor != null) { String remoteClient = mostRecent(forwardedFor); //we have no way of knowing the port if (IP4_EXACT.matcher(remoteClient).matches()) { exchange.setSourceAddress(new InetSocketAddress(NetworkUtils.parseIpv4Address(remoteClient), 0)); } else if (IP6_EXACT.matcher(remoteClient).matches()) { exchange.setSourceAddress(new InetSocketAddress(NetworkUtils.parseIpv6Address(remoteClient), 0)); } else { exchange.setSourceAddress(InetSocketAddress.createUnresolved(remoteClient, 0)); } } String forwardedProto = exchange.getRequestHeaders().getFirst(Headers.X_FORWARDED_PROTO); if (forwardedProto != null) { exchange.setRequestScheme(mostRecent(forwardedProto)); } String forwardedHost = exchange.getRequestHeaders().getFirst(Headers.X_FORWARDED_HOST); String forwardedPort = exchange.getRequestHeaders().getFirst(Headers.X_FORWARDED_PORT); if (forwardedHost != null) { String value = mostRecent(forwardedHost); if(value.startsWith("[")) { int end = value.lastIndexOf("]"); if(end == -1 ) { end = 0; } int index = value.indexOf(":", end); if(index != -1) { forwardedPort = value.substring(index + 1); value = value.substring(0, index); } } else { int index = value.lastIndexOf(":"); if(index != -1) { forwardedPort = value.substring(index + 1); value = value.substring(0, index); } } int port = 0; String hostHeader = NetworkUtils.formatPossibleIpv6Address(value); if(forwardedPort != null) { try { port = Integer.parseInt(mostRecent(forwardedPort)); if(port > 0) { String scheme = exchange.getRequestScheme(); if (!standardPort(port, scheme)) { hostHeader += ":" + port; } } else { UndertowLogger.REQUEST_LOGGER.debugf("Ignoring negative port: %s", forwardedPort); } } catch (NumberFormatException ignore) { UndertowLogger.REQUEST_LOGGER.debugf("Cannot parse port: %s", forwardedPort); } } exchange.getRequestHeaders().put(Headers.HOST, hostHeader); if (isChangeLocalAddrPort) { exchange.setDestinationAddress(InetSocketAddress.createUnresolved(value, port)); } } next.handleRequest(exchange); } private String mostRecent(String header) { int index = header.indexOf(','); if (index == -1) { return header; } else { return header.substring(0, index); } } private static boolean standardPort(int port, String scheme) { return (port == 80 && "http".equals(scheme)) || (port == 443 && "https".equals(scheme)); } @Override public String toString() { return "proxy-peer-address()"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "proxy-peer-address"; } @Override public Map> parameters() { Map> params = new HashMap<>(); params.put(CHANGE_LOCAL_ADDR_PORT, boolean.class); return params; } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return CHANGE_LOCAL_ADDR_PORT; } @Override public HandlerWrapper build(Map config) { Boolean isChangeLocalAddrPort = (Boolean) config.get(CHANGE_LOCAL_ADDR_PORT); return new Wrapper(isChangeLocalAddrPort == null ? DEFAULT_CHANGE_LOCAL_ADDR_PORT : isChangeLocalAddrPort); } } private static class Wrapper implements HandlerWrapper { private final boolean isChangeLocalAddrPort; private Wrapper(boolean isChangeLocalAddrPort) { this.isChangeLocalAddrPort = isChangeLocalAddrPort; } @Override public HttpHandler wrap(HttpHandler handler) { return new ProxyPeerAddressHandler(handler, isChangeLocalAddrPort); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/RedirectHandler.java000066400000000000000000000070421420065311100313470ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributeParser; import io.undertow.attribute.ExchangeAttributes; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; /** * A redirect handler that redirects to the specified location via a 302 redirect. *

* The location is specified as an exchange attribute string. * * @author Stuart Douglas * @see ExchangeAttributes */ public class RedirectHandler implements HttpHandler { private final ExchangeAttribute attribute; public RedirectHandler(final String location) { ExchangeAttributeParser parser = ExchangeAttributes.parser(getClass().getClassLoader()); attribute = parser.parse(location); } public RedirectHandler(final String location, final ClassLoader classLoader) { ExchangeAttributeParser parser = ExchangeAttributes.parser(classLoader); attribute = parser.parse(location); } public RedirectHandler(ExchangeAttribute attribute) { this.attribute = attribute; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.setStatusCode(StatusCodes.FOUND); exchange.getResponseHeaders().put(Headers.LOCATION, attribute.readAttribute(exchange)); exchange.endExchange(); } @Override public String toString() { return "redirect( '" + attribute.toString() + "' )"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "redirect"; } @Override public Map> parameters() { Map> params = new HashMap<>(); params.put("value", ExchangeAttribute.class); return params; } @Override public Set requiredParameters() { return Collections.singleton("value"); } @Override public String defaultParameter() { return "value"; } @Override public HandlerWrapper build(Map config) { return new Wrapper((ExchangeAttribute) config.get("value")); } } private static class Wrapper implements HandlerWrapper { private final ExchangeAttribute value; private Wrapper(ExchangeAttribute value) { this.value = value; } @Override public HttpHandler wrap(HttpHandler handler) { return new RedirectHandler(value); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/RequestBufferingHandler.java000066400000000000000000000207651420065311100330750ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.UndertowLogger; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.Connectors; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.server.protocol.http.HttpContinue; import org.xnio.ChannelListener; import org.xnio.IoUtils; import org.xnio.channels.StreamSourceChannel; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Collections; import java.util.Map; import java.util.Set; /** * * Handler that will buffer all request data * * @author Stuart Douglas */ public class RequestBufferingHandler implements HttpHandler { private final HttpHandler next; private final int maxBuffers; public RequestBufferingHandler(HttpHandler next, int maxBuffers) { this.next = next; this.maxBuffers = maxBuffers; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { if(!exchange.isRequestComplete() && !HttpContinue.requiresContinueResponse(exchange.getRequestHeaders())) { final StreamSourceChannel channel = exchange.getRequestChannel(); int readBuffers = 0; final PooledByteBuffer[] bufferedData = new PooledByteBuffer[maxBuffers]; PooledByteBuffer buffer = exchange.getConnection().getByteBufferPool().allocate(); try { do { int r; ByteBuffer b = buffer.getBuffer(); r = channel.read(b); if (r == -1) { if (b.position() == 0) { buffer.close(); } else { b.flip(); bufferedData[readBuffers] = buffer; } break; } else if (r == 0) { final PooledByteBuffer finalBuffer = buffer; final int finalReadBuffers = readBuffers; channel.getReadSetter().set(new ChannelListener() { PooledByteBuffer buffer = finalBuffer; int readBuffers = finalReadBuffers; @Override public void handleEvent(StreamSourceChannel channel) { try { do { int r; ByteBuffer b = buffer.getBuffer(); r = channel.read(b); if (r == -1) { if (b.position() == 0) { buffer.close(); } else { b.flip(); bufferedData[readBuffers] = buffer; } Connectors.ungetRequestBytes(exchange, bufferedData); Connectors.resetRequestChannel(exchange); channel.getReadSetter().set(null); channel.suspendReads(); Connectors.executeRootHandler(next, exchange); return; } else if (r == 0) { return; } else if (!b.hasRemaining()) { b.flip(); bufferedData[readBuffers++] = buffer; if (readBuffers == maxBuffers) { Connectors.ungetRequestBytes(exchange, bufferedData); Connectors.resetRequestChannel(exchange); channel.getReadSetter().set(null); channel.suspendReads(); Connectors.executeRootHandler(next, exchange); return; } buffer = exchange.getConnection().getByteBufferPool().allocate(); } } while (true); } catch (Throwable t) { if (t instanceof IOException) { UndertowLogger.REQUEST_IO_LOGGER.ioException((IOException) t); } else { UndertowLogger.REQUEST_IO_LOGGER.handleUnexpectedFailure(t); } for (int i = 0; i < bufferedData.length; ++i) { IoUtils.safeClose(bufferedData[i]); } if (buffer != null && buffer.isOpen()) { IoUtils.safeClose(buffer); } exchange.endExchange(); } } }); channel.resumeReads(); return; } else if (!b.hasRemaining()) { b.flip(); bufferedData[readBuffers++] = buffer; if (readBuffers == maxBuffers) { break; } buffer = exchange.getConnection().getByteBufferPool().allocate(); } } while (true); Connectors.ungetRequestBytes(exchange, bufferedData); Connectors.resetRequestChannel(exchange); } catch (Exception | Error e) { for (int i = 0; i < bufferedData.length; ++i) { IoUtils.safeClose(bufferedData[i]); } if (buffer != null && buffer.isOpen()) { IoUtils.safeClose(buffer); } throw e; } } next.handleRequest(exchange); } public static final class Wrapper implements HandlerWrapper { private final int maxBuffers; public Wrapper(int maxBuffers) { this.maxBuffers = maxBuffers; } @Override public HttpHandler wrap(HttpHandler handler) { return new RequestBufferingHandler(handler, maxBuffers); } } @Override public String toString() { return "buffer-request( " + maxBuffers + " )"; } public static final class Builder implements HandlerBuilder { @Override public String name() { return "buffer-request"; } @Override public Map> parameters() { return Collections.>singletonMap("buffers", Integer.class); } @Override public Set requiredParameters() { return Collections.singleton("buffers"); } @Override public String defaultParameter() { return "buffers"; } @Override public HandlerWrapper build(Map config) { return new Wrapper((Integer) config.get("buffers")); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/RequestDumpingHandler.java000066400000000000000000000217161420065311100325660ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.util.Collections; import java.util.Deque; import java.util.Iterator; import java.util.Map; import java.util.Set; import io.undertow.UndertowLogger; import io.undertow.attribute.StoredResponse; import io.undertow.security.api.SecurityContext; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.server.handlers.form.FormData; import io.undertow.server.handlers.form.FormDataParser; import io.undertow.util.HeaderValues; import io.undertow.util.Headers; import io.undertow.util.LocaleUtils; /** * Handler that dumps a exchange to a log. * * @author Stuart Douglas * @author Richard Opalka */ public class RequestDumpingHandler implements HttpHandler { private final HttpHandler next; public RequestDumpingHandler(final HttpHandler next) { this.next = next; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final StringBuilder sb = new StringBuilder(); // Log pre-service information final SecurityContext sc = exchange.getSecurityContext(); sb.append("\n----------------------------REQUEST---------------------------\n"); sb.append(" URI=" + exchange.getRequestURI() + "\n"); sb.append(" characterEncoding=" + exchange.getRequestHeaders().get(Headers.CONTENT_ENCODING) + "\n"); sb.append(" contentLength=" + exchange.getRequestContentLength() + "\n"); sb.append(" contentType=" + exchange.getRequestHeaders().get(Headers.CONTENT_TYPE) + "\n"); if (sc != null) { if (sc.isAuthenticated()) { sb.append(" authType=" + sc.getMechanismName() + "\n"); sb.append(" principle=" + sc.getAuthenticatedAccount().getPrincipal() + "\n"); } else { sb.append(" authType=none" + "\n"); } } for (Cookie cookie : exchange.requestCookies()) { sb.append(" cookie=" + cookie.getName() + "=" + cookie.getValue() + "\n"); } for (HeaderValues header : exchange.getRequestHeaders()) { for (String value : header) { sb.append(" header=" + header.getHeaderName() + "=" + value + "\n"); } } sb.append(" locale=" + LocaleUtils.getLocalesFromHeader(exchange.getRequestHeaders().get(Headers.ACCEPT_LANGUAGE)) + "\n"); sb.append(" method=" + exchange.getRequestMethod() + "\n"); Map> pnames = exchange.getQueryParameters(); for (Map.Entry> entry : pnames.entrySet()) { String pname = entry.getKey(); Iterator pvalues = entry.getValue().iterator(); sb.append(" parameter="); sb.append(pname); sb.append('='); while (pvalues.hasNext()) { sb.append(pvalues.next()); if (pvalues.hasNext()) { sb.append(", "); } } sb.append("\n"); } sb.append(" protocol=" + exchange.getProtocol() + "\n"); sb.append(" queryString=" + exchange.getQueryString() + "\n"); sb.append(" remoteAddr=" + exchange.getSourceAddress() + "\n"); sb.append(" remoteHost=" + exchange.getSourceAddress().getHostName() + "\n"); sb.append(" scheme=" + exchange.getRequestScheme() + "\n"); sb.append(" host=" + exchange.getRequestHeaders().getFirst(Headers.HOST) + "\n"); sb.append(" serverPort=" + exchange.getDestinationAddress().getPort() + "\n"); sb.append(" isSecure=" + exchange.isSecure() + "\n"); exchange.addExchangeCompleteListener(new ExchangeCompletionListener() { @Override public void exchangeEvent(final HttpServerExchange exchange, final NextListener nextListener) { dumpRequestBody(exchange, sb); // Log post-service information sb.append("--------------------------RESPONSE--------------------------\n"); if (sc != null) { if (sc.isAuthenticated()) { sb.append(" authType=" + sc.getMechanismName() + "\n"); sb.append(" principle=" + sc.getAuthenticatedAccount().getPrincipal() + "\n"); } else { sb.append(" authType=none" + "\n"); } } sb.append(" contentLength=" + exchange.getResponseContentLength() + "\n"); sb.append(" contentType=" + exchange.getResponseHeaders().getFirst(Headers.CONTENT_TYPE) + "\n"); for (Cookie cookie : exchange.responseCookies()) { sb.append(" cookie=" + cookie.getName() + "=" + cookie.getValue() + "; domain=" + cookie.getDomain() + "; path=" + cookie.getPath() + "\n"); } for (HeaderValues header : exchange.getResponseHeaders()) { for (String value : header) { sb.append(" header=" + header.getHeaderName() + "=" + value + "\n"); } } sb.append(" status=" + exchange.getStatusCode() + "\n"); String storedResponse = StoredResponse.INSTANCE.readAttribute(exchange); if (storedResponse != null) { sb.append("body=\n"); sb.append(storedResponse); } sb.append("\n=============================================================="); nextListener.proceed(); UndertowLogger.REQUEST_DUMPER_LOGGER.info(sb.toString()); } }); // Perform the exchange next.handleRequest(exchange); } private void dumpRequestBody(HttpServerExchange exchange, StringBuilder sb) { try { FormData formData = exchange.getAttachment(FormDataParser.FORM_DATA); if (formData != null) { sb.append("body=\n"); for (String formField : formData) { Deque formValues = formData.get(formField); sb.append(formField) .append("="); for (FormData.FormValue formValue : formValues) { sb.append(formValue.isFileItem() ? "[file-content]" : formValue.getValue()); sb.append("\n"); if (formValue.getHeaders() != null) { sb.append("headers=\n"); for (HeaderValues header : formValue.getHeaders()) { sb.append("\t") .append(header.getHeaderName()).append("=").append(header.getFirst()).append("\n"); } } } } } } catch (Exception e) { throw new RuntimeException(e); } } @Override public String toString() { return "dump-request()"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "dump-request"; } @Override public Map> parameters() { return Collections.emptyMap(); } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { return new Wrapper(); } } private static class Wrapper implements HandlerWrapper { @Override public HttpHandler wrap(HttpHandler handler) { return new RequestDumpingHandler(handler); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/RequestLimit.java000066400000000000000000000164331420065311100307430ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.UndertowLogger; import io.undertow.server.Connectors; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.SameThreadExecutor; import java.util.Queue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; /** * Represents a limit on a number of running requests. *

* This is basically a counter with a configured set of limits, that is used by {@link RequestLimitingHandler}. *

* When the number of active requests goes over the configured max requests then requests will be suspended and queued. *

* If the queue is full requests will be rejected with a 503 Service Unavailable according to RFC7231 Section 6.6.4. *

* The reason why this is abstracted out into a separate class is so that multiple handlers can share the same state. This * allows for fine grained control of resources. * * @author Stuart Douglas * @see RequestLimitingHandler */ public class RequestLimit { @SuppressWarnings("unused") private volatile int requests; private volatile int max; private static final AtomicIntegerFieldUpdater requestsUpdater = AtomicIntegerFieldUpdater.newUpdater(RequestLimit.class, "requests"); /** * The handler that will be invoked if the queue is full. */ private volatile HttpHandler failureHandler = new ResponseCodeHandler(503); private final Queue queue; private final ExchangeCompletionListener COMPLETION_LISTENER = new ExchangeCompletionListener() { @Override public void exchangeEvent(final HttpServerExchange exchange, final NextListener nextListener) { SuspendedRequest task = null; boolean found = false; while ((task = queue.poll()) != null) { try { task.exchange.addExchangeCompleteListener(COMPLETION_LISTENER); task.exchange.dispatch(task.next); found = true; break; } catch (Throwable e) { UndertowLogger.ROOT_LOGGER.error("Suspended request was skipped", e); } } if (!found) { decrementRequests(); } nextListener.proceed(); } }; public RequestLimit(int maximumConcurrentRequests) { this(maximumConcurrentRequests, -1); } /** * Construct a new instance. The maximum number of concurrent requests must be at least one. * * @param maximumConcurrentRequests the maximum concurrent requests * @param queueSize The maximum number of requests to queue */ public RequestLimit(int maximumConcurrentRequests, int queueSize) { if (maximumConcurrentRequests < 1) { throw new IllegalArgumentException("Maximum concurrent requests must be at least 1"); } max = maximumConcurrentRequests; this.queue = new LinkedBlockingQueue<>(queueSize <= 0 ? Integer.MAX_VALUE : queueSize); } public void handleRequest(final HttpServerExchange exchange, final HttpHandler next) throws Exception { int oldVal, newVal; do { oldVal = requests; if (oldVal >= max) { exchange.dispatch(SameThreadExecutor.INSTANCE, new Runnable() { @Override public void run() { //we have to try again in the sync block //we need to have already dispatched for thread safety reasons synchronized (RequestLimit.this) { int oldVal, newVal; do { oldVal = requests; if (oldVal >= max) { if (!queue.offer(new SuspendedRequest(exchange, next))) { Connectors.executeRootHandler(failureHandler, exchange); } return; } newVal = oldVal + 1; } while (!requestsUpdater.compareAndSet(RequestLimit.this, oldVal, newVal)); exchange.addExchangeCompleteListener(COMPLETION_LISTENER); exchange.dispatch(next); } } }); return; } newVal = oldVal + 1; } while (!requestsUpdater.compareAndSet(this, oldVal, newVal)); exchange.addExchangeCompleteListener(COMPLETION_LISTENER); next.handleRequest(exchange); } /** * Get the maximum concurrent requests. * * @return the maximum concurrent requests */ public int getMaximumConcurrentRequests() { return max; } /** * Set the maximum concurrent requests. The value must be greater than or equal to one. * * @param newMax the maximum concurrent requests */ public int setMaximumConcurrentRequests(int newMax) { if (newMax < 1) { throw new IllegalArgumentException("Maximum concurrent requests must be at least 1"); } int oldMax = this.max; this.max = newMax; if(newMax > oldMax) { synchronized (this) { while (!queue.isEmpty()) { int oldVal, newVal; do { oldVal = requests; if (oldVal >= max) { return oldMax; } newVal = oldVal + 1; } while (!requestsUpdater.compareAndSet(this, oldVal, newVal)); SuspendedRequest res = queue.poll(); res.exchange.dispatch(res.next); } } } return oldMax; } private void decrementRequests() { requestsUpdater.decrementAndGet(this); } public HttpHandler getFailureHandler() { return failureHandler; } public void setFailureHandler(HttpHandler failureHandler) { this.failureHandler = failureHandler; } private static final class SuspendedRequest { final HttpServerExchange exchange; final HttpHandler next; private SuspendedRequest(HttpServerExchange exchange, HttpHandler next) { this.exchange = exchange; this.next = next; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/RequestLimitingHandler.java000066400000000000000000000111241420065311100327270ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.util.Collections; import java.util.Map; import java.util.Set; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; /** * A handler which limits the maximum number of concurrent requests. Requests beyond the limit will * block until the previous request is complete. * * @author David M. Lloyd */ public final class RequestLimitingHandler implements HttpHandler { private final HttpHandler nextHandler; private final RequestLimit requestLimit; /** * Construct a new instance. The maximum number of concurrent requests must be at least one. The next handler * must not be {@code null}. * * @param maximumConcurrentRequests the maximum concurrent requests * @param nextHandler the next handler */ public RequestLimitingHandler(int maximumConcurrentRequests, HttpHandler nextHandler) { this(maximumConcurrentRequests, -1, nextHandler); } /** * Construct a new instance. The maximum number of concurrent requests must be at least one. The next handler * must not be {@code null}. * * @param maximumConcurrentRequests the maximum concurrent requests * @param queueSize the maximum number of requests to queue * @param nextHandler the next handler */ public RequestLimitingHandler(int maximumConcurrentRequests, int queueSize, HttpHandler nextHandler) { if (nextHandler == null) { throw new IllegalArgumentException("nextHandler is null"); } if (maximumConcurrentRequests < 1) { throw new IllegalArgumentException("Maximum concurrent requests must be at least 1"); } this.requestLimit = new RequestLimit(maximumConcurrentRequests, queueSize); this.nextHandler = nextHandler; } /** * Construct a new instance. This version takes a {@link RequestLimit} directly which may be shared with other * handlers. * * @param requestLimit the request limit information. * @param nextHandler the next handler */ public RequestLimitingHandler(RequestLimit requestLimit, HttpHandler nextHandler) { if (nextHandler == null) { throw new IllegalArgumentException("nextHandler is null"); } this.requestLimit = requestLimit; this.nextHandler = nextHandler; } public void handleRequest(final HttpServerExchange exchange) throws Exception { requestLimit.handleRequest(exchange, nextHandler); } public RequestLimit getRequestLimit() { return requestLimit; } @Override public String toString() { return "request-limit( " + requestLimit.getMaximumConcurrentRequests() + " )"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "request-limit"; } @Override public Map> parameters() { return Collections.>singletonMap("requests", int.class); } @Override public Set requiredParameters() { return Collections.singleton("requests"); } @Override public String defaultParameter() { return "requests"; } @Override public HandlerWrapper build(Map config) { return new Wrapper((Integer) config.get("requests")); } } private static class Wrapper implements HandlerWrapper { private final int requests; private Wrapper(int requests) { this.requests = requests; } @Override public HttpHandler wrap(HttpHandler handler) { return new RequestLimitingHandler(requests, handler); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/ResponseCodeHandler.java000066400000000000000000000054341420065311100322020ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.UndertowLogger; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; /** * A handler which simply sets a response code. * * @author David M. Lloyd */ public final class ResponseCodeHandler implements HttpHandler { private static final boolean debugEnabled; static { debugEnabled = UndertowLogger.PREDICATE_LOGGER.isDebugEnabled(); } /** * A handler which sets a 200 code. This is the default response code, so in most cases * this simply has the result of finishing the request */ public static final ResponseCodeHandler HANDLE_200 = new ResponseCodeHandler(200); /** * A handler which sets a 403 code. */ public static final ResponseCodeHandler HANDLE_403 = new ResponseCodeHandler(403); /** * A handler which sets a 404 code. */ public static final ResponseCodeHandler HANDLE_404 = new ResponseCodeHandler(404); /** * A handler which sets a 405 code. */ public static final ResponseCodeHandler HANDLE_405 = new ResponseCodeHandler(405); /** * A handler which sets a 406 code. */ public static final ResponseCodeHandler HANDLE_406 = new ResponseCodeHandler(406); /** * A handler which sets a 500 code. */ public static final ResponseCodeHandler HANDLE_500 = new ResponseCodeHandler(500); private final int responseCode; /** * Construct a new instance. * * @param responseCode the response code to set */ public ResponseCodeHandler(final int responseCode) { this.responseCode = responseCode; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.setStatusCode(responseCode); if(debugEnabled) { UndertowLogger.PREDICATE_LOGGER.debugf("Response code set to [%s] for %s.", responseCode, exchange); } } @Override public String toString() { return "response-code( " + this.responseCode + " )"; } } ResponseRateLimitingHandler.java000066400000000000000000000077031420065311100336420ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.conduits.RateLimitingStreamSinkConduit; import io.undertow.server.ConduitWrapper; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.ConduitFactory; import org.xnio.conduits.StreamSinkConduit; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; /** * Handler that limits the download rate * * @author Stuart Douglas */ public class ResponseRateLimitingHandler implements HttpHandler { private final long time; private final int bytes; private final HttpHandler next; private final ConduitWrapper WRAPPER = new ConduitWrapper() { @Override public StreamSinkConduit wrap(ConduitFactory factory, HttpServerExchange exchange) { return new RateLimitingStreamSinkConduit(factory.create(), bytes, time, TimeUnit.MILLISECONDS); } }; /** * * A handler that limits the download speed to a set number of bytes/period * * @param next The next handler * @param bytes The number of bytes per time period * @param time The time period * @param timeUnit The units of the time period */ public ResponseRateLimitingHandler(HttpHandler next, int bytes,long time, TimeUnit timeUnit) { this.time = timeUnit.toMillis(time); this.bytes = bytes; this.next = next; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.addResponseWrapper(WRAPPER); next.handleRequest(exchange); } @Override public String toString() { return "response-rate-limit( bytes=" + bytes + ", time=" + time + " )"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "response-rate-limit"; } @Override public Map> parameters() { Map> ret = new HashMap<>(); ret.put("bytes", Integer.class); ret.put("time", Long.class); return ret; } @Override public Set requiredParameters() { return new HashSet<>(Arrays.asList("bytes", "time")); } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { return new Wrapper((Integer)config.get("bytes"), (Long)config.get("time"), TimeUnit.MILLISECONDS); } } private static class Wrapper implements HandlerWrapper { private final long time; private final int bytes; private Wrapper(int bytes, long time, TimeUnit timeUnit) { this.time = timeUnit.toMillis(time); this.bytes = bytes; } @Override public HttpHandler wrap(HttpHandler handler) { return new ResponseRateLimitingHandler(handler, bytes, time, TimeUnit.MILLISECONDS); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/SSLHeaderHandler.java000066400000000000000000000136161420065311100313640ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.UndertowLogger; import io.undertow.server.BasicSSLSessionInfo; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.SSLSessionInfo; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.Certificates; import io.undertow.util.HeaderMap; import javax.security.cert.CertificateException; import static io.undertow.util.Headers.SSL_CIPHER; import static io.undertow.util.Headers.SSL_CIPHER_USEKEYSIZE; import static io.undertow.util.Headers.SSL_CLIENT_CERT; import static io.undertow.util.Headers.SSL_SESSION_ID; import java.util.Collections; import java.util.Map; import java.util.Set; /** * Handler that sets SSL information on the connection based on the following headers: *

*

    *
  • SSL_CLIENT_CERT
  • *
  • SSL_CIPHER
  • *
  • SSL_SESSION_ID
  • *
*

* If this handler is present in the chain it will always override the SSL session information, * even if these headers are not present. *

* This handler MUST only be used on servers that are behind a reverse proxy, where the reverse proxy * has been configured to always set these header for EVERY request (or strip existing headers with these * names if no SSL information is present). Otherwise it may be possible for a malicious client to spoof * a SSL connection. * * @author Stuart Douglas */ public class SSLHeaderHandler implements HttpHandler { public static final String HTTPS = "https"; private static final String NULL_VALUE = "(null)"; private static final ExchangeCompletionListener CLEAR_SSL_LISTENER = new ExchangeCompletionListener() { @Override public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { exchange.getConnection().setSslSessionInfo(null); nextListener.proceed(); } }; private final HttpHandler next; public SSLHeaderHandler(HttpHandler next) { this.next = next; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { HeaderMap requestHeaders = exchange.getRequestHeaders(); final String sessionId = requestHeaders.getFirst(SSL_SESSION_ID); final String cipher = requestHeaders.getFirst(SSL_CIPHER); String clientCert = requestHeaders.getFirst(SSL_CLIENT_CERT); String keySizeStr = requestHeaders.getFirst(SSL_CIPHER_USEKEYSIZE); Integer keySize = null; if (keySizeStr != null) { try { keySize = Integer.parseUnsignedInt(keySizeStr); } catch (NumberFormatException e) { UndertowLogger.REQUEST_LOGGER.debugf("Invalid SSL_CIPHER_USEKEYSIZE header %s", keySizeStr); } } if (clientCert != null || sessionId != null || cipher != null) { if (clientCert != null) { if (clientCert.isEmpty() || clientCert.equals(NULL_VALUE)) { // SSL is in place but client cert was not sent clientCert = null; } else if (clientCert.length() > 28 + 26) { // the proxy client replaces \n with ' ' StringBuilder sb = new StringBuilder(clientCert.length() + 1); sb.append(Certificates.BEGIN_CERT); sb.append('\n'); sb.append(clientCert.replace(' ', '\n').substring(28, clientCert.length() - 26));//core certificate data sb.append('\n'); sb.append(Certificates.END_CERT); clientCert = sb.toString(); } } try { SSLSessionInfo info = new BasicSSLSessionInfo(sessionId, cipher, clientCert, keySize); exchange.setRequestScheme(HTTPS); exchange.getConnection().setSslSessionInfo(info); exchange.addExchangeCompleteListener(CLEAR_SSL_LISTENER); } catch (java.security.cert.CertificateException | CertificateException e) { UndertowLogger.REQUEST_LOGGER.debugf(e, "Could not create certificate from header %s", clientCert); } } next.handleRequest(exchange); } @Override public String toString() { return "ssl-headers()"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "ssl-headers"; } @Override public Map> parameters() { return Collections.emptyMap(); } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { return new Wrapper(); } } private static class Wrapper implements HandlerWrapper { @Override public HttpHandler wrap(HttpHandler handler) { return new SSLHeaderHandler(handler); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/SameSiteCookieHandler.java000066400000000000000000000143011420065311100324460ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.ResponseCommitListener; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.Headers; import io.undertow.util.SameSiteNoneIncompatibleClientChecker; /** * Handler that will set the SameSite flag to response cookies * @author Richard Opalka */ public class SameSiteCookieHandler implements HttpHandler { private final HttpHandler next; private final String mode; private final Pattern cookiePattern; private final boolean enableClientChecker; private final boolean addSecureForNone; public SameSiteCookieHandler(final HttpHandler next, final String mode) { this(next, mode, null, true, true, true); } public SameSiteCookieHandler(final HttpHandler next, final String mode, final String cookiePattern) { this(next, mode, cookiePattern, true, true, true); } public SameSiteCookieHandler(final HttpHandler next, final String mode, final String cookiePattern, final boolean caseSensitive) { this(next, mode, cookiePattern, caseSensitive, true, true); } public SameSiteCookieHandler(final HttpHandler next, final String mode, final String cookiePattern, final boolean caseSensitive, final boolean enableClientChecker, final boolean addSecureForNone) { this.next = next; this.mode = mode; if (cookiePattern != null && !cookiePattern.isEmpty()) { this.cookiePattern = Pattern.compile(cookiePattern, caseSensitive ? 0 : Pattern.CASE_INSENSITIVE); } else { this.cookiePattern = null; } final boolean modeIsNone = CookieSameSiteMode.NONE.toString().equalsIgnoreCase(mode); this.enableClientChecker = enableClientChecker && modeIsNone; // client checker is enabled only for SameSite=None this.addSecureForNone = addSecureForNone && modeIsNone; // Add secure attribute for SameSite=None } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if (mode != null) { exchange.addResponseCommitListener(new ResponseCommitListener() { @Override public void beforeCommit(HttpServerExchange exchange) { // If user-agent is available check it and skip sending "SameSite=None" for incompatible user-agents String userAgent = exchange.getRequestHeaders().getFirst(Headers.USER_AGENT); if (enableClientChecker && userAgent != null && !SameSiteNoneIncompatibleClientChecker.shouldSendSameSiteNone(userAgent)) { return; } for (Cookie cookie : exchange.responseCookies()) { if (cookiePattern == null || cookiePattern.matcher(cookie.getName()).matches()) { // set SameSite attribute to all response cookies when cookie pattern is not specified. // or, set SameSite attribute if cookie name matches the specified cookie pattern. cookie.setSameSiteMode(mode); if (addSecureForNone) { // Add secure attribute for "SameSite=None" cookie.setSecure(true); } } } } }); } next.handleRequest(exchange); } public static class Builder implements HandlerBuilder { @Override public String name() { return "samesite-cookie"; } @Override public Map> parameters() { Map> parameters = new HashMap<>(); parameters.put("mode", String.class); parameters.put("cookie-pattern", String.class); parameters.put("case-sensitive", Boolean.class); parameters.put("enable-client-checker", Boolean.class); parameters.put("add-secure-for-none", Boolean.class); return parameters; } @Override public Set requiredParameters() { return Collections.singleton("mode"); } @Override public String defaultParameter() { return "mode"; } @Override public HandlerWrapper build(Map config) { final String mode = (String) config.get("mode"); final String pattern = (String) config.get("cookie-pattern"); final Boolean caseSensitive = (Boolean) config.get("case-sensitive"); final Boolean enableClientChecker = (Boolean) config.get("enable-client-checker"); final Boolean addSecureForNone = (Boolean) config.get("add-secure-for-none"); return new HandlerWrapper() { @Override public HttpHandler wrap(HttpHandler handler) { return new SameSiteCookieHandler(handler, mode, pattern, caseSensitive == null ? true : caseSensitive, enableClientChecker == null ? true : enableClientChecker, addSecureForNone == null ? true : addSecureForNone); } }; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/SecureCookieHandler.java000066400000000000000000000047701420065311100321730ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.util.Collections; import java.util.Map; import java.util.Set; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.SecureCookieCommitListener; import io.undertow.server.handlers.builder.HandlerBuilder; /** * Handler that will set the secure flag on all cookies that are received over a secure connection */ public class SecureCookieHandler implements HttpHandler { public static final HandlerWrapper WRAPPER = new HandlerWrapper() { @Override public HttpHandler wrap(HttpHandler handler) { return new SecureCookieHandler(handler); } }; private final HttpHandler next; public SecureCookieHandler(HttpHandler next) { this.next = next; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if(exchange.isSecure()) { exchange.addResponseCommitListener(SecureCookieCommitListener.INSTANCE); } next.handleRequest(exchange); } @Override public String toString() { return "secure-cookie()"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "secure-cookie"; } @Override public Map> parameters() { return Collections.emptyMap(); } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { return WRAPPER; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/SetAttributeHandler.java000066400000000000000000000163731420065311100322340ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributeParser; import io.undertow.attribute.ExchangeAttributes; import io.undertow.attribute.NullAttribute; import io.undertow.attribute.ReadOnlyAttributeException; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.ResponseCommitListener; import io.undertow.server.handlers.builder.HandlerBuilder; /** * Handler that can set an arbitrary attribute on the exchange. Both the attribute and the * value to set are expressed as exchange attributes. * * * @author Stuart Douglas */ public class SetAttributeHandler implements HttpHandler { private final HttpHandler next; private final ExchangeAttribute attribute; private final ExchangeAttribute value; private final boolean preCommit; public SetAttributeHandler(HttpHandler next, ExchangeAttribute attribute, ExchangeAttribute value) { this(next, attribute, value, false); } public SetAttributeHandler(HttpHandler next, final String attribute, final String value) { this.next = next; ExchangeAttributeParser parser = ExchangeAttributes.parser(getClass().getClassLoader()); this.attribute = parser.parseSingleToken(attribute); this.value = parser.parse(value); this.preCommit = false; } public SetAttributeHandler(HttpHandler next, final String attribute, final String value, final ClassLoader classLoader) { this.next = next; ExchangeAttributeParser parser = ExchangeAttributes.parser(classLoader); this.attribute = parser.parseSingleToken(attribute); this.value = parser.parse(value); this.preCommit = false; } public SetAttributeHandler(HttpHandler next, ExchangeAttribute attribute, ExchangeAttribute value, boolean preCommit) { this.next = next; this.attribute = attribute; this.value = value; this.preCommit = preCommit; } public SetAttributeHandler(HttpHandler next, final String attribute, final String value, boolean preCommit) { this.next = next; this.preCommit = preCommit; ExchangeAttributeParser parser = ExchangeAttributes.parser(getClass().getClassLoader()); this.attribute = parser.parseSingleToken(attribute); this.value = parser.parse(value); } public SetAttributeHandler(HttpHandler next, final String attribute, final String value, final ClassLoader classLoader, boolean preCommit) { this.next = next; this.preCommit = preCommit; ExchangeAttributeParser parser = ExchangeAttributes.parser(classLoader); this.attribute = parser.parseSingleToken(attribute); this.value = parser.parse(value); } public ExchangeAttribute getValue() { return value; } @Override public String toString() { return "set( attribute='" + attribute.toString() + "', value='" + value.toString() + "' )"; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if(preCommit) { exchange.addResponseCommitListener(new ResponseCommitListener() { @Override public void beforeCommit(HttpServerExchange exchange) { try { attribute.writeAttribute(exchange, value.readAttribute(exchange)); } catch (ReadOnlyAttributeException e) { throw new RuntimeException(e); } } }); } else { attribute.writeAttribute(exchange, value.readAttribute(exchange)); } next.handleRequest(exchange); } public static class Builder implements HandlerBuilder { @Override public String name() { return "set"; } @Override public Map> parameters() { Map> parameters = new HashMap<>(); parameters.put("value", ExchangeAttribute.class); parameters.put("attribute", ExchangeAttribute.class); parameters.put("pre-commit", Boolean.class); return parameters; } @Override public Set requiredParameters() { final Set req = new HashSet<>(); req.add("value"); req.add("attribute"); return req; } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(final Map config) { final ExchangeAttribute value = (ExchangeAttribute) config.get("value"); final ExchangeAttribute attribute = (ExchangeAttribute) config.get("attribute"); final Boolean preCommit = (Boolean) config.get("pre-commit"); return new HandlerWrapper() { @Override public HttpHandler wrap(HttpHandler handler) { return new SetAttributeHandler(handler, attribute, value, preCommit == null ? false : preCommit); } }; } } public static class ClearBuilder implements HandlerBuilder { @Override public String name() { return "clear"; } @Override public Map> parameters() { Map> parameters = new HashMap<>(); parameters.put("attribute", ExchangeAttribute.class); parameters.put("pre-commit", Boolean.class); return parameters; } @Override public Set requiredParameters() { final Set req = new HashSet<>(); req.add("attribute"); return req; } @Override public String defaultParameter() { return "attribute"; } @Override public HandlerWrapper build(final Map config) { final ExchangeAttribute attribute = (ExchangeAttribute) config.get("attribute"); final Boolean preCommit = (Boolean) config.get("pre-commit"); return new HandlerWrapper() { @Override public HttpHandler wrap(HttpHandler handler) { return new SetAttributeHandler(handler, attribute, NullAttribute.INSTANCE, preCommit == null ? false : preCommit); } }; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/SetErrorHandler.java000066400000000000000000000060031420065311100313470ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * A handler that sets response code but continues the exchange so the servlet's * error page can be returned. * * @author Brad Wood */ public class SetErrorHandler implements HttpHandler { private final int responseCode; private final HttpHandler next; /** * Construct a new instance. * * @param responseCode the response code to set */ public SetErrorHandler(HttpHandler next, final int responseCode) { this.next = next; this.responseCode = responseCode; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.setStatusCode(responseCode); next.handleRequest(exchange); } @Override public String toString() { return "set-error( " + responseCode + " )"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "set-error"; } @Override public Map> parameters() { Map> params = new HashMap<>(); params.put("response-code", Integer.class); return params; } @Override public Set requiredParameters() { final Set req = new HashSet<>(); req.add("response-code"); return req; } @Override public String defaultParameter() { return "response-code"; } @Override public HandlerWrapper build(Map config) { return new Wrapper((Integer) config.get("response-code")); } } private static class Wrapper implements HandlerWrapper { private final Integer responseCode; private Wrapper(Integer responseCode) { this.responseCode = responseCode; } @Override public HttpHandler wrap(HttpHandler handler) { return new SetErrorHandler(handler, responseCode); } } }undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/SetHeaderHandler.java000066400000000000000000000112631420065311100314520ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.UndertowMessages; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributes; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.HttpString; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Set a fixed response header. * * @author Stuart Douglas */ public class SetHeaderHandler implements HttpHandler { private final HttpString header; private final ExchangeAttribute value; private final HttpHandler next; public SetHeaderHandler(final String header, final String value) { if(value == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("value"); } if(header == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("header"); } this.next = ResponseCodeHandler.HANDLE_404; this.value = ExchangeAttributes.constant(value); this.header = new HttpString(header); } public SetHeaderHandler(final HttpHandler next, final String header, final ExchangeAttribute value) { if(value == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("value"); } if(header == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("header"); } if(next == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("next"); } this.next = next; this.value = value; this.header = new HttpString(header); } public SetHeaderHandler(final HttpHandler next, final String header, final String value) { if(value == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("value"); } if(header == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("header"); } if(next == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("next"); } this.next = next; this.value = ExchangeAttributes.constant(value); this.header = new HttpString(header); } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getResponseHeaders().put(header, value.readAttribute(exchange)); next.handleRequest(exchange); } public ExchangeAttribute getValue() { return value; } public HttpString getHeader() { return header; } @Override public String toString() { return "set( header='" + header.toString() + "', value='" + value.toString() + "' )"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "header"; } @Override public Map> parameters() { Map> parameters = new HashMap<>(); parameters.put("header", String.class); parameters.put("value", ExchangeAttribute.class); return parameters; } @Override public Set requiredParameters() { final Set req = new HashSet<>(); req.add("value"); req.add("header"); return req; } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(final Map config) { final ExchangeAttribute value = (ExchangeAttribute) config.get("value"); final String header = (String) config.get("header"); return new HandlerWrapper() { @Override public HttpHandler wrap(HttpHandler handler) { return new SetHeaderHandler(handler, header, value); } }; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/StoredResponseHandler.java000066400000000000000000000060461420065311100325700ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.util.Collections; import java.util.Map; import java.util.Set; import org.xnio.conduits.StreamSinkConduit; import io.undertow.conduits.StoredResponseStreamSinkConduit; import io.undertow.server.ConduitWrapper; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.ConduitFactory; /** * A handler that buffers the full response and attaches it to the exchange. This makes use of * {@link StoredResponseStreamSinkConduit} *

* This will be made available once the response is fully complete, so should generally * be read in an {@link io.undertow.server.ExchangeCompletionListener} * * @author Stuart Douglas */ public class StoredResponseHandler implements HttpHandler { private final HttpHandler next; public StoredResponseHandler(HttpHandler next) { this.next = next; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.addResponseWrapper(new ConduitWrapper() { @Override public StreamSinkConduit wrap(ConduitFactory factory, HttpServerExchange exchange) { return new StoredResponseStreamSinkConduit(factory.create(), exchange); } }); next.handleRequest(exchange); } @Override public String toString() { return "store-response()"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "store-response"; } @Override public Map> parameters() { return Collections.emptyMap(); } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { return new HandlerWrapper() { @Override public HttpHandler wrap(HttpHandler handler) { return new StoredResponseHandler(handler); } }; } } } StuckThreadDetectionHandler.java000066400000000000000000000252341420065311100336120ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/* * 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. */ package io.undertow.server.handlers; import io.undertow.UndertowLogger; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.WorkerUtils; import org.xnio.XnioExecutor; import org.xnio.XnioIoThread; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * This valve allows to detect requests that take a long time to process, which might * indicate that the thread that is processing it is stuck. * Based on code proposed by TomLu in Bugzilla entry #50306 * * @author slaurent * */ public class StuckThreadDetectionHandler implements HttpHandler { public static final int DEFAULT_THRESHOLD = 600; /** * Keeps count of the number of stuck threads detected */ private final AtomicInteger stuckCount = new AtomicInteger(0); /** * In seconds. Default 600 (10 minutes). */ private final int threshold; /** * The only references we keep to actual running Thread objects are in * this Map (which is automatically cleaned in invoke()s finally clause). * That way, Threads can be GC'ed, eventhough the Valve still thinks they * are stuck (caused by a long monitor interval) */ private final ConcurrentHashMap activeThreads = new ConcurrentHashMap(); /** * */ private final Queue completedStuckThreadsQueue = new ConcurrentLinkedQueue<>(); private final HttpHandler next; private final Runnable stuckThreadTask = new Runnable() { @Override public void run() { long thresholdInMillis = threshold * 1000L; // Check monitored threads, being careful that the request might have // completed by the time we examine it for (MonitoredThread monitoredThread : activeThreads.values()) { long activeTime = monitoredThread.getActiveTimeInMillis(); if (activeTime >= thresholdInMillis && monitoredThread.markAsStuckIfStillRunning()) { int numStuckThreads = stuckCount.incrementAndGet(); notifyStuckThreadDetected(monitoredThread, activeTime, numStuckThreads); } } // Check if any threads previously reported as stuck, have finished. for (CompletedStuckThread completedStuckThread = completedStuckThreadsQueue.poll(); completedStuckThread != null; completedStuckThread = completedStuckThreadsQueue.poll()) { int numStuckThreads = stuckCount.decrementAndGet(); notifyStuckThreadCompleted(completedStuckThread, numStuckThreads); } synchronized (StuckThreadDetectionHandler.this) { if(activeThreads.isEmpty()) { timerKey = null; } else { timerKey = WorkerUtils.executeAfter(((XnioIoThread)Thread.currentThread()), stuckThreadTask, 1, TimeUnit.SECONDS); } } } }; private volatile XnioExecutor.Key timerKey; public StuckThreadDetectionHandler(HttpHandler next) { this(DEFAULT_THRESHOLD, next); } public StuckThreadDetectionHandler(int threshold, HttpHandler next) { this.threshold = threshold; this.next = next; } /** * @return The current threshold in seconds */ public int getThreshold() { return threshold; } private void notifyStuckThreadDetected(MonitoredThread monitoredThread, long activeTime, int numStuckThreads) { Throwable th = new Throwable(); th.setStackTrace(monitoredThread.getThread().getStackTrace()); UndertowLogger.REQUEST_LOGGER.stuckThreadDetected (monitoredThread.getThread().getName(), monitoredThread.getThread().getId(), activeTime, monitoredThread.getStartTime(), monitoredThread.getRequestUri(), threshold, numStuckThreads, th); } private void notifyStuckThreadCompleted(CompletedStuckThread thread, int numStuckThreads) { UndertowLogger.REQUEST_LOGGER.stuckThreadCompleted (thread.getName(), thread.getId(), thread.getTotalActiveTime(), numStuckThreads); } /** * {@inheritDoc} */ @Override public void handleRequest(HttpServerExchange exchange) throws Exception { // Save the thread/runnable // Keeping a reference to the thread object here does not prevent // GC'ing, as the reference is removed from the Map in the finally clause Long key = Thread.currentThread().getId(); MonitoredThread monitoredThread = new MonitoredThread(Thread.currentThread(), exchange.getRequestURI() + exchange.getQueryString()); activeThreads.put(key, monitoredThread); if(timerKey == null) { synchronized (this) { if(timerKey == null) { timerKey = exchange.getIoThread().executeAfter(stuckThreadTask, 1, TimeUnit.SECONDS); } } } try { next.handleRequest(exchange); } finally { activeThreads.remove(key); if (monitoredThread.markAsDone() == MonitoredThreadState.STUCK) { completedStuckThreadsQueue.add( new CompletedStuckThread(monitoredThread.getThread(), monitoredThread.getActiveTimeInMillis())); } } } public long[] getStuckThreadIds() { List idList = new ArrayList<>(); for (MonitoredThread monitoredThread : activeThreads.values()) { if (monitoredThread.isMarkedAsStuck()) { idList.add(Long.valueOf(monitoredThread.getThread().getId())); } } long[] result = new long[idList.size()]; for (int i = 0; i < result.length; i++) { result[i] = idList.get(i).longValue(); } return result; } private static class MonitoredThread { /** * Reference to the thread to get a stack trace from background task */ private final Thread thread; private final String requestUri; private final long start; private final AtomicInteger state = new AtomicInteger( MonitoredThreadState.RUNNING.ordinal()); MonitoredThread(Thread thread, String requestUri) { this.thread = thread; this.requestUri = requestUri; this.start = System.currentTimeMillis(); } public Thread getThread() { return this.thread; } public String getRequestUri() { return requestUri; } public long getActiveTimeInMillis() { return System.currentTimeMillis() - start; } public Date getStartTime() { return new Date(start); } public boolean markAsStuckIfStillRunning() { return this.state.compareAndSet(MonitoredThreadState.RUNNING.ordinal(), MonitoredThreadState.STUCK.ordinal()); } public MonitoredThreadState markAsDone() { int val = this.state.getAndSet(MonitoredThreadState.DONE.ordinal()); return MonitoredThreadState.values()[val]; } boolean isMarkedAsStuck() { return this.state.get() == MonitoredThreadState.STUCK.ordinal(); } } private static class CompletedStuckThread { private final String threadName; private final long threadId; private final long totalActiveTime; CompletedStuckThread(Thread thread, long totalActiveTime) { this.threadName = thread.getName(); this.threadId = thread.getId(); this.totalActiveTime = totalActiveTime; } public String getName() { return this.threadName; } public long getId() { return this.threadId; } public long getTotalActiveTime() { return this.totalActiveTime; } } private enum MonitoredThreadState { RUNNING, STUCK, DONE; } public static final class Wrapper implements HandlerWrapper { private final int threshhold; public Wrapper(int threshhold) { this.threshhold = threshhold; } public Wrapper() { this.threshhold = DEFAULT_THRESHOLD; } @Override public HttpHandler wrap(HttpHandler handler) { return new StuckThreadDetectionHandler(threshhold, handler); } } @Override public String toString() { return "stuck-thread-detector( " + threshold + " )"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "stuck-thread-detector"; } @Override public Map> parameters() { return Collections.>singletonMap("threshhold", Integer.class); } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return "threshhold"; } @Override public HandlerWrapper build(Map config) { Integer threshhold = (Integer) config.get("threshhold"); if(threshhold == null) { return new Wrapper(); } else { return new Wrapper(threshhold); } } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/URLDecodingHandler.java000066400000000000000000000150751420065311100317120ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.util.ArrayDeque; import java.util.Collections; import java.util.Deque; import java.util.Map; import java.util.Set; import java.util.TreeMap; import io.undertow.UndertowOptions; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.AttachmentKey; import io.undertow.util.PathTemplateMatch; import io.undertow.util.URLUtils; /** * A handler that will decode the URL and query parameters to the specified charset. *

* This handler will not have any effect unless the {@link io.undertow.UndertowOptions#DECODE_URL} parameter is set to false. *

* This is not as efficient as using the parsers built in UTF-8 decoder. Unless you need to decode to something other * than UTF-8 you should rely on the parsers decoding instead. * * @author Stuart Douglas */ public class URLDecodingHandler implements HttpHandler { private static final ThreadLocal DECODING_BUFFER_CACHE = ThreadLocal.withInitial(StringBuilder::new); private static final AttachmentKey ALREADY_DECODED = AttachmentKey.create(Object.class); private final HttpHandler next; private final String charset; public URLDecodingHandler(final HttpHandler next, final String charset) { this.next = next; this.charset = charset; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { if (shouldDecode(exchange)) { final StringBuilder sb = getStringBuilderForDecoding(exchange); decodePath(exchange, charset, sb); decodeQueryString(exchange, charset, sb); decodePathTemplateMatch(exchange, charset, sb); } next.handleRequest(exchange); } // Returns true if the exchange should be decoded. This method updates the ALREADY_DECODED // attachment so that subsequent invocations will always return false. private static boolean shouldDecode(final HttpServerExchange exchange) { return !exchange.getConnection().getUndertowOptions().get(UndertowOptions.DECODE_URL, true) && exchange.putAttachment(ALREADY_DECODED, Boolean.TRUE) == null; } private static void decodePath(HttpServerExchange exchange, String charset, StringBuilder sb) { final boolean decodeSlash = exchange.getConnection().getUndertowOptions().get(UndertowOptions.ALLOW_ENCODED_SLASH, false); exchange.setRequestPath(URLUtils.decode(exchange.getRequestPath(), charset, decodeSlash, false, sb)); exchange.setRelativePath(URLUtils.decode(exchange.getRelativePath(), charset, decodeSlash, false, sb)); exchange.setResolvedPath(URLUtils.decode(exchange.getResolvedPath(), charset, decodeSlash, false, sb)); } private static void decodeQueryString(HttpServerExchange exchange, String charset, StringBuilder sb) { if (!exchange.getQueryString().isEmpty()) { final TreeMap> newParams = new TreeMap<>(); for (Map.Entry> param : exchange.getQueryParameters().entrySet()) { final Deque newValues = new ArrayDeque<>(param.getValue().size()); for (String val : param.getValue()) { newValues.add(URLUtils.decode(val, charset, true, true, sb)); } newParams.put(URLUtils.decode(param.getKey(), charset, true, true, sb), newValues); } exchange.getQueryParameters().clear(); exchange.getQueryParameters().putAll(newParams); } } private static void decodePathTemplateMatch(HttpServerExchange exchange, String charset, StringBuilder sb) { PathTemplateMatch pathTemplateMatch = exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY); if (pathTemplateMatch != null) { Map parameters = pathTemplateMatch.getParameters(); if (parameters != null) { for (Map.Entry entry : parameters.entrySet()) { entry.setValue(URLUtils.decode(entry.getValue(), charset, true, false, sb)); } } } } private static StringBuilder getStringBuilderForDecoding(HttpServerExchange exchange) { if (exchange.isInIoThread()) { // Unnecessary to clear the buffer here, URLUtils.decode updates the buffer length before usage. // We don't need to check the size after use because decoded size is bounded to the request line, // which cannot exceed one buffer. return DECODING_BUFFER_CACHE.get(); } return new StringBuilder(); } @Override public String toString() { return "url-decoding( " + charset + " )"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "url-decoding"; } @Override public Map> parameters() { return Collections.>singletonMap("charset", String.class); } @Override public Set requiredParameters() { return Collections.singleton("charset"); } @Override public String defaultParameter() { return "charset"; } @Override public HandlerWrapper build(Map config) { return new Wrapper(config.get("charset").toString()); } } private static class Wrapper implements HandlerWrapper { private final String charset; private Wrapper(String charset) { this.charset = charset; } @Override public HttpHandler wrap(HttpHandler handler) { return new URLDecodingHandler(handler, charset); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/accesslog/000077500000000000000000000000001420065311100274055ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/accesslog/AccessLogHandler.java000066400000000000000000000210361420065311100334130ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.accesslog; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributes; import io.undertow.attribute.SubstituteEmptyWrapper; import io.undertow.predicate.Predicate; import io.undertow.predicate.Predicates; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; /** * Access log handler. This handler will generate access log messages based on the provided format string, * and pass these messages into the provided {@link AccessLogReceiver}. *

* This handler can log any attribute that is provides via the {@link io.undertow.attribute.ExchangeAttribute} * mechanism. A general guide to the most common attribute is provided before, however this mechanism is extensible. *

*

*

This factory produces token handlers for the following patterns

*
    *
  • %a - Remote IP address *
  • %A - Local IP address *
  • %b - Bytes sent, excluding HTTP headers, or '-' if no bytes * were sent *
  • %B - Bytes sent, excluding HTTP headers *
  • %h - Remote host name *
  • %H - Request protocol *
  • %l - Remote logical username from identd (always returns '-') *
  • %m - Request method *
  • %o - Obfuscated remote IP address (IPv4: last byte removed, * IPv6: cut off after second colon, ie. '1.2.3.' or 'fe08:44:') *
  • %p - Local port *
  • %q - Query string (excluding the '?' character) *
  • %r - First line of the request *
  • %s - HTTP status code of the response *
  • %t - Date and time, in Common Log Format format *
  • %u - Remote user that was authenticated *
  • %U - Requested URL path *
  • %v - Local server name *
  • %D - Time taken to process the request, in millis *
  • %T - Time taken to process the request, in seconds *
  • %I - current Request thread name (can compare later with stacktraces) *
*

In addition, the caller can specify one of the following aliases for * commonly utilized patterns:

*
    *
  • common - %h %l %u %t "%r" %s %b *
  • combined - * %h %l %u %t "%r" %s %b "%{i,Referer}" "%{i,User-Agent}" *
  • commonobf - %o %l %u %t "%r" %s %b *
  • combinedobf - * %o %l %u %t "%r" %s %b "%{i,Referer}" "%{i,User-Agent}" *
*

*

* There is also support to write information from the cookie, incoming * header, or the session
* It is modeled after the apache syntax: *

    *
  • %{i,xxx} for incoming headers *
  • %{o,xxx} for outgoing response headers *
  • %{c,xxx} for a specific cookie *
  • %{r,xxx} xxx is an attribute in the ServletRequest *
  • %{s,xxx} xxx is an attribute in the HttpSession *
* * @author Stuart Douglas */ public class AccessLogHandler implements HttpHandler { private final HttpHandler next; private final AccessLogReceiver accessLogReceiver; private final String formatString; private final ExchangeAttribute tokens; private final ExchangeCompletionListener exchangeCompletionListener = new AccessLogCompletionListener(); private final Predicate predicate; public AccessLogHandler(final HttpHandler next, final AccessLogReceiver accessLogReceiver, final String formatString, ClassLoader classLoader) { this(next, accessLogReceiver, formatString, classLoader, Predicates.truePredicate()); } public AccessLogHandler(final HttpHandler next, final AccessLogReceiver accessLogReceiver, final String formatString, ClassLoader classLoader, Predicate predicate) { this.next = next; this.accessLogReceiver = accessLogReceiver; this.predicate = predicate; this.formatString = handleCommonNames(formatString); this.tokens = ExchangeAttributes.parser(classLoader, new SubstituteEmptyWrapper("-")).parse(this.formatString); } public AccessLogHandler(final HttpHandler next, final AccessLogReceiver accessLogReceiver, String formatString, final ExchangeAttribute attribute) { this(next, accessLogReceiver, formatString, attribute, Predicates.truePredicate()); } public AccessLogHandler(final HttpHandler next, final AccessLogReceiver accessLogReceiver, String formatString, final ExchangeAttribute attribute, Predicate predicate) { this.next = next; this.accessLogReceiver = accessLogReceiver; this.predicate = predicate; this.formatString = handleCommonNames(formatString); this.tokens = attribute; } private static String handleCommonNames(String formatString) { if(formatString.equals("common")) { return "%h %l %u %t \"%r\" %s %b"; } else if (formatString.equals("combined")) { return "%h %l %u %t \"%r\" %s %b \"%{i,Referer}\" \"%{i,User-Agent}\""; } else if(formatString.equals("commonobf")) { return "%o %l %u %t \"%r\" %s %b"; } else if (formatString.equals("combinedobf")) { return "%o %l %u %t \"%r\" %s %b \"%{i,Referer}\" \"%{i,User-Agent}\""; } return formatString; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.addExchangeCompleteListener(exchangeCompletionListener); next.handleRequest(exchange); } private class AccessLogCompletionListener implements ExchangeCompletionListener { @Override public void exchangeEvent(final HttpServerExchange exchange, final NextListener nextListener) { try { if(predicate == null || predicate.resolve(exchange)) { accessLogReceiver.logMessage(tokens.readAttribute(exchange)); } } finally { nextListener.proceed(); } } } @Override public String toString() { return "AccessLogHandler{" + "formatString='" + formatString + '\'' + '}'; } public static class Builder implements HandlerBuilder { @Override public String name() { return "access-log"; } @Override public Map> parameters() { Map> params = new HashMap<>(); params.put("format", String.class); params.put("category", String.class); return params; } @Override public Set requiredParameters() { return Collections.singleton("format"); } @Override public String defaultParameter() { return "format"; } @Override public HandlerWrapper build(Map config) { return new Wrapper((String) config.get("format"), (String) config.get("category")); } } private static class Wrapper implements HandlerWrapper { private final String format; private final String category; private Wrapper(String format, String category) { this.format = format; this.category = category; } @Override public HttpHandler wrap(HttpHandler handler) { if (category == null || category.trim().isEmpty()) { return new AccessLogHandler(handler, new JBossLoggingAccessLogReceiver(), format, Wrapper.class.getClassLoader()); } else { return new AccessLogHandler(handler, new JBossLoggingAccessLogReceiver(category), format, Wrapper.class.getClassLoader()); } } } } AccessLogReceiver.java000066400000000000000000000020111420065311100335130ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/accesslog/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.accesslog; /** * Interface that is used by the access log handler to send data to the log file manager. * * Implementations of this interface must be thread safe. * * @author Stuart Douglas */ public interface AccessLogReceiver { void logMessage(final String message); } DefaultAccessLogReceiver.java000066400000000000000000000345011420065311100350310ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/accesslog/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.accesslog; import java.io.BufferedWriter; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.Deque; import java.util.List; import java.util.Locale; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import io.undertow.UndertowLogger; /** * Log Receiver that stores logs in a directory under the specified file name, and rotates them after * midnight. *

* Web threads do not touch the log file, but simply queue messages to be written later by a worker thread. * A lightweight CAS based locking mechanism is used to ensure than only 1 thread is active writing messages at * any given time * * @author Stuart Douglas */ public class DefaultAccessLogReceiver implements AccessLogReceiver, Runnable, Closeable { private static final String DEFAULT_LOG_SUFFIX = "log"; private final Executor logWriteExecutor; private final Deque pendingMessages; //0 = not running //1 = queued //2 = running //3 = final state of running (inside finally of run()) @SuppressWarnings("unused") private volatile int state = 0; private static final AtomicIntegerFieldUpdater stateUpdater = AtomicIntegerFieldUpdater.newUpdater(DefaultAccessLogReceiver.class, "state"); private long changeOverPoint; private String currentDateString; private boolean forceLogRotation; private final Path outputDirectory; private final Path defaultLogFile; private final String logBaseName; private final String logNameSuffix; private BufferedWriter writer = null; private volatile boolean closed = false; private boolean initialRun = true; private final boolean rotate; private final LogFileHeaderGenerator fileHeaderGenerator; public DefaultAccessLogReceiver(final Executor logWriteExecutor, final File outputDirectory, final String logBaseName) { this(logWriteExecutor, outputDirectory.toPath(), logBaseName, null); } public DefaultAccessLogReceiver(final Executor logWriteExecutor, final File outputDirectory, final String logBaseName, final String logNameSuffix) { this(logWriteExecutor, outputDirectory.toPath(), logBaseName, logNameSuffix, true); } public DefaultAccessLogReceiver(final Executor logWriteExecutor, final File outputDirectory, final String logBaseName, final String logNameSuffix, boolean rotate) { this(logWriteExecutor, outputDirectory.toPath(), logBaseName, logNameSuffix, rotate); } public DefaultAccessLogReceiver(final Executor logWriteExecutor, final Path outputDirectory, final String logBaseName) { this(logWriteExecutor, outputDirectory, logBaseName, null); } public DefaultAccessLogReceiver(final Executor logWriteExecutor, final Path outputDirectory, final String logBaseName, final String logNameSuffix) { this(logWriteExecutor, outputDirectory, logBaseName, logNameSuffix, true); } public DefaultAccessLogReceiver(final Executor logWriteExecutor, final Path outputDirectory, final String logBaseName, final String logNameSuffix, boolean rotate) { this(logWriteExecutor, outputDirectory, logBaseName, logNameSuffix, rotate, null); } private DefaultAccessLogReceiver(final Executor logWriteExecutor, final Path outputDirectory, final String logBaseName, final String logNameSuffix, boolean rotate, LogFileHeaderGenerator fileHeader) { this.logWriteExecutor = logWriteExecutor; this.outputDirectory = outputDirectory; this.logBaseName = logBaseName; this.rotate = rotate; this.fileHeaderGenerator = fileHeader; this.logNameSuffix = (logNameSuffix != null) ? logNameSuffix : DEFAULT_LOG_SUFFIX; this.pendingMessages = new ConcurrentLinkedDeque<>(); this.defaultLogFile = outputDirectory.resolve(logBaseName + this.logNameSuffix); calculateChangeOverPoint(); } private void calculateChangeOverPoint() { Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.add(Calendar.DATE, 1); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd", Locale.US); currentDateString = df.format(new Date()); // if there is an existing default log file, use the date last modified instead of the current date if (Files.exists(defaultLogFile)) { try { currentDateString = df.format(new Date(Files.getLastModifiedTime(defaultLogFile).toMillis())); } catch(IOException e){ // ignore. use the current date if exception happens. } } changeOverPoint = calendar.getTimeInMillis(); } @Override public void logMessage(final String message) { this.pendingMessages.add(message); int state = stateUpdater.get(this); if (state == 0) { if (stateUpdater.compareAndSet(this, 0, 1)) { logWriteExecutor.execute(this); } } } /** * processes all queued log messages */ @Override public void run() { if (!stateUpdater.compareAndSet(this, 1, 2)) { return; } if (forceLogRotation) { doRotate(); } else if (initialRun && Files.exists(defaultLogFile)) { //if there is an existing log file check if it should be rotated long lm = 0; try { lm = Files.getLastModifiedTime(defaultLogFile).toMillis(); } catch (IOException e) { UndertowLogger.ROOT_LOGGER.errorRotatingAccessLog(e); } Calendar c = Calendar.getInstance(); c.setTimeInMillis(changeOverPoint); c.add(Calendar.DATE, -1); if (lm <= c.getTimeInMillis()) { doRotate(); } } initialRun = false; List messages = new ArrayList<>(); String msg; //only grab at most 1000 messages at a time for (int i = 0; i < 1000; ++i) { msg = pendingMessages.poll(); if (msg == null) { break; } messages.add(msg); } try { if (!messages.isEmpty()) { writeMessage(messages); } } finally { // change this state to final state stateUpdater.set(this, 3); //check to see if there is still more messages //if so then run this again if (!pendingMessages.isEmpty() || forceLogRotation) { if (stateUpdater.compareAndSet(this, 3, 1)) { logWriteExecutor.execute(this); } } // Check the state before resetting the state to 0 (not running) and checking if a writer needs to be closed: // - If state != 3 here, another thread is executing this. // The other thread will visit here and will check if a writer needs to be closed. // We can leave state and skip closing a writer. // - If state == 3 here, there is no another thread executing this. // So, update the state to 0 (not running) and check if a writer needs be closed. if (stateUpdater.compareAndSet(this, 3, 0) && closed) { // As close() can be invoked from another thread in parallel, // it will dispatch a new thread to close writer if state == 0 (not running) at that moment. // So, just in case, check the state again: // - if state != 0, another thread has already dispatched from close() and it will visit here. So, closing writer can be skipped here. // - if state == 0, writer can be closed here. Let's change state to 3 again in order to prevent close() from dispatching a new thread. if (stateUpdater.compareAndSet(this, 0, 3)) { try { if(writer != null) { writer.flush(); writer.close(); writer = null; } } catch (IOException e) { UndertowLogger.ROOT_LOGGER.errorWritingAccessLog(e); } finally { // reset the state to 0 again finally stateUpdater.set(this, 0); } } } } } /** * For tests only. Blocks the current thread until all messages are written * Just does a busy wait. *

* DO NOT USE THIS OUTSIDE OF A TEST */ void awaitWrittenForTest() throws InterruptedException { while (!pendingMessages.isEmpty() || forceLogRotation) { Thread.sleep(10); } while (state != 0) { Thread.sleep(10); } } private void writeMessage(final List messages) { if (System.currentTimeMillis() > changeOverPoint) { doRotate(); } try { if (writer == null) { writer = Files.newBufferedWriter(defaultLogFile, StandardCharsets.UTF_8, StandardOpenOption.APPEND, StandardOpenOption.CREATE); if(Files.size(defaultLogFile) == 0 && fileHeaderGenerator != null) { String header = fileHeaderGenerator.generateHeader(); if(header != null) { writer.write(header); writer.newLine(); writer.flush(); } } } for (String message : messages) { writer.write(message); writer.newLine(); } writer.flush(); } catch (IOException e) { UndertowLogger.ROOT_LOGGER.errorWritingAccessLog(e); } } private void doRotate() { forceLogRotation = false; if (!rotate) { return; } try { if (writer != null) { writer.flush(); writer.close(); writer = null; } if (!Files.exists(defaultLogFile)) { return; } Path newFile = outputDirectory.resolve(logBaseName + currentDateString + "." + logNameSuffix); int count = 0; while (Files.exists(newFile)) { ++count; newFile = outputDirectory.resolve(logBaseName + currentDateString + "-" + count + "." + logNameSuffix); } Files.move(defaultLogFile, newFile); } catch (IOException e) { UndertowLogger.ROOT_LOGGER.errorRotatingAccessLog(e); } finally { calculateChangeOverPoint(); } } /** * forces a log rotation. This rotation is performed in an async manner, you cannot rely on the rotation * being performed immediately after this method returns. */ public void rotate() { forceLogRotation = true; if (stateUpdater.compareAndSet(this, 0, 1)) { logWriteExecutor.execute(this); } } @Override public void close() throws IOException { closed = true; if (stateUpdater.compareAndSet(this, 0, 1)) { logWriteExecutor.execute(this); } } public static Builder builder() { return new Builder(); } public static class Builder { private Executor logWriteExecutor; private Path outputDirectory; private String logBaseName; private String logNameSuffix; private boolean rotate; private LogFileHeaderGenerator logFileHeaderGenerator; public Executor getLogWriteExecutor() { return logWriteExecutor; } public Builder setLogWriteExecutor(Executor logWriteExecutor) { this.logWriteExecutor = logWriteExecutor; return this; } public Path getOutputDirectory() { return outputDirectory; } public Builder setOutputDirectory(Path outputDirectory) { this.outputDirectory = outputDirectory; return this; } public String getLogBaseName() { return logBaseName; } public Builder setLogBaseName(String logBaseName) { this.logBaseName = logBaseName; return this; } public String getLogNameSuffix() { return logNameSuffix; } public Builder setLogNameSuffix(String logNameSuffix) { this.logNameSuffix = logNameSuffix; return this; } public boolean isRotate() { return rotate; } public Builder setRotate(boolean rotate) { this.rotate = rotate; return this; } public LogFileHeaderGenerator getLogFileHeaderGenerator() { return logFileHeaderGenerator; } public Builder setLogFileHeaderGenerator(LogFileHeaderGenerator logFileHeaderGenerator) { this.logFileHeaderGenerator = logFileHeaderGenerator; return this; } public DefaultAccessLogReceiver build() { return new DefaultAccessLogReceiver(logWriteExecutor, outputDirectory, logBaseName, logNameSuffix, rotate, logFileHeaderGenerator); } } } ExtendedAccessLogParser.java000066400000000000000000000500671420065311100347020ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/accesslog/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.accesslog; import io.undertow.UndertowLogger; import io.undertow.Version; import io.undertow.attribute.AuthenticationTypeExchangeAttribute; import io.undertow.attribute.BytesSentAttribute; import io.undertow.attribute.CompositeExchangeAttribute; import io.undertow.attribute.ConstantExchangeAttribute; import io.undertow.attribute.CookieAttribute; import io.undertow.attribute.DateTimeAttribute; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributeParser; import io.undertow.attribute.ExchangeAttributes; import io.undertow.attribute.LocalIPAttribute; import io.undertow.attribute.QueryStringAttribute; import io.undertow.attribute.QuotingExchangeAttribute; import io.undertow.attribute.ReadOnlyAttributeException; import io.undertow.attribute.RemoteIPAttribute; import io.undertow.attribute.RemoteUserAttribute; import io.undertow.attribute.RequestHeaderAttribute; import io.undertow.attribute.RequestMethodAttribute; import io.undertow.attribute.RequestProtocolAttribute; import io.undertow.attribute.RequestSchemeAttribute; import io.undertow.attribute.RequestURLAttribute; import io.undertow.attribute.ResponseCodeAttribute; import io.undertow.attribute.ResponseHeaderAttribute; import io.undertow.attribute.ResponseTimeAttribute; import io.undertow.attribute.SecureExchangeAttribute; import io.undertow.attribute.SubstituteEmptyWrapper; import io.undertow.server.HttpServerExchange; import io.undertow.util.HeaderValues; import io.undertow.util.Headers; import io.undertow.util.HttpString; import java.io.IOException; import java.io.StringReader; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; /** * Parser that transforms an extended access log format string into a * Undertow access log format string. * * @author Stuart Douglas */ public class ExtendedAccessLogParser { /** * parser that is used to access servlet context attributes, to avoid bringing in servlet * context dependencies */ private final ExchangeAttributeParser parser; public ExtendedAccessLogParser(ClassLoader classLoader) { this.parser = ExchangeAttributes.parser(classLoader, QuotingExchangeAttribute.WRAPPER); } private static class PatternTokenizer { private StringReader sr = null; private StringBuilder buf = new StringBuilder(); private boolean ended = false; private boolean subToken; private boolean parameter; PatternTokenizer(String str) { sr = new StringReader(str); } public boolean hasSubToken() { return subToken; } public boolean hasParameter() { return parameter; } public String getToken() throws IOException { if (ended) return null; String result = null; subToken = false; parameter = false; int c = sr.read(); while (c != -1) { switch (c) { case ' ': result = buf.toString(); buf = new StringBuilder(); buf.append((char) c); return result; case '-': result = buf.toString(); buf = new StringBuilder(); subToken = true; return result; case '(': result = buf.toString(); buf = new StringBuilder(); parameter = true; return result; case ')': result = buf.toString(); buf = new StringBuilder(); break; default: buf.append((char) c); } c = sr.read(); } ended = true; if (buf.length() != 0) { return buf.toString(); } else { return null; } } public String getParameter() throws IOException { String result; if (!parameter) { return null; } parameter = false; int c = sr.read(); while (c != -1) { if (c == ')') { result = buf.toString(); buf = new StringBuilder(); return result; } buf.append((char) c); c = sr.read(); } return null; } public String getWhiteSpaces() throws IOException { if (isEnded()) return ""; StringBuilder whiteSpaces = new StringBuilder(); if (buf.length() > 0) { whiteSpaces.append(buf); buf = new StringBuilder(); } int c = sr.read(); while (Character.isWhitespace((char) c)) { whiteSpaces.append((char) c); c = sr.read(); } if (c == -1) { ended = true; } else { buf.append((char) c); } return whiteSpaces.toString(); } public boolean isEnded() { return ended; } public String getRemains() throws IOException { StringBuilder remains = new StringBuilder(); for (int c = sr.read(); c != -1; c = sr.read()) { remains.append((char) c); } return remains.toString(); } } public ExchangeAttribute parse(String pattern) { List list = new ArrayList(); PatternTokenizer tokenizer = new PatternTokenizer(pattern); try { // Ignore leading whitespace. tokenizer.getWhiteSpaces(); if (tokenizer.isEnded()) { UndertowLogger.ROOT_LOGGER.extendedAccessLogEmptyPattern(); return null; } String token = tokenizer.getToken(); while (token != null) { if (UndertowLogger.ROOT_LOGGER.isDebugEnabled()) { UndertowLogger.ROOT_LOGGER.debug("token = " + token); } ExchangeAttribute element = getLogElement(token, tokenizer); if (element == null) { break; } list.add(element); String whiteSpaces = tokenizer.getWhiteSpaces(); if (whiteSpaces.length() > 0) { list.add(new ConstantExchangeAttribute(whiteSpaces)); } if (tokenizer.isEnded()) { break; } token = tokenizer.getToken(); } if (UndertowLogger.ROOT_LOGGER.isDebugEnabled()) { UndertowLogger.ROOT_LOGGER.debug("finished decoding with element size of: " + list.size()); } return new CompositeExchangeAttribute(list.toArray(new ExchangeAttribute[list.size()])); } catch (IOException e) { UndertowLogger.ROOT_LOGGER.extendedAccessLogPatternParseError(e); return null; } } protected ExchangeAttribute getLogElement(String token, PatternTokenizer tokenizer) throws IOException { if ("date".equals(token)) { return new DateTimeAttribute("yyyy-MM-dd", "GMT"); } else if ("time".equals(token)) { if (tokenizer.hasSubToken()) { String nextToken = tokenizer.getToken(); if ("taken".equals(nextToken)) { //if response timing are not enabled we just print a '-' return new SubstituteEmptyWrapper.SubstituteEmptyAttribute(new ResponseTimeAttribute(TimeUnit.SECONDS), "-"); } } else { return new DateTimeAttribute("HH:mm:ss", "GMT"); } } else if ("bytes".equals(token)) { return new BytesSentAttribute(true); } else if ("cached".equals(token)) { /* I don't know how to evaluate this! */ return new ConstantExchangeAttribute("-"); } else if ("c".equals(token)) { String nextToken = tokenizer.getToken(); if ("ip".equals(nextToken)) { return RemoteIPAttribute.INSTANCE; } else if ("dns".equals(nextToken)) { return new ExchangeAttribute() { @Override public String readAttribute(HttpServerExchange exchange) { final InetSocketAddress peerAddress = exchange.getConnection().getPeerAddress(InetSocketAddress.class); try { return peerAddress.getHostName(); } catch (Throwable e) { return peerAddress.getHostString(); } } @Override public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException(); } }; } } else if ("s".equals(token)) { String nextToken = tokenizer.getToken(); if ("ip".equals(nextToken)) { return LocalIPAttribute.INSTANCE; } else if ("dns".equals(nextToken)) { return new ExchangeAttribute() { @Override public String readAttribute(HttpServerExchange exchange) { try { return exchange.getConnection().getLocalAddress(InetSocketAddress.class).getHostName(); } catch (Throwable e) { return "localhost"; } } @Override public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException(); } }; } } else if ("cs".equals(token)) { return getClientToServerElement(tokenizer); } else if ("sc".equals(token)) { return getServerToClientElement(tokenizer); } else if ("sr".equals(token) || "rs".equals(token)) { return getProxyElement(tokenizer); } else if ("x".equals(token)) { return getXParameterElement(tokenizer); } UndertowLogger.ROOT_LOGGER.extendedAccessLogUnknownToken(token); return null; } protected ExchangeAttribute getClientToServerElement( PatternTokenizer tokenizer) throws IOException { if (tokenizer.hasSubToken()) { String token = tokenizer.getToken(); if ("method".equals(token)) { return RequestMethodAttribute.INSTANCE; } else if ("uri".equals(token)) { if (tokenizer.hasSubToken()) { token = tokenizer.getToken(); if ("stem".equals(token)) { return RequestURLAttribute.INSTANCE; } else if ("query".equals(token)) { return new SubstituteEmptyWrapper.SubstituteEmptyAttribute(QueryStringAttribute.BARE_INSTANCE, "-"); } } else { return new ExchangeAttribute() { @Override public String readAttribute(HttpServerExchange exchange) { String query = exchange.getQueryString(); if (query.isEmpty()) { return exchange.getRequestURI(); } else { StringBuilder buf = new StringBuilder(); buf.append(exchange.getRequestURI()); buf.append('?'); buf.append(exchange.getQueryString()); return buf.toString(); } } @Override public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException(); } }; } } } else if (tokenizer.hasParameter()) { String parameter = tokenizer.getParameter(); if (parameter == null) { UndertowLogger.ROOT_LOGGER.extendedAccessLogMissingClosing(); return null; } return new QuotingExchangeAttribute(new RequestHeaderAttribute(new HttpString(parameter))); } UndertowLogger.ROOT_LOGGER.extendedAccessLogCannotDecode(tokenizer.getRemains()); return null; } protected ExchangeAttribute getServerToClientElement( PatternTokenizer tokenizer) throws IOException { if (tokenizer.hasSubToken()) { String token = tokenizer.getToken(); if ("status".equals(token)) { return ResponseCodeAttribute.INSTANCE; } else if ("comment".equals(token)) { return new ConstantExchangeAttribute("?"); } } else if (tokenizer.hasParameter()) { String parameter = tokenizer.getParameter(); if (parameter == null) { UndertowLogger.ROOT_LOGGER.extendedAccessLogMissingClosing(); return null; } return new QuotingExchangeAttribute(new ResponseHeaderAttribute(new HttpString(parameter))); } UndertowLogger.ROOT_LOGGER.extendedAccessLogCannotDecode(tokenizer.getRemains()); return null; } protected ExchangeAttribute getProxyElement(PatternTokenizer tokenizer) throws IOException { String token = null; if (tokenizer.hasSubToken()) { tokenizer.getToken(); return new ConstantExchangeAttribute("-"); } else if (tokenizer.hasParameter()) { tokenizer.getParameter(); return new ConstantExchangeAttribute("-"); } UndertowLogger.ROOT_LOGGER.extendedAccessLogCannotDecode(token); return null; } protected ExchangeAttribute getXParameterElement(PatternTokenizer tokenizer) throws IOException { if (!tokenizer.hasSubToken()) { UndertowLogger.ROOT_LOGGER.extendedAccessLogBadXParam(); return null; } final String token = tokenizer.getToken(); if (!tokenizer.hasParameter()) { UndertowLogger.ROOT_LOGGER.extendedAccessLogBadXParam(); return null; } String parameter = tokenizer.getParameter(); if (parameter == null) { UndertowLogger.ROOT_LOGGER.extendedAccessLogMissingClosing(); return null; } if ("A".equals(token)) { return new SubstituteEmptyWrapper.SubstituteEmptyAttribute(parser.parse("%{sc," + parameter + "}"),"-"); } else if ("C".equals(token)) { return new SubstituteEmptyWrapper.SubstituteEmptyAttribute(new CookieAttribute(parameter),"-"); } else if ("R".equals(token)) { return parser.parse("%{r," + parameter + "}"); } else if ("S".equals(token)) { return new SubstituteEmptyWrapper.SubstituteEmptyAttribute(parser.parse("%{s," + parameter + "}"),"-"); } else if ("H".equals(token)) { return getServletRequestElement(parameter); } else if ("P".equals(token)) { return new SubstituteEmptyWrapper.SubstituteEmptyAttribute(parser.parse("%{rp," + parameter + "}"),"-"); } else if ("O".equals(token)) { return new QuotingExchangeAttribute(new ExchangeAttribute() { @Override public String readAttribute(HttpServerExchange exchange) { HeaderValues values = exchange.getResponseHeaders().get(parameter); if (values != null && values.size() > 0) { StringBuilder buffer = new StringBuilder(); for (int i = 0; i < values.size(); i++) { String string = values.get(i); buffer.append(string); if (i + 1 < values.size()) buffer.append(","); } return buffer.toString(); } return null; } @Override public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException(); } }); } UndertowLogger.ROOT_LOGGER.extendedAccessLogCannotDecodeXParamValue(token); return null; } protected ExchangeAttribute getServletRequestElement(String parameter) { if ("authType".equals(parameter)) { return new SubstituteEmptyWrapper.SubstituteEmptyAttribute(AuthenticationTypeExchangeAttribute.INSTANCE,"-"); } else if ("remoteUser".equals(parameter)) { return new SubstituteEmptyWrapper.SubstituteEmptyAttribute(RemoteUserAttribute.INSTANCE,"-"); } else if ("requestedSessionId".equals(parameter)) { return parser.parse("%{REQUESTED_SESSION_ID}"); } else if ("requestedSessionIdFromCookie".equals(parameter)) { return parser.parse("%{REQUESTED_SESSION_ID_FROM_COOKIE}"); } else if ("requestedSessionIdValid".equals(parameter)) { return parser.parse("%{REQUESTED_SESSION_ID_VALID}"); } else if ("contentLength".equals(parameter)) { return new QuotingExchangeAttribute(new RequestHeaderAttribute(Headers.CONTENT_LENGTH)); } else if ("characterEncoding".equals(parameter)) { return parser.parse("%{REQUEST_CHARACTER_ENCODING}"); } else if ("locale".equals(parameter)) { return parser.parse("%{REQUEST_LOCALE}"); } else if ("protocol".equals(parameter)) { return RequestProtocolAttribute.INSTANCE; } else if ("scheme".equals(parameter)) { return RequestSchemeAttribute.INSTANCE; } else if ("secure".equals(parameter)) { return SecureExchangeAttribute.INSTANCE; } UndertowLogger.ROOT_LOGGER.extendedAccessLogCannotDecodeXParamValue(parameter); return null; } public static class ExtendedAccessLogHeaderGenerator implements LogFileHeaderGenerator { private final String pattern; public ExtendedAccessLogHeaderGenerator(String pattern) { this.pattern = pattern; } @Override public String generateHeader() { StringBuilder sb = new StringBuilder(); sb.append("#Fields: "); sb.append(pattern); sb.append(System.lineSeparator()); sb.append("#Version: 2.0"); sb.append(System.lineSeparator()); sb.append("#Software: "); sb.append(Version.getFullVersionString()); sb.append(System.lineSeparator()); return sb.toString(); } } } JBossLoggingAccessLogReceiver.java000066400000000000000000000025741420065311100360010ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/accesslog/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.accesslog; import org.jboss.logging.Logger; /** * Access log receiver that logs messages at INFO level. * * @author Stuart Douglas */ public class JBossLoggingAccessLogReceiver implements AccessLogReceiver { public static final String DEFAULT_CATEGORY = "io.undertow.accesslog"; private final Logger logger; public JBossLoggingAccessLogReceiver(final String category) { this.logger = Logger.getLogger(category); } public JBossLoggingAccessLogReceiver() { this.logger = Logger.getLogger(DEFAULT_CATEGORY); } @Override public void logMessage(String message) { logger.info(message); } } LogFileHeaderGenerator.java000066400000000000000000000017331420065311100344760ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/accesslog/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.accesslog; /** * Interface that generates the header for an access log. This is called * every time a new log file is started. * * @author Stuart Douglas */ public interface LogFileHeaderGenerator { String generateHeader(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/builder/000077500000000000000000000000001420065311100270705ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/builder/HandlerBuilder.java000066400000000000000000000031751420065311100326250ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.builder; import io.undertow.server.HandlerWrapper; import java.util.Map; import java.util.Set; /** * Interface that provides a way of providing a textual representation of a handler. * * @author Stuart Douglas */ public interface HandlerBuilder { /** * The string representation of the handler name. * * @return The handler name */ String name(); /** * Returns a map of parameters and their types. */ Map> parameters(); /** * @return The required parameters */ Set requiredParameters(); /** * @return The default parameter name, or null if it does not have a default parameter */ String defaultParameter(); /** * Creates the handler * * @param config The handler config * @return The new predicate */ HandlerWrapper build(final Map config); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/builder/HandlerParser.java000066400000000000000000000030421420065311100324640ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.builder; import io.undertow.server.HandlerWrapper; /** * Parser that can build a handler from a string representation. The underlying syntax is quite simple, and example is * shown below: *

* * rewrite[value="/path"] * * If a handler is only being passed a single parameter then the parameter name can be omitted. * Strings can be enclosed in optional double or single quotations marks, and quotation marks can be escaped using * \". *

* Array types are represented via a comma separated list of values enclosed in curly braces. *

* * @author Stuart Douglas */ public class HandlerParser { public static HandlerWrapper parse(String string, final ClassLoader classLoader) { return PredicatedHandlersParser.parseHandler(string, classLoader); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/builder/PredicatedHandler.java000066400000000000000000000031031420065311100332720ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.builder; import io.undertow.predicate.Predicate; import io.undertow.server.HandlerWrapper; /** * @author Stuart Douglas */ public class PredicatedHandler { private final Predicate predicate; private final HandlerWrapper handler; private final HandlerWrapper elseHandler; public PredicatedHandler(Predicate predicate, HandlerWrapper handler) { this(predicate, handler, null); } public PredicatedHandler(Predicate predicate, HandlerWrapper handler, HandlerWrapper elseHandler) { this.predicate = predicate; this.handler = handler; this.elseHandler = elseHandler; } public Predicate getPredicate() { return predicate; } public HandlerWrapper getHandler() { return handler; } public HandlerWrapper getElseHandler() { return elseHandler; } } PredicatedHandlersParser.java000066400000000000000000001134711420065311100345650ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/builder/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.builder; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributeParser; import io.undertow.attribute.ExchangeAttributes; import io.undertow.predicate.Predicate; import io.undertow.predicate.PredicateBuilder; import io.undertow.predicate.Predicates; import io.undertow.predicate.PredicatesHandler; import io.undertow.server.HandlerWrapper; import io.undertow.util.FileUtils; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Array; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.ServiceLoader; /** * Parser for the undertow-handlers.conf file. *

* This file has a line by line syntax, specifying predicate -> handler. If no predicate is specified then * the line is assumed to just contain a handler. * * @author Stuart Douglas */ public class PredicatedHandlersParser { public static final String ELSE = "else"; public static final String ARROW = "->"; public static final String NOT = "not"; public static final String OR = "or"; public static final String AND = "and"; public static final String TRUE = "true"; public static final String FALSE = "false"; public static List parse(final File file, final ClassLoader classLoader) { return parse(file.toPath(), classLoader); } public static List parse(final Path file, final ClassLoader classLoader) { try { return parse(new String(Files.readAllBytes(file), StandardCharsets.UTF_8), classLoader); } catch (IOException e) { throw new RuntimeException(e); } } public static List parse(final InputStream inputStream, final ClassLoader classLoader) { return parse(FileUtils.readFile(inputStream), classLoader); } public static List parse(final String contents, final ClassLoader classLoader) { Deque tokens = tokenize(contents); Node node = parse(contents, tokens); Map predicateBuilders = loadPredicateBuilders(classLoader); Map handlerBuilders = loadHandlerBuilders(classLoader); final ExchangeAttributeParser attributeParser = ExchangeAttributes.parser(classLoader); return handleNode(contents, node, predicateBuilders, handlerBuilders, attributeParser); } public static Predicate parsePredicate(String string, ClassLoader classLoader) { Deque tokens = tokenize(string); Node node = parse(string, tokens); Map predicateBuilders = loadPredicateBuilders(classLoader); final ExchangeAttributeParser attributeParser = ExchangeAttributes.parser(classLoader); return handlePredicateNode(string, node, predicateBuilders, attributeParser); } public static HandlerWrapper parseHandler(String string, ClassLoader classLoader) { Deque tokens = tokenize(string); Node node = parse(string, tokens); Map handlerBuilders = loadHandlerBuilders(classLoader); final ExchangeAttributeParser attributeParser = ExchangeAttributes.parser(classLoader); return handleHandlerNode(string, (ExpressionNode)node, handlerBuilders, attributeParser); } private static List handleNode(String contents, Node node, Map predicateBuilders, Map handlerBuilders, ExchangeAttributeParser attributeParser) { if(node instanceof BlockNode) { return handleBlockNode(contents, (BlockNode) node, predicateBuilders, handlerBuilders, attributeParser); } else if(node instanceof ExpressionNode) { HandlerWrapper handler = handleHandlerNode(contents, (ExpressionNode) node, handlerBuilders, attributeParser); return Collections.singletonList(new PredicatedHandler(Predicates.truePredicate(), handler)); } else if(node instanceof PredicateOperatorNode) { return Collections.singletonList(handlePredicateOperatorNode(contents, (PredicateOperatorNode)node, predicateBuilders, handlerBuilders, attributeParser)); } else { throw error(contents, node.getToken().getPosition(), "unexpected token " + node.getToken()); } } private static PredicatedHandler handlePredicateOperatorNode(String contents, PredicateOperatorNode node, Map predicateBuilders, Map handlerBuilders, ExchangeAttributeParser parser) { Predicate predicate = handlePredicateNode(contents, node.getLeft(), predicateBuilders, parser); HandlerWrapper ret = handlePredicatedAction(contents, node.getRight(), predicateBuilders, handlerBuilders, parser); HandlerWrapper elseBranch = null; if(node.getElseBranch() != null) { elseBranch = handlePredicatedAction(contents, node.getElseBranch(), predicateBuilders, handlerBuilders, parser); } return new PredicatedHandler(predicate, ret, elseBranch); } private static HandlerWrapper handlePredicatedAction(String contents, Node node, Map predicateBuilders, Map handlerBuilders, ExchangeAttributeParser parser) { if(node instanceof ExpressionNode) { return handleHandlerNode(contents, (ExpressionNode) node, handlerBuilders, parser); } else if(node instanceof BlockNode) { List handlers = handleBlockNode(contents, (BlockNode) node, predicateBuilders, handlerBuilders, parser); return new PredicatesHandler.Wrapper(handlers, false); } else if(node instanceof PredicateOperatorNode) { List handlers = Collections.singletonList(handlePredicateOperatorNode(contents, (PredicateOperatorNode)node, predicateBuilders, handlerBuilders, parser)); return new PredicatesHandler.Wrapper(handlers, false); } else { throw error(contents, node.getToken().getPosition(), "unexpected token " + node.getToken()); } } private static List handleBlockNode(String contents, BlockNode node, Map predicateBuilders, Map handlerBuilders, ExchangeAttributeParser parser) { List ret = new ArrayList<>(); for(Node line : node.getBlock()) { ret.addAll(handleNode(contents, line, predicateBuilders, handlerBuilders, parser)); } return ret; } private static HandlerWrapper handleHandlerNode(String contents, ExpressionNode node, Map handlerBuilders, ExchangeAttributeParser parser) { Token token = node.getToken(); HandlerBuilder builder = handlerBuilders.get(token.getToken()); if (builder == null) { throw error(contents, token.getPosition(), "no handler named " + token.getToken() + " known handlers are " + handlerBuilders.keySet()); } Map parameters = new HashMap<>(); for(Map.Entry val : node.getValues().entrySet()) { String name = val.getKey(); if(name == null) { if(builder.defaultParameter() == null) { throw error(contents, token.getPosition(), "default parameter not supported"); } name = builder.defaultParameter(); } Class type = builder.parameters().get(name); if(type == null) { throw error(contents, val.getValue().getToken().getPosition(), "unknown parameter " + name); } if(val.getValue() instanceof ValueNode) { parameters.put(name, coerceToType(contents, val.getValue().getToken(), type, parser)); } else if(val.getValue() instanceof ArrayNode) { parameters.put(name, readArrayType(contents, name, (ArrayNode)val.getValue(), parser, type)); } else { throw error(contents, val.getValue().getToken().getPosition(), "unexpected node " + val.getValue()); } } return builder.build(parameters); } private static Predicate handlePredicateNode(String contents, Node node, Map handlerBuilders, ExchangeAttributeParser parser) { if(node instanceof AndNode) { AndNode andNode = (AndNode)node; return Predicates.and(handlePredicateNode(contents, andNode.getLeft(), handlerBuilders, parser), handlePredicateNode(contents, andNode.getRight(), handlerBuilders, parser)); } else if(node instanceof OrNode) { OrNode orNode = (OrNode)node; return Predicates.or(handlePredicateNode(contents, orNode.getLeft(), handlerBuilders, parser), handlePredicateNode(contents, orNode.getRight(), handlerBuilders, parser)); } else if(node instanceof NotNode) { NotNode orNode = (NotNode)node; return Predicates.not(handlePredicateNode(contents, orNode.getNode(), handlerBuilders, parser)); } else if(node instanceof ExpressionNode) { return handlePredicateExpressionNode(contents, (ExpressionNode) node, handlerBuilders, parser); }else if(node instanceof OperatorNode) { switch (node.getToken().getToken()) { case TRUE: { return Predicates.truePredicate(); } case FALSE: { return Predicates.falsePredicate(); } } } throw error(contents, node.getToken().getPosition(), "unexpected node " + node); } private static Predicate handlePredicateExpressionNode(String contents, ExpressionNode node, Map handlerBuilders, ExchangeAttributeParser parser) { Token token = node.getToken(); PredicateBuilder builder = handlerBuilders.get(token.getToken()); if (builder == null) { throw error(contents, token.getPosition(), "no predicate named " + token.getToken() + " known predicates are " + handlerBuilders.keySet()); } Map parameters = new HashMap<>(); for(Map.Entry val : node.getValues().entrySet()) { String name = val.getKey(); if(name == null) { if(builder.defaultParameter() == null) { throw error(contents, token.getPosition(), "default parameter not supported"); } name = builder.defaultParameter(); } Class type = builder.parameters().get(name); if(type == null) { throw error(contents, val.getValue().getToken().getPosition(), "unknown parameter " + name); } if(val.getValue() instanceof ValueNode) { parameters.put(name, coerceToType(contents, val.getValue().getToken(), type, parser)); } else if(val.getValue() instanceof ArrayNode) { parameters.put(name, readArrayType(contents, name, (ArrayNode)val.getValue(), parser, type)); } else { throw error(contents, val.getValue().getToken().getPosition(), "unexpected node " + val.getValue()); } } return builder.build(parameters); } private static Object readArrayType(final String string, String paramName, ArrayNode value, ExchangeAttributeParser parser, Class type) { if (!type.isArray()) { throw error(string, value.getToken().getPosition(), "parameter is not an array type " + paramName); } Class componentType = type.getComponentType(); final List values = new ArrayList<>(); for(Token token : value.getValues()) { values.add(coerceToType(string, token, componentType, parser)); } Object array = Array.newInstance(componentType, values.size()); for (int i = 0; i < values.size(); ++i) { Array.set(array, i, values.get(i)); } return array; } private static Object coerceToType(final String string, final Token token, final Class type, final ExchangeAttributeParser attributeParser) { if (type.isArray()) { Object array = Array.newInstance(type.getComponentType(), 1); Array.set(array, 0, coerceToType(string, token, type.getComponentType(), attributeParser)); return array; } if (type == String.class) { return token.getToken(); } else if (type.equals(Boolean.class) || type.equals(boolean.class)) { return Boolean.valueOf(token.getToken()); } else if (type.equals(Byte.class) || type.equals(byte.class)) { return Byte.valueOf(token.getToken()); } else if (type.equals(Character.class) || type.equals(char.class)) { if (token.getToken().length() != 1) { throw error(string, token.getPosition(), "Cannot coerce " + token.getToken() + " to a Character"); } return Character.valueOf(token.getToken().charAt(0)); } else if (type.equals(Short.class) || type.equals(short.class)) { return Short.valueOf(token.getToken()); } else if (type.equals(Integer.class) || type.equals(int.class)) { return Integer.valueOf(token.getToken()); } else if (type.equals(Long.class) || type.equals(long.class)) { return Long.valueOf(token.getToken()); } else if (type.equals(Float.class) || type.equals(float.class)) { return Float.valueOf(token.getToken()); } else if (type.equals(Double.class) || type.equals(double.class)) { return Double.valueOf(token.getToken()); } else if (type.equals(ExchangeAttribute.class)) { return attributeParser.parse(token.getToken()); } return token.getToken(); } private static Map loadPredicateBuilders(final ClassLoader classLoader) { ServiceLoader loader = ServiceLoader.load(PredicateBuilder.class, classLoader); final Map ret = new HashMap<>(); for (PredicateBuilder builder : loader) { if (ret.containsKey(builder.name())) { if (ret.get(builder.name()).getClass() != builder.getClass()) { throw UndertowMessages.MESSAGES.moreThanOnePredicateWithName(builder.name(), builder.getClass(), ret.get(builder.name()).getClass()); } } else { ret.put(builder.name(), builder); } } return ret; } private static Map loadHandlerBuilders(final ClassLoader classLoader) { ServiceLoader loader = ServiceLoader.load(HandlerBuilder.class, classLoader); final Map ret = new HashMap<>(); for (HandlerBuilder builder : loader) { if (ret.containsKey(builder.name())) { if (ret.get(builder.name()).getClass() != builder.getClass()) { throw UndertowMessages.MESSAGES.moreThanOneHandlerWithName(builder.name(), builder.getClass(), ret.get(builder.name()).getClass()); } } else { ret.put(builder.name(), builder); } } return ret; } static Node parse(final String string, Deque tokens) { return parse(string, tokens, true); } static Node parse(final String string, Deque tokens, boolean topLevel) { //shunting yard algorithm //gets rid or parentheses and fixes up operator ordering Deque operatorStack = new ArrayDeque<>(); Deque output = new ArrayDeque<>(); List blocks = new ArrayList<>(); while (!tokens.isEmpty()) { Token token = tokens.poll(); if(token.getToken().equals("{")) { output.push(parse(string, tokens, false)); } else if(token.getToken().equals("}")) { if(topLevel) { throw error(string, token.getPosition(), "Unexpected token"); } break; } else if(token.getToken().equals("\n") || token.getToken().equals(";")) { if(token.getToken().equals(";") && tokens.peek()!=null && tokens.peek().getToken().equals(ELSE)) { // something() -> predicate; ELSE predicate; - dont end processing since its followed by ELSE and its singular block continue; } handleLineEnd(string, operatorStack, output, blocks); } else if (isSpecialChar(token.getToken())) { if (token.getToken().equals("(")) { operatorStack.push(token); } else if (token.getToken().equals(")")) { for (; ; ) { Token op = operatorStack.pop(); if (op == null) { throw error(string, token.getPosition(), "Unexpected end of input"); } else if (op.getToken().equals("(")) { break; } else { output.push(new OperatorNode(op)); } } } else { output.push(new OperatorNode(token)); } } else { if (isOperator(token.getToken()) && !token.getToken().equals(ELSE)) { int prec = precedence(token.getToken()); Token top = operatorStack.peek(); while (top != null) { if (top.getToken().equals("(")) { break; } int exitingPrec = precedence(top.getToken()); if (prec <= exitingPrec) { output.push(new OperatorNode(operatorStack.pop())); } else { break; } top = operatorStack.peek(); } operatorStack.push(token); } else { output.push(parseExpression(string, token, tokens)); } } } handleLineEnd(string, operatorStack, output, blocks); if(blocks.size() == 1) { return blocks.get(0); } else { return new BlockNode(new Token("", 0), blocks); } } private static void handleLineEnd(String string, Deque operatorStack, Deque output, List blocks) { while (!operatorStack.isEmpty()) { Token op = operatorStack.pop(); if (op.getToken().equals(")")) { throw error(string, string.length(), "Mismatched parenthesis"); } output.push(new OperatorNode(op)); } if(output.isEmpty()) { return; } //now we have our tokens for this line Node predicate = collapseOutput(output.pop(), output); if (!output.isEmpty()) { throw error(string, output.getFirst().getToken().getPosition(), "Invalid expression"); } blocks.add(predicate); } private static Node collapseOutput(final Node token, final Deque tokens) { if (token instanceof OperatorNode) { OperatorNode node = (OperatorNode) token; if (node.token.getToken().equals(AND)) { Node n1 = collapseOutput(tokens.pop(), tokens); Node n2 = collapseOutput(tokens.pop(), tokens); return new AndNode(token.getToken(), n2, n1); } else if (node.token.getToken().equals(OR)) { Node n1 = collapseOutput(tokens.pop(), tokens); Node n2 = collapseOutput(tokens.pop(), tokens); return new OrNode(token.getToken(), n2, n1); } else if (node.token.getToken().equals(NOT)) { Node n1 = collapseOutput(tokens.pop(), tokens); return new NotNode(token.getToken(), n1); } else if (node.token.getToken().equals(ARROW)) { Node n1 = collapseOutput(tokens.pop(), tokens); Node n2 = null; Node elseBranch = null; final Node popped = tokens.pop(); if(popped.getToken().getToken().equals(ELSE)) { elseBranch = n1; n1 = collapseOutput(tokens.pop(), tokens); n2 = collapseOutput(tokens.pop(), tokens); } else { n2 = collapseOutput(popped, tokens); } return new PredicateOperatorNode(token.getToken(), n2, n1, elseBranch); } else { return token; } } else { return token; } } private static Node parseExpression(final String string, final Token token, final Deque tokens) { if (token.getToken().equals(TRUE)) { return new OperatorNode(token); } else if (token.getToken().equals(FALSE)) { return new OperatorNode(token); } else { Token next = tokens.peek(); String endChar = ")"; if (next != null && (next.getToken().equals("[") || next.getToken().equals("("))) { if (next.getToken().equals("[")) { endChar = "]"; UndertowLogger.ROOT_LOGGER.oldStylePredicateSyntax(string); } final Map values = new HashMap<>(); tokens.poll(); next = tokens.poll(); if (next == null) { throw error(string, string.length(), "Unexpected end of input"); } if (next.getToken().equals("{")) { return handleSingleArrayValue(string, token, tokens, endChar); } while (!next.getToken().equals(endChar)) { Token equals = tokens.poll(); if (equals == null) { throw error(string, string.length(), "Unexpected end of input"); } if (!equals.getToken().equals("=")) { if (equals.getToken().equals(endChar) && values.isEmpty()) { //single value case return handleSingleValue(token, next); } else if (equals.getToken().equals(",")) { tokens.push(equals); tokens.push(next); return handleSingleVarArgsValue(string, token, tokens, endChar); } throw error(string, equals.getPosition(), "Unexpected token"); } Token value = tokens.poll(); if (value == null) { throw error(string, string.length(), "Unexpected end of input"); } if (value.getToken().equals("{")) { values.put(next.getToken(), new ArrayNode(value, readArrayType(string, tokens,"}"))); } else { if (isOperator(value.getToken()) || isSpecialChar(value.getToken())) { throw error(string, value.getPosition(), "Unexpected token"); } values.put(next.getToken(), new ValueNode(value)); } next = tokens.poll(); if (next == null) { throw error(string, string.length(), "Unexpected end of input"); } if (!next.getToken().equals(endChar)) { if (!next.getToken().equals(",")) { throw error(string, string.length(), "Expecting , or " + endChar); } next = tokens.poll(); if (next == null) { throw error(string, string.length(), "Unexpected end of input"); } } } return new ExpressionNode(token, values); } else { if (next != null && isSpecialChar(next.getToken())) { throw error(string, next.getPosition(), "Unexpected character"); } return new ExpressionNode(token, Collections.emptyMap()); } } } private static Node handleSingleArrayValue(final String string, final Token builder, final Deque tokens, String endChar) { List array = readArrayType(string, tokens, "}"); Token close = tokens.poll(); if (!close.getToken().equals(endChar)) { throw error(string, close.getPosition(), "expected " + endChar); } return new ExpressionNode(builder, Collections.singletonMap(null, new ArrayNode(builder, array))); } private static Node handleSingleVarArgsValue(final String string, final Token expressionName, final Deque tokens, String endChar) { List array = readArrayType(string, tokens, endChar); return new ExpressionNode(expressionName, Collections.singletonMap(null, new ArrayNode(expressionName, array))); } private static List readArrayType(final String string, final Deque tokens, String expectedEndToken) { final List values = new ArrayList<>(); Token token = tokens.poll(); if(token.getToken().equals(expectedEndToken)) { return Collections.emptyList(); } while (token != null) { Token commaOrEnd = tokens.poll(); values.add(token); if (commaOrEnd.getToken().equals(expectedEndToken)) { return values; } else if (!commaOrEnd.getToken().equals(",")) { throw error(string, commaOrEnd.getPosition(), "expected either , or " + expectedEndToken); } token = tokens.poll(); } throw error(string, string.length(), "unexpected end of input in array"); } private static Node handleSingleValue(final Token token, final Token next) { return new ExpressionNode(token, Collections.singletonMap(null, new ValueNode(next))); } private static int precedence(String operator) { if (operator.equals(NOT)) { return 3; } else if (operator.equals(AND)) { return 2; } else if (operator.equals(OR)) { return 1; } else if (operator.equals(ARROW)) { return -1000; } throw new IllegalStateException(); } private static boolean isOperator(final String op) { return op.equals(AND) || op.equals(OR) || op.equals(NOT) || op.equals(ARROW); } private static boolean isSpecialChar(String token) { if (token.length() == 1) { char c = token.charAt(0); switch (c) { case '(': case ')': case ',': case '=': case '[': case ']': return true; default: return false; } } return false; } public static Deque tokenize(final String string) { char currentStringDelim = 0; boolean inVariable = false; int pos = 0; StringBuilder current = new StringBuilder(); Deque ret = new ArrayDeque<>(); while (pos < string.length()) { char c = string.charAt(pos); if (currentStringDelim != 0) { if (c == currentStringDelim && current.charAt(current.length() - 1) != '\\') { ret.add(new Token(current.toString(), pos)); current.setLength(0); currentStringDelim = 0; } else if (c == '\n' || c == '\r') { ret.add(new Token(current.toString(), pos)); current.setLength(0); currentStringDelim = 0; ret.add(new Token("\n", pos)); } else { current.append(c); } } else { switch (c) { case ' ': case '\t': { if (current.length() != 0) { ret.add(new Token(current.toString(), pos)); current.setLength(0); } break; } case '\r': case '\n': { if (current.length() != 0) { ret.add(new Token(current.toString(), pos)); current.setLength(0); } ret.add(new Token("\n", pos)); break; } case ';': case '(': case ')': case ',': case '=': case '[': case ']': case '{': case '}': { if (inVariable) { current.append(c); if (c == '}') { inVariable = false; } } else { if (current.length() != 0) { ret.add(new Token(current.toString(), pos)); current.setLength(0); } ret.add(new Token("" + c, pos)); } break; } case '"': case '\'': { if (current.length() != 0) { throw error(string, pos, "Unexpected token"); } currentStringDelim = c; break; } case '%': case '$': { current.append(c); if (string.charAt(pos + 1) == '{') { inVariable = true; } break; } case '-': if (inVariable) { current.append(c); } else { if (pos != string.length() && string.charAt(pos + 1) == '>') { pos++; if (current.length() != 0) { ret.add(new Token(current.toString(), pos)); current.setLength(0); } ret.add(new Token(ARROW, pos)); } else { current.append(c); } } break; default: current.append(c); } } ++pos; } if (current.length() > 0) { ret.add(new Token(current.toString(), string.length())); } return ret; } private static IllegalStateException error(final String string, int pos, String reason) { StringBuilder b = new StringBuilder(); int linePos = 0; for (int i = 0; i < string.length(); ++i) { if (string.charAt(i) == '\n') { if (i >= pos) { //truncate the string at the error line break; } else { linePos = 0; } } else if (i < pos) { linePos++; } b.append(string.charAt(i)); } b.append('\n'); for (int i = 0; i < linePos; ++i) { b.append(' '); } b.append('^'); throw UndertowMessages.MESSAGES.errorParsingPredicateString(reason, b.toString()); } public interface Node { Token getToken(); } /** * A parsed expression */ static class ExpressionNode implements Node { private final Token token; private final Map values; private ExpressionNode(Token token, Map values) { this.token = token; this.values = values; } public Token getToken() { return token; } public Map getValues() { return values; } } static class ArrayNode implements Node { private final Token start; private final List values; private ArrayNode(Token start, List tokens) { this.start = start; this.values = tokens; } public List getValues() { return values; } @Override public Token getToken() { return start; } } static class ValueNode implements Node { private final Token value; private ValueNode(Token value) { this.value = value; } public Token getValue() { return value; } @Override public String toString() { return value.getToken(); } @Override public Token getToken() { return value; } } static class OperatorNode implements Node { private final Token token; private OperatorNode(Token token) { this.token = token; } public Token getToken() { return token; } } static class AndNode implements Node { private final Token token; private final Node left; private final Node right; AndNode(Token token, Node left, Node right) { this.token = token; this.left = left; this.right = right; } public Node getLeft() { return left; } public Node getRight() { return right; } public Token getToken() { return token; } } static class OrNode implements Node { private final Token token; private final Node left; private final Node right; OrNode(Token token, Node left, Node right) { this.token = token; this.left = left; this.right = right; } public Node getLeft() { return left; } public Node getRight() { return right; } public Token getToken() { return token; } } static class PredicateOperatorNode implements Node { private final Token token; private final Node left; private final Node right; private final Node elseBranch; PredicateOperatorNode(Token token, Node left, Node right, Node elseBranch) { this.token = token; this.left = left; this.right = right; this.elseBranch = elseBranch; } public Node getLeft() { return left; } public Node getRight() { return right; } public Node getElseBranch() { return elseBranch; } @Override public Token getToken() { return token; } } static class NotNode implements Node { private final Token token; private final Node node; NotNode(Token token, Node node) { this.token = token; this.node = node; } public Node getNode() { return node; } public Token getToken() { return token; } } static class BlockNode implements Node { private final Token token; private final List block; BlockNode(Token token, List block) { this.token = token; this.block = block; } public List getBlock() { return block; } @Override public Token getToken() { return token; } } static final class Token { private final String token; private final int position; Token(final String token, final int position) { this.token = token; this.position = position; } public String getToken() { return token; } public int getPosition() { return position; } @Override public String toString() { return token + " <" + position + ">"; } } } ResponseCodeHandlerBuilder.java000066400000000000000000000036701420065311100350600ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/builder/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.builder; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.handlers.ResponseCodeHandler; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * @author Stuart Douglas */ public class ResponseCodeHandlerBuilder implements HandlerBuilder { @Override public String name() { return "response-code"; } @Override public Map> parameters() { Map> parameters = new HashMap<>(); parameters.put("value", Integer.class); return parameters; } @Override public Set requiredParameters() { final Set req = new HashSet<>(); req.add("value"); return req; } @Override public String defaultParameter() { return "value"; } @Override public HandlerWrapper build(final Map config) { final Integer value = (Integer) config.get("value"); return new HandlerWrapper() { @Override public HttpHandler wrap(HttpHandler handler) { return new ResponseCodeHandler(value); } }; } } RewriteHandlerBuilder.java000066400000000000000000000051171420065311100341060ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/builder/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.builder; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributes; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.SetAttributeHandler; import io.undertow.UndertowLogger; import java.util.Collections; import java.util.Map; import java.util.Set; /** * @author Stuart Douglas */ public class RewriteHandlerBuilder implements HandlerBuilder { @Override public String name() { return "rewrite"; } @Override public Map> parameters() { return Collections.>singletonMap("value", ExchangeAttribute.class); } @Override public Set requiredParameters() { return Collections.singleton("value"); } @Override public String defaultParameter() { return "value"; } @Override public HandlerWrapper build(final Map config) { final ExchangeAttribute value = (ExchangeAttribute) config.get("value"); return new HandlerWrapper() { @Override public HttpHandler wrap(HttpHandler handler) { return new SetAttributeHandler(handler, ExchangeAttributes.relativePath(), value){ @Override public void handleRequest(HttpServerExchange exchange) throws Exception { UndertowLogger.PREDICATE_LOGGER.debugf("Request rewritten to [%s] for %s.", getValue().readAttribute(exchange), exchange); super.handleRequest(exchange); } @Override public String toString() { return "rewrite( '" + getValue().toString() + "' )"; } }; } }; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/cache/000077500000000000000000000000001420065311100265055ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/cache/CacheHandler.java000066400000000000000000000076661420065311100316700ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.cache; import io.undertow.Handlers; import io.undertow.server.ConduitWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.ResponseCodeHandler; import io.undertow.server.handlers.encoding.AllowedContentEncodings; import io.undertow.util.ConduitFactory; import org.xnio.conduits.StreamSinkConduit; import static io.undertow.util.Headers.CONTENT_LENGTH; /** * * Handler that attaches a cache to the exchange, a handler can query this cache to see if the * cache has a cached copy of the content, and if so have the cache serve this content automatically. * * * @author Stuart Douglas */ public class CacheHandler implements HttpHandler { private final DirectBufferCache cache; private volatile HttpHandler next = ResponseCodeHandler.HANDLE_404; public CacheHandler(final DirectBufferCache cache, final HttpHandler next) { this.cache = cache; this.next = next; } public CacheHandler(final DirectBufferCache cache) { this.cache = cache; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final ResponseCache responseCache = new ResponseCache(cache, exchange); exchange.putAttachment(ResponseCache.ATTACHMENT_KEY, responseCache); exchange.addResponseWrapper(new ConduitWrapper() { @Override public StreamSinkConduit wrap(final ConduitFactory factory, final HttpServerExchange exchange) { if(!responseCache.isResponseCachable()) { return factory.create(); } final AllowedContentEncodings contentEncodings = exchange.getAttachment(AllowedContentEncodings.ATTACHMENT_KEY); if(contentEncodings != null) { if(!contentEncodings.isIdentity()) { //we can't cache content encoded responses, as we have no idea how big they will end up being return factory.create(); } } String lengthString = exchange.getResponseHeaders().getFirst(CONTENT_LENGTH); if(lengthString == null) { //we don't cache chunked requests return factory.create(); } int length = Integer.parseInt(lengthString); final CachedHttpRequest key = new CachedHttpRequest(exchange); final DirectBufferCache.CacheEntry entry = cache.add(key, length); if (entry == null || entry.buffers().length == 0 || !entry.claimEnable()) { return factory.create(); } if (!entry.reference()) { entry.disable(); return factory.create(); } return new ResponseCachingStreamSinkConduit(factory.create(), entry, length); } }); next.handleRequest(exchange); } public HttpHandler getNext() { return next; } public CacheHandler setNext(final HttpHandler next) { Handlers.handlerNotNull(next); this.next = next; return this; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/cache/CachedHttpRequest.java000066400000000000000000000116001420065311100327260ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.cache; import java.util.Date; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.encoding.AllowedContentEncodings; import io.undertow.util.DateUtils; import io.undertow.util.ETag; import io.undertow.util.ETagUtils; import io.undertow.util.Headers; /** * @author Stuart Douglas */ public class CachedHttpRequest { private final String path; private final ETag etag; private final String contentEncoding; private final String contentLocation; private final String language; private final String contentType; private final Date lastModified; private final int responseCode; public CachedHttpRequest(final HttpServerExchange exchange) { this.path = exchange.getRequestPath(); this.etag = ETagUtils.getETag(exchange); this.contentLocation = exchange.getResponseHeaders().getFirst(Headers.CONTENT_LOCATION); this.language = exchange.getResponseHeaders().getFirst(Headers.CONTENT_LANGUAGE); this.contentType = exchange.getResponseHeaders().getFirst(Headers.CONTENT_TYPE); String lmString = exchange.getResponseHeaders().getFirst(Headers.LAST_MODIFIED); if (lmString == null) { this.lastModified = null; } else { this.lastModified = DateUtils.parseDate(lmString); } //the content encoding can be decided dynamically, based on the current state of the request //as the decision to compress generally depends on size and mime type final AllowedContentEncodings encoding = exchange.getAttachment(AllowedContentEncodings.ATTACHMENT_KEY); if(encoding != null) { this.contentEncoding = encoding.getCurrentContentEncoding(); } else { this.contentEncoding = exchange.getResponseHeaders().getFirst(Headers.CONTENT_ENCODING); } this.responseCode = exchange.getStatusCode(); } public String getPath() { return path; } public ETag getEtag() { return etag; } public String getContentEncoding() { return contentEncoding; } public String getLanguage() { return language; } public String getContentType() { return contentType; } public Date getLastModified() { return lastModified; } public String getContentLocation() { return contentLocation; } public int getResponseCode() { return responseCode; } @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final CachedHttpRequest that = (CachedHttpRequest) o; if (responseCode != that.responseCode) return false; if (contentEncoding != null ? !contentEncoding.equals(that.contentEncoding) : that.contentEncoding != null) return false; if (contentLocation != null ? !contentLocation.equals(that.contentLocation) : that.contentLocation != null) return false; if (contentType != null ? !contentType.equals(that.contentType) : that.contentType != null) return false; if (etag != null ? !etag.equals(that.etag) : that.etag != null) return false; if (language != null ? !language.equals(that.language) : that.language != null) return false; if (lastModified != null ? !lastModified.equals(that.lastModified) : that.lastModified != null) return false; if (path != null ? !path.equals(that.path) : that.path != null) return false; return true; } @Override public int hashCode() { int result = path != null ? path.hashCode() : 0; result = 31 * result + (etag != null ? etag.hashCode() : 0); result = 31 * result + (contentEncoding != null ? contentEncoding.hashCode() : 0); result = 31 * result + (contentLocation != null ? contentLocation.hashCode() : 0); result = 31 * result + (language != null ? language.hashCode() : 0); result = 31 * result + (contentType != null ? contentType.hashCode() : 0); result = 31 * result + (lastModified != null ? lastModified.hashCode() : 0); result = 31 * result + responseCode; return result; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/cache/DirectBufferCache.java000066400000000000000000000265631420065311100326540ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.cache; import static io.undertow.server.handlers.cache.LimitedBufferSlicePool.PooledByteBuffer; import java.nio.ByteBuffer; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.concurrent.ConcurrentHashMap; import io.undertow.util.ConcurrentDirectDeque; import org.xnio.BufferAllocator; /** * A non-blocking buffer cache where entries are indexed by a path and are made up of a * subsequence of blocks in a fixed large direct buffer. An ideal application is * a file system cache, where the path corresponds to a file location. * *

To reduce contention, entry allocation and eviction execute in a sampling * fashion (entry hits modulo N). Eviction follows an LRU approach (oldest sampled * entries are removed first) when the cache is out of capacity

* *

In order to expedite reclamation, cache entries are reference counted as * opposed to garbage collected.

* * @author Jason T. Greene */ public class DirectBufferCache { private static final int SAMPLE_INTERVAL = 5; private final LimitedBufferSlicePool pool; private final ConcurrentMap cache; private final ConcurrentDirectDeque accessQueue; private final int sliceSize; private final int maxAge; public DirectBufferCache(int sliceSize, int slicesPerPage, int maxMemory) { this(sliceSize, slicesPerPage, maxMemory, BufferAllocator.DIRECT_BYTE_BUFFER_ALLOCATOR); } public DirectBufferCache(int sliceSize, int slicesPerPage, int maxMemory, final BufferAllocator bufferAllocator) { this(sliceSize, slicesPerPage, maxMemory, bufferAllocator, -1); } public DirectBufferCache(int sliceSize, int slicesPerPage, int maxMemory, final BufferAllocator bufferAllocator, int maxAge) { this.sliceSize = sliceSize; this.pool = new LimitedBufferSlicePool(bufferAllocator, sliceSize, sliceSize * slicesPerPage, maxMemory / (sliceSize * slicesPerPage)); this.cache = new ConcurrentHashMap<>(16); this.accessQueue = ConcurrentDirectDeque.newInstance(); this.maxAge = maxAge; } public CacheEntry add(Object key, int size) { return add(key, size, maxAge); } public CacheEntry add(Object key, int size, int maxAge) { CacheEntry value = cache.get(key); if (value == null) { value = new CacheEntry(key, size, this, maxAge); CacheEntry result = cache.putIfAbsent(key, value); if (result != null) { value = result; } else { bumpAccess(value); } } return value; } public CacheEntry get(Object key) { CacheEntry cacheEntry = cache.get(key); if (cacheEntry == null) { return null; } long expires = cacheEntry.getExpires(); if(expires != -1) { if(System.currentTimeMillis() > expires) { remove(key); return null; } } if (cacheEntry.hit() % SAMPLE_INTERVAL == 0) { bumpAccess(cacheEntry); if (! cacheEntry.allocate()) { // Try and make room int reclaimSize = cacheEntry.size(); for (CacheEntry oldest : accessQueue) { if (oldest == cacheEntry) { continue; } if (oldest.buffers().length > 0) { reclaimSize -= oldest.size(); } this.remove(oldest.key()); if (reclaimSize <= 0) { break; } } // Maybe lucky? cacheEntry.allocate(); } } return cacheEntry; } /** * Returns a set of all the keys in the cache. This is a copy of the * key set at the time of method invocation. * * @return all the keys in this cache */ public Set getAllKeys() { return new HashSet<>(cache.keySet()); } private void bumpAccess(CacheEntry cacheEntry) { Object prevToken = cacheEntry.claimToken(); if (!Boolean.FALSE.equals(prevToken)) { if (prevToken != null) { accessQueue.removeToken(prevToken); } Object token = null; try { token = accessQueue.offerLastAndReturnToken(cacheEntry); } catch (Throwable t) { // In case of disaster (OOME), we need to release the claim, so leave it aas null } if (! cacheEntry.setToken(token) && token != null) { // Always set if null accessQueue.removeToken(token); } } } public void remove(Object key) { CacheEntry remove = cache.remove(key); if (remove != null) { Object old = remove.clearToken(); if (old != null) { accessQueue.removeToken(old); } remove.dereference(); } } public static final class CacheEntry { private static final PooledByteBuffer[] EMPTY_BUFFERS = new PooledByteBuffer[0]; private static final PooledByteBuffer[] INIT_BUFFERS = new PooledByteBuffer[0]; private static final Object CLAIM_TOKEN = new Object(); private static final AtomicIntegerFieldUpdater hitsUpdater = AtomicIntegerFieldUpdater.newUpdater(CacheEntry.class, "hits"); private static final AtomicIntegerFieldUpdater refsUpdater = AtomicIntegerFieldUpdater.newUpdater(CacheEntry.class, "refs"); private static final AtomicIntegerFieldUpdater enabledUpdator = AtomicIntegerFieldUpdater.newUpdater(CacheEntry.class, "enabled"); private static final AtomicReferenceFieldUpdater bufsUpdater = AtomicReferenceFieldUpdater.newUpdater(CacheEntry.class, PooledByteBuffer[].class, "buffers"); private static final AtomicReferenceFieldUpdater tokenUpdator = AtomicReferenceFieldUpdater.newUpdater(CacheEntry.class, Object.class, "accessToken"); private final Object key; private final int size; private final DirectBufferCache cache; private final int maxAge; private volatile PooledByteBuffer[] buffers = INIT_BUFFERS; private volatile int refs = 1; private volatile int hits = 1; private volatile Object accessToken; private volatile int enabled; private volatile long expires = -1; private CacheEntry(Object key, int size, DirectBufferCache cache, final int maxAge) { this.key = key; this.size = size; this.cache = cache; this.maxAge = maxAge; } public int size() { return size; } public PooledByteBuffer[] buffers() { return buffers; } public int hit() { for (;;) { int i = hits; if (hitsUpdater.weakCompareAndSet(this, i, ++i)) { return i; } } } public Object key() { return key; } public boolean enabled() { return enabled == 2; } public void enable() { if(maxAge == -1) { this.expires = -1; } else { this.expires = System.currentTimeMillis() + maxAge; } this.enabled = 2; } public void disable() { this.enabled = 0; } public boolean claimEnable() { return enabledUpdator.compareAndSet(this, 0, 1); } public boolean reference() { for(;;) { int refs = this.refs; if (refs < 1) { return false; // destroying } if (refsUpdater.compareAndSet(this, refs++, refs)) { return true; } } } public boolean dereference() { for(;;) { int refs = this.refs; if (refs < 1) { return false; // destroying } if (refsUpdater.compareAndSet(this, refs--, refs)) { if (refs == 0) { destroy(); } return true; } } } public boolean allocate() { if (buffers.length > 0) return true; if (! bufsUpdater.compareAndSet(this, INIT_BUFFERS, EMPTY_BUFFERS)) { return true; } int reserveSize = size; int n = 1; DirectBufferCache bufferCache = cache; while ((reserveSize -= bufferCache.sliceSize) > 0) { n++; } // Try to avoid mutations LimitedBufferSlicePool slicePool = bufferCache.pool; if (! slicePool.canAllocate(n)) { this.buffers = INIT_BUFFERS; return false; } PooledByteBuffer[] buffers = new PooledByteBuffer[n]; for (int i = 0; i < n; i++) { PooledByteBuffer allocate = slicePool.allocate(); if (allocate == null) { while (--i >= 0) { buffers[i].free(); } this.buffers = INIT_BUFFERS; return false; } buffers[i] = allocate; } this.buffers = buffers; return true; } private void destroy() { this.buffers = EMPTY_BUFFERS; for (PooledByteBuffer buffer : buffers) { buffer.free(); } } Object claimToken() { for (;;) { Object current = this.accessToken; if (current == CLAIM_TOKEN) { return Boolean.FALSE; } if (tokenUpdator.compareAndSet(this, current, CLAIM_TOKEN)) { return current; } } } boolean setToken(Object token) { return tokenUpdator.compareAndSet(this, CLAIM_TOKEN, token); } Object clearToken() { Object old = tokenUpdator.getAndSet(this, null); return old == CLAIM_TOKEN ? null : old; } long getExpires() { return expires; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/cache/LRUCache.java000066400000000000000000000155261420065311100307470ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.cache; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import io.undertow.util.ConcurrentDirectDeque; /** * A non-blocking cache where entries are indexed by a key. *

*

To reduce contention, entry allocation and eviction execute in a sampling * fashion (entry hits modulo N). Eviction follows an LRU approach (oldest sampled * entries are removed first) when the cache is out of capacity.

*

* * This cache can also be configured to run in FIFO mode, rather than LRU. * * @author Jason T. Greene * @author Stuart Douglas */ public class LRUCache { private static final int SAMPLE_INTERVAL = 5; /** * Max active entries that are present in the cache. */ private final int maxEntries; private final ConcurrentMap> cache; private final ConcurrentDirectDeque> accessQueue; /** * How long an item can stay in the cache in milliseconds */ private final int maxAge; private final boolean fifo; public LRUCache(int maxEntries, final int maxAge) { this.maxAge = maxAge; this.cache = new ConcurrentHashMap<>(16); this.accessQueue = ConcurrentDirectDeque.newInstance(); this.maxEntries = maxEntries; this.fifo = false; } public LRUCache(int maxEntries, final int maxAge, boolean fifo) { this.maxAge = maxAge; this.cache = new ConcurrentHashMap<>(16); this.accessQueue = ConcurrentDirectDeque.newInstance(); this.maxEntries = maxEntries; this.fifo = fifo; } public void add(K key, V newValue) { CacheEntry value = cache.get(key); if (value == null) { long expires; if(maxAge == -1) { expires = -1; } else { expires = System.currentTimeMillis() + maxAge; } value = new CacheEntry<>(key, newValue, expires); CacheEntry result = cache.putIfAbsent(key, value); if (result != null) { value = result; value.setValue(newValue); } bumpAccess(value); if (cache.size() > maxEntries) { //remove the oldest CacheEntry oldest = accessQueue.poll(); if (oldest != value) { this.remove(oldest.key()); } } } } public V get(K key) { CacheEntry cacheEntry = cache.get(key); if (cacheEntry == null) { return null; } long expires = cacheEntry.getExpires(); if(expires != -1) { if(System.currentTimeMillis() > expires) { remove(key); return null; } } if(!fifo) { if (cacheEntry.hit() % SAMPLE_INTERVAL == 0) { bumpAccess(cacheEntry); } } return cacheEntry.getValue(); } private void bumpAccess(CacheEntry cacheEntry) { Object prevToken = cacheEntry.claimToken(); if (!Boolean.FALSE.equals(prevToken)) { if (prevToken != null) { accessQueue.removeToken(prevToken); } Object token = null; try { token = accessQueue.offerLastAndReturnToken(cacheEntry); } catch (Throwable t) { // In case of disaster (OOME), we need to release the claim, so leave it aas null } if (!cacheEntry.setToken(token) && token != null) { // Always set if null accessQueue.removeToken(token); } } } public V remove(K key) { CacheEntry remove = cache.remove(key); if (remove != null) { Object old = remove.clearToken(); if (old != null) { accessQueue.removeToken(old); } return remove.getValue(); } else { return null; } } public void clear() { cache.clear(); accessQueue.clear(); } public static final class CacheEntry { private static final Object CLAIM_TOKEN = new Object(); private static final AtomicIntegerFieldUpdater hitsUpdater = AtomicIntegerFieldUpdater.newUpdater(CacheEntry.class, "hits"); private static final AtomicReferenceFieldUpdater tokenUpdator = AtomicReferenceFieldUpdater.newUpdater(CacheEntry.class, Object.class, "accessToken"); private final K key; private volatile V value; private final long expires; private volatile int hits = 1; private volatile Object accessToken; private CacheEntry(K key, V value, final long expires) { this.key = key; this.value = value; this.expires = expires; } public void setValue(final V value) { this.value = value; } public V getValue() { return value; } public int hit() { for (; ; ) { int i = hits; if (hitsUpdater.weakCompareAndSet(this, i, ++i)) { return i; } } } public K key() { return key; } Object claimToken() { for (; ; ) { Object current = this.accessToken; if (current == CLAIM_TOKEN) { return Boolean.FALSE; } if (tokenUpdator.compareAndSet(this, current, CLAIM_TOKEN)) { return current; } } } boolean setToken(Object token) { return tokenUpdator.compareAndSet(this, CLAIM_TOKEN, token); } Object clearToken() { Object old = tokenUpdator.getAndSet(this, null); return old == CLAIM_TOKEN ? null : old; } public long getExpires() { return expires; } } } LimitedBufferSlicePool.java000066400000000000000000000154171420065311100336340ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/cache/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.cache; import org.xnio.BufferAllocator; import java.nio.ByteBuffer; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; /** * A limited buffer pooled allocator. This pool uses a series of buffer regions to back the * returned pooled buffers. When the buffer is no longer needed, it should be freed back into the pool; failure * to do so will cause the corresponding buffer area to be unavailable until the buffer is garbage-collected. * * @author David M. Lloyd * @author Jason T. Greene */ public final class LimitedBufferSlicePool { private static final AtomicIntegerFieldUpdater regionUpdater = AtomicIntegerFieldUpdater.newUpdater(LimitedBufferSlicePool.class, "regionsUsed"); private final Queue sliceQueue = new ConcurrentLinkedQueue<>(); private final BufferAllocator allocator; private final int bufferSize; private final int buffersPerRegion; private final int maxRegions; private volatile int regionsUsed; /** * Construct a new instance. * * @param allocator the buffer allocator to use * @param bufferSize the size of each buffer * @param maxRegionSize the maximum region size for each backing buffer * @param maxRegions the maximum regions to create, zero for unlimited */ public LimitedBufferSlicePool(final BufferAllocator allocator, final int bufferSize, final int maxRegionSize, final int maxRegions) { if (bufferSize <= 0) { throw new IllegalArgumentException("Buffer size must be greater than zero"); } if (maxRegionSize < bufferSize) { throw new IllegalArgumentException("Maximum region size must be greater than or equal to the buffer size"); } buffersPerRegion = maxRegionSize / bufferSize; this.bufferSize = bufferSize; this.allocator = allocator; this.maxRegions = maxRegions; } /** * Construct a new instance. * * @param allocator the buffer allocator to use * @param bufferSize the size of each buffer * @param maxRegionSize the maximum region size for each backing buffer */ public LimitedBufferSlicePool(BufferAllocator allocator, int bufferSize, int maxRegionSize) { this(allocator, bufferSize, maxRegionSize, 0); } /** * Construct a new instance, using a direct buffer allocator. * * @param bufferSize the size of each buffer * @param maxRegionSize the maximum region size for each backing buffer */ public LimitedBufferSlicePool(final int bufferSize, final int maxRegionSize) { this(BufferAllocator.DIRECT_BYTE_BUFFER_ALLOCATOR, bufferSize, maxRegionSize); } /** * Allocates a new byte buffer if possible * * @return new buffer or null if none available **/ public PooledByteBuffer allocate() { final Queue sliceQueue = this.sliceQueue; final Slice slice = sliceQueue.poll(); if (slice == null && (maxRegions <= 0 || regionUpdater.getAndIncrement(this) < maxRegions)) { final int bufferSize = this.bufferSize; final int buffersPerRegion = this.buffersPerRegion; final ByteBuffer region = allocator.allocate(buffersPerRegion * bufferSize); int idx = bufferSize; for (int i = 1; i < buffersPerRegion; i ++) { sliceQueue.add(new Slice(region, idx, bufferSize)); idx += bufferSize; } final Slice newSlice = new Slice(region, 0, bufferSize); return new PooledByteBuffer(newSlice, newSlice.slice(), sliceQueue); } if (slice == null) { return null; } return new PooledByteBuffer(slice, slice.slice(), sliceQueue); } public boolean canAllocate(int slices) { if (regionsUsed < maxRegions) return true; if (sliceQueue.isEmpty()) return false; Iterator iterator = sliceQueue.iterator(); for (int i = 0; i < slices; i++) { if (! iterator.hasNext()) { return false; } try { iterator.next(); } catch (NoSuchElementException e) { return false; } } return true; } public static final class PooledByteBuffer { private final Slice region; private final Queue slices; volatile ByteBuffer buffer; private static final AtomicReferenceFieldUpdater bufferUpdater = AtomicReferenceFieldUpdater.newUpdater(PooledByteBuffer.class, ByteBuffer.class, "buffer"); private PooledByteBuffer(final Slice region, final ByteBuffer buffer, final Queue slices) { this.region = region; this.buffer = buffer; this.slices = slices; } public void free() { if (bufferUpdater.getAndSet(this, null) != null) { // trust the user, repool the buffer slices.add(region); } } public ByteBuffer getBuffer() { final ByteBuffer buffer = this.buffer; if (buffer == null) { throw new IllegalStateException(); } return buffer; } public String toString() { return "Pooled buffer " + buffer; } } private static final class Slice { private final ByteBuffer parent; private final int start; private final int size; private Slice(final ByteBuffer parent, final int start, final int size) { this.parent = parent; this.start = start; this.size = size; } ByteBuffer slice() { return ((ByteBuffer)parent.duplicate().position(start).limit(start+size)).slice(); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/cache/ResponseCache.java000066400000000000000000000210301420065311100320660ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.cache; import java.io.IOException; import java.nio.ByteBuffer; import io.undertow.UndertowLogger; import io.undertow.io.IoCallback; import io.undertow.io.Sender; import io.undertow.server.HttpServerExchange; import io.undertow.util.AttachmentKey; import io.undertow.util.DateUtils; import io.undertow.util.ETag; import io.undertow.util.ETagUtils; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.StatusCodes; import static io.undertow.util.Methods.GET; import static io.undertow.util.Methods.HEAD; /** * Facade for an underlying buffer cache that contains response information. *

* This facade is attached to the exchange and provides a mechanism for handlers to * serve cached content. By default a request to serve cached content is interpreted * to mean that the resulting response is cacheable, and so by default this will result * in the current response being cached (as long as it meets the criteria for caching). *

* Calling tryServeResponse can also result in the exchange being ended with a not modified * response code, if the response headers indicate that this is justified (e.g. if the * If-Modified-Since or If-None-Match headers indicate that the client has a cached copy * of the response) *

* This should be installed early in the handler chain, before any content encoding handlers. * This allows it to cache compressed copies of the response, which can significantly reduce * CPU load. *

* NOTE: This cache has no concept of authentication, it assumes that if the underlying handler * indicates that a response is cachable, then the current user has been properly authenticated * to access that resource, and that the resource will not change per user. * * @author Stuart Douglas */ public class ResponseCache { public static final AttachmentKey ATTACHMENT_KEY = AttachmentKey.create(ResponseCache.class); private final DirectBufferCache cache; private final HttpServerExchange exchange; private boolean responseCachable; public ResponseCache(final DirectBufferCache cache, final HttpServerExchange exchange) { this.cache = cache; this.exchange = exchange; } /** * Attempts to serve the response from a cache. *

* If this fails, then the response will be considered cachable, and may be cached * to be served by future handlers. *

* If this returns true then the caller should not modify the exchange any more, as this * can result in a handoff to an IO thread * * @return true if serving succeeded, */ public boolean tryServeResponse() { return tryServeResponse(true); } /** * Attempts to serve the response from a cache. *

* If this fails, and the markCachable parameter is true then the response will be considered cachable, * and may be cached to be served by future handlers. *

* If this returns true then the caller should not modify the exchange any more, as this * can result in a handoff to an IO thread * * @param markCacheable If this is true then the resulting response will be considered cachable * @return true if serving succeeded, */ public boolean tryServeResponse(boolean markCacheable) { final CachedHttpRequest key = new CachedHttpRequest(exchange); DirectBufferCache.CacheEntry entry = cache.get(key); //we only cache get and head requests if (!exchange.getRequestMethod().equals(GET) && !exchange.getRequestMethod().equals(HEAD)) { return false; } if (entry == null) { this.responseCachable = markCacheable; return false; } // It's loading retry later if (!entry.enabled() || !entry.reference()) { this.responseCachable = markCacheable; return false; } CachedHttpRequest existingKey = (CachedHttpRequest) entry.key(); //if any of the header matches fail we just return //we don't can the request, as it is possible the underlying handler //may have additional etags final ETag etag = existingKey.getEtag(); if (!ETagUtils.handleIfMatch(exchange, etag, false)) { return false; } //we do send a 304 if the if-none-match header matches if (!ETagUtils.handleIfNoneMatch(exchange, etag, true)) { exchange.setStatusCode(StatusCodes.NOT_MODIFIED); exchange.endExchange(); return true; } //the server may have a more up to date representation if (!DateUtils.handleIfUnmodifiedSince(exchange, existingKey.getLastModified())) { return false; } if (!DateUtils.handleIfModifiedSince(exchange, existingKey.getLastModified())) { exchange.setStatusCode(StatusCodes.NOT_MODIFIED); exchange.endExchange(); return true; } //we are going to proceed. Set the appropriate headers if(existingKey.getContentType() != null) { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, existingKey.getContentType()); } if(existingKey.getContentEncoding() != null && !Headers.IDENTITY.equals(HttpString.tryFromString(existingKey.getContentEncoding()))) { exchange.getResponseHeaders().put(Headers.CONTENT_ENCODING, existingKey.getContentEncoding()); } if(existingKey.getLastModified() != null) { exchange.getResponseHeaders().put(Headers.LAST_MODIFIED, DateUtils.toDateString(existingKey.getLastModified())); } if(existingKey.getContentLocation() != null) { exchange.getResponseHeaders().put(Headers.CONTENT_LOCATION, existingKey.getContentLocation()); } if(existingKey.getLanguage() != null) { exchange.getResponseHeaders().put(Headers.CONTENT_LANGUAGE, existingKey.getLanguage()); } if(etag != null) { exchange.getResponseHeaders().put(Headers.CONTENT_LANGUAGE, etag.toString()); } //TODO: support if-range exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, Long.toString(entry.size())); if (exchange.getRequestMethod().equals(HEAD)) { exchange.endExchange(); return true; } final ByteBuffer[] buffers; boolean ok = false; try { LimitedBufferSlicePool.PooledByteBuffer[] pooled = entry.buffers(); buffers = new ByteBuffer[pooled.length]; for (int i = 0; i < buffers.length; i++) { // Keep position from mutating buffers[i] = pooled[i].getBuffer().duplicate(); } ok = true; } finally { if (!ok) { entry.dereference(); } } // Transfer Inline, or register and continue transfer // Pass off the entry dereference call to the listener exchange.getResponseSender().send(buffers, new DereferenceCallback(entry)); return true; } boolean isResponseCachable() { return responseCachable; } private static class DereferenceCallback implements IoCallback { private final DirectBufferCache.CacheEntry entry; DereferenceCallback(DirectBufferCache.CacheEntry entry) { this.entry = entry; } @Override public void onComplete(final HttpServerExchange exchange, final Sender sender) { entry.dereference(); exchange.endExchange(); } @Override public void onException(final HttpServerExchange exchange, final Sender sender, final IOException exception) { UndertowLogger.REQUEST_IO_LOGGER.ioException(exception); entry.dereference(); exchange.endExchange(); } } } ResponseCachingSender.java000066400000000000000000000127601420065311100335130ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/cache/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.cache; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import io.undertow.io.IoCallback; import io.undertow.io.Sender; import org.xnio.Buffers; /** * @author Stuart Douglas */ public class ResponseCachingSender implements Sender { private final Sender delegate; private final DirectBufferCache.CacheEntry cacheEntry; private final long length; private long written; public ResponseCachingSender(final Sender delegate, final DirectBufferCache.CacheEntry cacheEntry, final long length) { this.delegate = delegate; this.cacheEntry = cacheEntry; this.length = length; } @Override public void send(final ByteBuffer src, final IoCallback callback) { ByteBuffer origSrc = src.duplicate(); handleUpdate(origSrc); delegate.send(src, callback); } @Override public void send(final ByteBuffer[] srcs, final IoCallback callback) { ByteBuffer[] origSrc = new ByteBuffer[srcs.length]; long total = 0; for (int i = 0; i < srcs.length; i++) { origSrc[i] = srcs[i].duplicate(); total += origSrc[i].remaining(); } handleUpdate(origSrc, total); delegate.send(srcs, callback); } @Override public void send(final ByteBuffer src) { ByteBuffer origSrc = src.duplicate(); handleUpdate(origSrc); delegate.send(src); } @Override public void send(final ByteBuffer[] srcs) { ByteBuffer[] origSrc = new ByteBuffer[srcs.length]; long total = 0; for (int i = 0; i < srcs.length; i++) { origSrc[i] = srcs[i].duplicate(); total += origSrc[i].remaining(); } handleUpdate(origSrc, total); delegate.send(srcs); } @Override public void send(final String data, final IoCallback callback) { handleUpdate(ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8))); delegate.send(data, callback); } @Override public void send(final String data, final Charset charset, final IoCallback callback) { handleUpdate(ByteBuffer.wrap(data.getBytes(charset))); delegate.send(data, charset, callback); } @Override public void send(final String data) { handleUpdate(ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8))); delegate.send(data); } @Override public void send(final String data, final Charset charset) { handleUpdate(ByteBuffer.wrap(data.getBytes(charset))); delegate.send(data, charset); } @Override public void transferFrom(FileChannel channel, IoCallback callback) { // Transfer never caches delegate.transferFrom(channel, callback); } @Override public void close(final IoCallback callback) { if (written != length) { cacheEntry.disable(); cacheEntry.dereference(); } delegate.close(); } @Override public void close() { if (written != length) { cacheEntry.disable(); cacheEntry.dereference(); } delegate.close(); } private void handleUpdate(final ByteBuffer origSrc) { LimitedBufferSlicePool.PooledByteBuffer[] pooled = cacheEntry.buffers(); ByteBuffer[] buffers = new ByteBuffer[pooled.length]; for (int i = 0; i < buffers.length; i++) { buffers[i] = pooled[i].getBuffer(); } written += Buffers.copy(buffers, 0, buffers.length, origSrc); if (written == length) { for (ByteBuffer buffer : buffers) { //prepare buffers for reading buffer.flip(); } cacheEntry.enable(); } } private void handleUpdate(final ByteBuffer[] origSrc, long totalWritten) { LimitedBufferSlicePool.PooledByteBuffer[] pooled = cacheEntry.buffers(); ByteBuffer[] buffers = new ByteBuffer[pooled.length]; for (int i = 0; i < buffers.length; i++) { buffers[i] = pooled[i].getBuffer(); } long leftToCopy = totalWritten; for (int i = 0; i < origSrc.length; ++i) { ByteBuffer buf = origSrc[i]; if (buf.remaining() > leftToCopy) { buf.limit((int) (buf.position() + leftToCopy)); } leftToCopy -= buf.remaining(); Buffers.copy(buffers, 0, buffers.length, buf); if (leftToCopy == 0) { break; } } written += totalWritten; if (written == length) { for (ByteBuffer buffer : buffers) { //prepare buffers for reading buffer.flip(); } cacheEntry.enable(); } } } ResponseCachingStreamSinkConduit.java000066400000000000000000000125751420065311100357050ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/cache/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.cache; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import org.xnio.Buffers; import org.xnio.IoUtils; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.AbstractStreamSinkConduit; import org.xnio.conduits.ConduitWritableByteChannel; import org.xnio.conduits.Conduits; import org.xnio.conduits.StreamSinkConduit; /** * @author Stuart Douglas */ public class ResponseCachingStreamSinkConduit extends AbstractStreamSinkConduit { private final DirectBufferCache.CacheEntry cacheEntry; private final long length; private long written; /** * Construct a new instance. * * @param next the delegate conduit to set * @param cacheEntry * @param length */ public ResponseCachingStreamSinkConduit(final StreamSinkConduit next, final DirectBufferCache.CacheEntry cacheEntry, final long length) { super(next); this.cacheEntry = cacheEntry; this.length = length; for(LimitedBufferSlicePool.PooledByteBuffer buffer: cacheEntry.buffers()) { buffer.getBuffer().clear(); } } @Override public long transferFrom(final FileChannel src, final long position, final long count) throws IOException { return src.transferTo(position, count, new ConduitWritableByteChannel(this)); } @Override public long transferFrom(final StreamSourceChannel source, final long count, final ByteBuffer throughBuffer) throws IOException { return IoUtils.transfer(source, count, throughBuffer, new ConduitWritableByteChannel(this)); } @Override public int write(final ByteBuffer src) throws IOException { ByteBuffer origSrc = src.duplicate(); int totalWritten = super.write(src); if(totalWritten > 0) { LimitedBufferSlicePool.PooledByteBuffer[] pooled = cacheEntry.buffers(); ByteBuffer[] buffers = new ByteBuffer[pooled.length]; for (int i = 0; i < buffers.length; i++) { buffers[i] = pooled[i].getBuffer(); } origSrc.limit(origSrc.position() + totalWritten); written += Buffers.copy(buffers, 0, buffers.length, origSrc); if (written == length) { for (ByteBuffer buffer : buffers) { //prepare buffers for reading buffer.flip(); } cacheEntry.enable(); } } return totalWritten; } @Override public long write(final ByteBuffer[] srcs, final int offs, final int len) throws IOException { ByteBuffer[] origSrc = new ByteBuffer[srcs.length]; for (int i = 0; i < srcs.length; i++) { origSrc[i] = srcs[i].duplicate(); } long totalWritten = super.write(srcs, offs, len); if(totalWritten > 0) { LimitedBufferSlicePool.PooledByteBuffer[] pooled = cacheEntry.buffers(); ByteBuffer[] buffers = new ByteBuffer[pooled.length]; for (int i = 0; i < buffers.length; i++) { buffers[i] = pooled[i].getBuffer(); } long leftToCopy = totalWritten; for(int i = 0; i < len; ++i) { ByteBuffer buf = origSrc[offs + i]; if(buf.remaining() > leftToCopy) { buf.limit((int) (buf.position() + leftToCopy)); } leftToCopy -= buf.remaining(); Buffers.copy(buffers, 0, buffers.length, buf); if(leftToCopy == 0) { break; } } written += totalWritten; if (written == length) { for (ByteBuffer buffer : buffers) { //prepare buffers for reading buffer.flip(); } cacheEntry.enable(); } } return totalWritten; } @Override public void terminateWrites() throws IOException { if (written != length) { cacheEntry.disable(); cacheEntry.dereference(); } super.terminateWrites(); } @Override public void truncateWrites() throws IOException { if (written != length) { cacheEntry.disable(); cacheEntry.dereference(); } super.truncateWrites(); } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { return Conduits.writeFinalBasic(this, srcs, offset, length); } @Override public int writeFinal(ByteBuffer src) throws IOException { return Conduits.writeFinalBasic(this, src); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/encoding/000077500000000000000000000000001420065311100272305ustar00rootroot00000000000000AllowedContentEncodings.java000066400000000000000000000076531420065311100346030ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/encoding/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.encoding; import java.util.List; import io.undertow.server.ConduitWrapper; import io.undertow.server.HttpServerExchange; import io.undertow.util.AttachmentKey; import io.undertow.util.ConduitFactory; import io.undertow.util.Headers; import io.undertow.util.Methods; import io.undertow.util.StatusCodes; import org.xnio.conduits.StreamSinkConduit; /** * An attachment that provides information about the current content encoding that will be chosen for the response * * @author Stuart Douglas */ public class AllowedContentEncodings implements ConduitWrapper { public static final AttachmentKey ATTACHMENT_KEY = AttachmentKey.create(AllowedContentEncodings.class); private final HttpServerExchange exchange; private final List encodings; public AllowedContentEncodings(final HttpServerExchange exchange, final List encodings) { this.exchange = exchange; this.encodings = encodings; } /** * @return The content encoding that will be set, given the current state of the HttpServerExchange */ public String getCurrentContentEncoding() { for (EncodingMapping encoding : encodings) { if (encoding.getAllowed() == null || encoding.getAllowed().resolve(exchange)) { return encoding.getName(); } } return Headers.IDENTITY.toString(); } public EncodingMapping getEncoding() { for (EncodingMapping encoding : encodings) { if (encoding.getAllowed() == null || encoding.getAllowed().resolve(exchange)) { return encoding; } } return null; } public boolean isIdentity() { return getCurrentContentEncoding().equals(Headers.IDENTITY.toString()); } /** * If the list of allowed encodings was empty then it means that no encodings were allowed, and * identity was explicitly prohibited with a q value of 0. */ public boolean isNoEncodingsAllowed() { return encodings.isEmpty(); } @Override public StreamSinkConduit wrap(final ConduitFactory factory, final HttpServerExchange exchange) { if (exchange.getResponseHeaders().contains(Headers.CONTENT_ENCODING)) { //already encoded return factory.create(); } //if this is a zero length response we don't want to encode if (exchange.getResponseContentLength() != 0 && exchange.getStatusCode() != StatusCodes.NO_CONTENT && exchange.getStatusCode() != StatusCodes.NOT_MODIFIED) { EncodingMapping encoding = getEncoding(); if (encoding != null) { exchange.getResponseHeaders().put(Headers.CONTENT_ENCODING, encoding.getName()); if (exchange.getRequestMethod().equals(Methods.HEAD)) { //we don't create an actual encoder for HEAD requests, but we set the header return factory.create(); } else { return encoding.getEncoding().getResponseWrapper().wrap(factory, exchange); } } } return factory.create(); } } ContentEncodedResource.java000066400000000000000000000024631420065311100344250ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/encoding/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.encoding; import io.undertow.server.handlers.resource.Resource; /** * A resource that has been pre-compressed * * @author Stuart Douglas */ public class ContentEncodedResource { private final Resource resource; private final String contentEncoding; public ContentEncodedResource(Resource resource, String contentEncoding) { this.resource = resource; this.contentEncoding = contentEncoding; } public Resource getResource() { return resource; } public String getContentEncoding() { return contentEncoding; } } ContentEncodedResourceManager.java000066400000000000000000000251221420065311100357150ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/encoding/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.encoding; import io.undertow.UndertowLogger; import io.undertow.predicate.Predicate; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.resource.CachingResourceManager; import io.undertow.server.handlers.resource.Resource; import io.undertow.util.ImmediateConduitFactory; import org.xnio.IoUtils; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.ConduitStreamSinkChannel; import org.xnio.conduits.Conduits; import org.xnio.conduits.StreamSinkConduit; import org.xnio.conduits.WriteReadyHandler; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; /** * Class that provides a way of serving pre-encoded resources. * * @author Stuart Douglas */ public class ContentEncodedResourceManager { private final Path encodedResourcesRoot; private final CachingResourceManager encoded; private final ContentEncodingRepository contentEncodingRepository; private final int minResourceSize; private final int maxResourceSize; private final Predicate encodingAllowed; private final ConcurrentMap fileLocks = new ConcurrentHashMap<>(); public ContentEncodedResourceManager(Path encodedResourcesRoot, CachingResourceManager encodedResourceManager, ContentEncodingRepository contentEncodingRepository, int minResourceSize, int maxResourceSize, Predicate encodingAllowed) { this.encodedResourcesRoot = encodedResourcesRoot; this.encoded = encodedResourceManager; this.contentEncodingRepository = contentEncodingRepository; this.minResourceSize = minResourceSize; this.maxResourceSize = maxResourceSize; this.encodingAllowed = encodingAllowed; } /** * Gets a pre-encoded resource. *

* TODO: blocking / non-blocking semantics * * @param resource * @param exchange * @return * @throws IOException */ public ContentEncodedResource getResource(final Resource resource, final HttpServerExchange exchange) throws IOException { final String path = resource.getPath(); Path file = resource.getFilePath(); if (file == null) { return null; } if (minResourceSize > 0 && resource.getContentLength() < minResourceSize || maxResourceSize > 0 && resource.getContentLength() > maxResourceSize || !(encodingAllowed == null || encodingAllowed.resolve(exchange))) { return null; } AllowedContentEncodings encodings = contentEncodingRepository.getContentEncodings(exchange); if (encodings == null || encodings.isNoEncodingsAllowed()) { return null; } EncodingMapping encoding = encodings.getEncoding(); if (encoding == null || encoding.getName().equals(ContentEncodingRepository.IDENTITY)) { return null; } String newPath = path + ".undertow.encoding." + encoding.getName(); Resource preCompressed = encoded.getResource(newPath); if (preCompressed != null) { return new ContentEncodedResource(preCompressed, encoding.getName()); } final LockKey key = new LockKey(path, encoding.getName()); if (fileLocks.putIfAbsent(key, this) != null) { //another thread is already compressing //we don't do anything fancy here, just return and serve non-compressed content return null; } FileChannel targetFileChannel = null; FileChannel sourceFileChannel = null; try { //double check, the compressing thread could have finished just before we acquired the lock preCompressed = encoded.getResource(newPath); if (preCompressed != null) { return new ContentEncodedResource(preCompressed, encoding.getName()); } final Path finalTarget = encodedResourcesRoot.resolve(newPath); final Path tempTarget = encodedResourcesRoot.resolve(newPath); //horrible hack to work around XNIO issue OutputStream tmp = Files.newOutputStream(tempTarget); try { tmp.close(); } finally { IoUtils.safeClose(tmp); } targetFileChannel = FileChannel.open(tempTarget, StandardOpenOption.READ, StandardOpenOption.WRITE); sourceFileChannel = FileChannel.open(file, StandardOpenOption.READ); StreamSinkConduit conduit = encoding.getEncoding().getResponseWrapper().wrap(new ImmediateConduitFactory(new FileConduitTarget(targetFileChannel, exchange)), exchange); final ConduitStreamSinkChannel targetChannel = new ConduitStreamSinkChannel(null, conduit); long transferred = sourceFileChannel.transferTo(0, resource.getContentLength(), targetChannel); targetChannel.shutdownWrites(); org.xnio.channels.Channels.flushBlocking(targetChannel); if (transferred != resource.getContentLength()) { UndertowLogger.REQUEST_LOGGER.failedToWritePreCachedFile(); } Files.move(tempTarget, finalTarget); encoded.invalidate(newPath); final Resource encodedResource = encoded.getResource(newPath); return new ContentEncodedResource(encodedResource, encoding.getName()); } finally { IoUtils.safeClose(targetFileChannel); IoUtils.safeClose(sourceFileChannel); fileLocks.remove(key); } } private static final class LockKey { private final String path; private final String encoding; private LockKey(String path, String encoding) { this.path = path; this.encoding = encoding; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; LockKey lockKey = (LockKey) o; if (encoding != null ? !encoding.equals(lockKey.encoding) : lockKey.encoding != null) return false; if (path != null ? !path.equals(lockKey.path) : lockKey.path != null) return false; return true; } @Override public int hashCode() { int result = path != null ? path.hashCode() : 0; result = 31 * result + (encoding != null ? encoding.hashCode() : 0); return result; } } private static final class FileConduitTarget implements StreamSinkConduit { private final FileChannel fileChannel; private final HttpServerExchange exchange; private WriteReadyHandler writeReadyHandler; private boolean writesResumed = false; private FileConduitTarget(FileChannel fileChannel, HttpServerExchange exchange) { this.fileChannel = fileChannel; this.exchange = exchange; } @Override public long transferFrom(FileChannel fileChannel, long l, long l2) throws IOException { return this.fileChannel.transferFrom(fileChannel, l, l2); } @Override public long transferFrom(StreamSourceChannel streamSourceChannel, long l, ByteBuffer byteBuffer) throws IOException { return IoUtils.transfer(streamSourceChannel, l, byteBuffer, fileChannel); } @Override public int write(ByteBuffer byteBuffer) throws IOException { return fileChannel.write(byteBuffer); } @Override public long write(ByteBuffer[] byteBuffers, int i, int i2) throws IOException { return fileChannel.write(byteBuffers, i, i2); } @Override public int writeFinal(ByteBuffer src) throws IOException { return Conduits.writeFinalBasic(this, src); } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { return Conduits.writeFinalBasic(this, srcs, offset, length); } @Override public void terminateWrites() throws IOException { fileChannel.close(); } @Override public boolean isWriteShutdown() { return !fileChannel.isOpen(); } @Override public void resumeWrites() { wakeupWrites(); } @Override public void suspendWrites() { writesResumed = false; } @Override public void wakeupWrites() { if (writeReadyHandler != null) { writesResumed = true; while (writesResumed && writeReadyHandler != null) { writeReadyHandler.writeReady(); } } } @Override public boolean isWriteResumed() { return writesResumed; } @Override public void awaitWritable() throws IOException { } @Override public void awaitWritable(long l, TimeUnit timeUnit) throws IOException { } @Override public XnioIoThread getWriteThread() { return exchange.getIoThread(); } @Override public void setWriteReadyHandler(WriteReadyHandler writeReadyHandler) { this.writeReadyHandler = writeReadyHandler; } @Override public void truncateWrites() throws IOException { fileChannel.close(); } @Override public boolean flush() throws IOException { return true; } @Override public XnioWorker getWorker() { return exchange.getConnection().getWorker(); } } } ContentEncodingProvider.java000066400000000000000000000031301420065311100346050ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/encoding/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.encoding; import io.undertow.server.ConduitWrapper; import io.undertow.server.HttpServerExchange; import io.undertow.util.ConduitFactory; import org.xnio.conduits.StreamSinkConduit; /** * @author Stuart Douglas */ public interface ContentEncodingProvider { ContentEncodingProvider IDENTITY = new ContentEncodingProvider() { private final ConduitWrapper CONDUIT_WRAPPER = new ConduitWrapper() { @Override public StreamSinkConduit wrap(final ConduitFactory factory, final HttpServerExchange exchange) { return factory.create(); } }; @Override public ConduitWrapper getResponseWrapper() { return CONDUIT_WRAPPER; } }; ConduitWrapper getResponseWrapper(); } ContentEncodingRepository.java000066400000000000000000000103121420065311100351720ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/encoding/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.encoding; import io.undertow.predicate.Predicate; import io.undertow.predicate.Predicates; import io.undertow.server.HttpServerExchange; import io.undertow.util.CopyOnWriteMap; import io.undertow.util.Headers; import io.undertow.util.QValueParser; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; /** * * * @author Stuart Douglas */ public class ContentEncodingRepository { public static final String IDENTITY = "identity"; public static final EncodingMapping IDENTITY_ENCODING = new EncodingMapping(IDENTITY, ContentEncodingProvider.IDENTITY, 0, Predicates.truePredicate()); private final Map encodingMap = new CopyOnWriteMap<>(); public AllowedContentEncodings getContentEncodings(final HttpServerExchange exchange) { final List res = exchange.getRequestHeaders().get(Headers.ACCEPT_ENCODING); if (res == null || res.isEmpty()) { return null; } final List resultingMappings = new ArrayList<>(); final List> found = QValueParser.parse(res); for (List result : found) { List available = new ArrayList<>(); boolean includesIdentity = false; boolean isQValue0 = false; for (final QValueParser.QValueResult value : result) { EncodingMapping encoding; if (value.getValue().equals("*")) { includesIdentity = true; encoding = IDENTITY_ENCODING; } else { encoding = encodingMap.get(value.getValue()); if(encoding == null && IDENTITY.equals(value.getValue())) { encoding = IDENTITY_ENCODING; } } if (value.isQValueZero()) { isQValue0 = true; } if (encoding != null) { available.add(encoding); } } if (isQValue0) { if (resultingMappings.isEmpty()) { if (includesIdentity) { return new AllowedContentEncodings(exchange, Collections.emptyList()); } else { return null; } } } else if (!available.isEmpty()) { Collections.sort(available, Collections.reverseOrder()); resultingMappings.addAll(available); } } if (!resultingMappings.isEmpty()) { return new AllowedContentEncodings(exchange, resultingMappings); } return null; } public synchronized ContentEncodingRepository addEncodingHandler(final String encoding, final ContentEncodingProvider encoder, int priority) { addEncodingHandler(encoding, encoder, priority, Predicates.truePredicate()); return this; } public synchronized ContentEncodingRepository addEncodingHandler(final String encoding, final ContentEncodingProvider encoder, int priority, final Predicate enabledPredicate) { this.encodingMap.put(encoding, new EncodingMapping(encoding, encoder, priority, enabledPredicate)); return this; } public synchronized ContentEncodingRepository removeEncodingHandler(final String encoding) { encodingMap.remove(encoding); return this; } } DeflateEncodingProvider.java000066400000000000000000000041461420065311100345470ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/encoding/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.encoding; import io.undertow.UndertowLogger; import io.undertow.conduits.DeflatingStreamSinkConduit; import io.undertow.server.ConduitWrapper; import io.undertow.server.HttpServerExchange; import io.undertow.util.ConduitFactory; import io.undertow.util.ObjectPool; import org.xnio.conduits.StreamSinkConduit; import java.util.zip.Deflater; /** * Content coding for 'deflate' * * @author Stuart Douglas */ public class DeflateEncodingProvider implements ContentEncodingProvider { private final ObjectPool deflaterPool; public DeflateEncodingProvider() { this(Deflater.DEFLATED); } public DeflateEncodingProvider(int deflateLevel) { this(DeflatingStreamSinkConduit.newInstanceDeflaterPool(deflateLevel)); } public DeflateEncodingProvider(ObjectPool deflaterPool) { this.deflaterPool = deflaterPool; } @Override public ConduitWrapper getResponseWrapper() { return new ConduitWrapper() { @Override public StreamSinkConduit wrap(final ConduitFactory factory, final HttpServerExchange exchange) { UndertowLogger.REQUEST_LOGGER.tracef("Created DEFLATE response conduit for %s", exchange); return new DeflatingStreamSinkConduit(factory, exchange, deflaterPool); } }; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/encoding/EncodingHandler.java000066400000000000000000000111301420065311100331130ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.encoding; import io.undertow.Handlers; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.ResponseCodeHandler; import io.undertow.server.handlers.builder.HandlerBuilder; import java.util.Collections; import java.util.Map; import java.util.Set; /** * Handler that serves as the basis for content encoding implementations. *

* Encoding handlers are added as delegates to this handler, with a specified server side priority. *

* If a request comes in with no q value then then server will pick the handler with the highest priority * as the encoding to use, otherwise the q value will be used to determine the correct handler. *

* If no handler matches then the identity encoding is assumed. If the identity encoding has been * specifically disallowed due to a q value of 0 then the handler will set the response code * 406 (Not Acceptable) and return. * * @author Stuart Douglas */ public class EncodingHandler implements HttpHandler { private volatile HttpHandler next = ResponseCodeHandler.HANDLE_404; private volatile HttpHandler noEncodingHandler = ResponseCodeHandler.HANDLE_406; private final ContentEncodingRepository contentEncodingRepository; public EncodingHandler(final HttpHandler next, ContentEncodingRepository contentEncodingRepository) { this.next = next; this.contentEncodingRepository = contentEncodingRepository; } public EncodingHandler(ContentEncodingRepository contentEncodingRepository) { this.contentEncodingRepository = contentEncodingRepository; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { AllowedContentEncodings encodings = contentEncodingRepository.getContentEncodings(exchange); if (encodings == null || !exchange.isResponseChannelAvailable()) { next.handleRequest(exchange); } else if (encodings.isNoEncodingsAllowed()) { noEncodingHandler.handleRequest(exchange); } else { exchange.addResponseWrapper(encodings); exchange.putAttachment(AllowedContentEncodings.ATTACHMENT_KEY, encodings); next.handleRequest(exchange); } } public HttpHandler getNext() { return next; } public EncodingHandler setNext(final HttpHandler next) { Handlers.handlerNotNull(next); this.next = next; return this; } public HttpHandler getNoEncodingHandler() { return noEncodingHandler; } public EncodingHandler setNoEncodingHandler(HttpHandler noEncodingHandler) { Handlers.handlerNotNull(noEncodingHandler); this.noEncodingHandler = noEncodingHandler; return this; } @Override public String toString() { return "compress()"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "compress"; } @Override public Map> parameters() { return Collections.emptyMap(); } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { return new HandlerWrapper() { @Override public HttpHandler wrap(HttpHandler handler) { return new EncodingHandler(handler, new ContentEncodingRepository() .addEncodingHandler("gzip", new GzipEncodingProvider(), 100) .addEncodingHandler("deflate", new DeflateEncodingProvider(), 10)); } }; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/encoding/EncodingMapping.java000066400000000000000000000037521420065311100331440ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.encoding; import io.undertow.predicate.Predicate; /** * @author Stuart Douglas */ final class EncodingMapping implements Comparable { private final String name; private final ContentEncodingProvider encoding; private final int priority; private final Predicate allowed; EncodingMapping(final String name, final ContentEncodingProvider encoding, final int priority, final Predicate allowed) { this.name = name; this.encoding = encoding; this.priority = priority; this.allowed = allowed; } public String getName() { return name; } public ContentEncodingProvider getEncoding() { return encoding; } public int getPriority() { return priority; } public Predicate getAllowed() { return allowed; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof EncodingMapping)) return false; EncodingMapping that = (EncodingMapping) o; return this.compareTo(that) == 0; } @Override public int hashCode() { return getPriority(); } @Override public int compareTo(final EncodingMapping o) { return priority - o.priority; } } GzipEncodingProvider.java000066400000000000000000000042171420065311100341130ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/encoding/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.encoding; import io.undertow.UndertowLogger; import io.undertow.conduits.DeflatingStreamSinkConduit; import io.undertow.conduits.GzipStreamSinkConduit; import io.undertow.server.ConduitWrapper; import io.undertow.server.HttpServerExchange; import io.undertow.util.ConduitFactory; import io.undertow.util.ObjectPool; import org.xnio.conduits.StreamSinkConduit; import java.util.zip.Deflater; /** * Content coding for 'deflate' * * @author Stuart Douglas */ public class GzipEncodingProvider implements ContentEncodingProvider { private final ObjectPool deflaterPool; public GzipEncodingProvider() { this(Deflater.DEFAULT_COMPRESSION); } public GzipEncodingProvider(int deflateLevel) { this(DeflatingStreamSinkConduit.newInstanceDeflaterPool(deflateLevel)); } public GzipEncodingProvider(ObjectPool deflaterPool) { this.deflaterPool = deflaterPool; } @Override public ConduitWrapper getResponseWrapper() { return new ConduitWrapper() { @Override public StreamSinkConduit wrap(final ConduitFactory factory, final HttpServerExchange exchange) { UndertowLogger.REQUEST_LOGGER.tracef("Created GZIP response conduit for %s", exchange); return new GzipStreamSinkConduit(factory, exchange, deflaterPool); } }; } } RequestEncodingHandler.java000066400000000000000000000077671420065311100344320ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/encoding/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.encoding; import java.util.Collections; import java.util.Map; import java.util.Set; import org.xnio.conduits.StreamSourceConduit; import io.undertow.conduits.GzipStreamSourceConduit; import io.undertow.conduits.InflatingStreamSourceConduit; import io.undertow.server.ConduitWrapper; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.CopyOnWriteMap; import io.undertow.util.Headers; /** * Handler that serves as the basis for request content encoding. *

* This is not part of the HTTP spec, however there are some applications where it is useful. *

* It behaves in a similar manner to {@link EncodingHandler}, however it deals with the requests * content encoding. * * @author Stuart Douglas */ public class RequestEncodingHandler implements HttpHandler { private final HttpHandler next; private final Map> requestEncodings = new CopyOnWriteMap<>(); public RequestEncodingHandler(final HttpHandler next) { this.next = next; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { ConduitWrapper encodings = requestEncodings.get(exchange.getRequestHeaders().getFirst(Headers.CONTENT_ENCODING)); if (encodings != null && exchange.isRequestChannelAvailable()) { exchange.addRequestWrapper(encodings); // Nested handlers or even servlet filters may implement logic to decode encoded request data. // Since the data is no longer encoded, we remove the encoding header. exchange.getRequestHeaders().remove(Headers.CONTENT_ENCODING); } next.handleRequest(exchange); } public RequestEncodingHandler addEncoding(String name, ConduitWrapper wrapper) { this.requestEncodings.put(name, wrapper); return this; } public RequestEncodingHandler removeEncoding(String encoding) { this.requestEncodings.remove(encoding); return this; } public HttpHandler getNext() { return next; } @Override public String toString() { return "uncompress()"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "uncompress"; } @Override public Map> parameters() { return Collections.emptyMap(); } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { return new HandlerWrapper() { @Override public HttpHandler wrap(HttpHandler handler) { return new RequestEncodingHandler(handler) .addEncoding("gzip", GzipStreamSourceConduit.WRAPPER) .addEncoding("deflate", InflatingStreamSourceConduit.WRAPPER); } }; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/error/000077500000000000000000000000001420065311100265735ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/error/FileErrorPageHandler.java000066400000000000000000000225651420065311100334340ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.error; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import io.undertow.util.MimeMappings; import org.jboss.logging.Logger; import org.xnio.IoUtils; import org.xnio.channels.Channels; import org.xnio.channels.StreamSinkChannel; import io.undertow.Handlers; import io.undertow.UndertowLogger; import io.undertow.server.DefaultResponseListener; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.ResponseCodeHandler; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.util.Headers; /** * Handler that serves up a file from disk to serve as an error page. *

* This handler does not server up and response codes by default, you must configure * the response codes it responds to. * * @author Stuart Douglas */ public class FileErrorPageHandler implements HttpHandler { private static final Logger log = Logger.getLogger("io.undertow.server.error.file"); private volatile HttpHandler next = ResponseCodeHandler.HANDLE_404; /** * The response codes that this handler will handle. If this is empty then this handler will have no effect. */ private volatile Set responseCodes; private volatile Path file; private final MimeMappings mimeMappings; @Deprecated public FileErrorPageHandler(final File file, final Integer... responseCodes) { this(file.toPath(), responseCodes); } public FileErrorPageHandler(final Path file, final Integer... responseCodes) { this.file = file; this.responseCodes = new HashSet<>(Arrays.asList(responseCodes)); this.mimeMappings = MimeMappings.DEFAULT; } @Deprecated public FileErrorPageHandler(HttpHandler next, final File file, final Integer... responseCodes) { this(next, file.toPath(), responseCodes); } public FileErrorPageHandler(HttpHandler next, final Path file, final Integer... responseCodes) { this(next, file, MimeMappings.DEFAULT, responseCodes); } public FileErrorPageHandler(HttpHandler next, final Path file, MimeMappings mimeMappings, final Integer... responseCodes) { this.next = next; this.file = file; this.responseCodes = new HashSet<>(Arrays.asList(responseCodes)); this.mimeMappings = mimeMappings; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.addDefaultResponseListener(new DefaultResponseListener() { @Override public boolean handleDefaultResponse(final HttpServerExchange exchange) { Set codes = responseCodes; if (!exchange.isResponseStarted() && codes.contains(exchange.getStatusCode())) { serveFile(exchange); return true; } return false; } }); next.handleRequest(exchange); } private void serveFile(final HttpServerExchange exchange) { String fileName = file.toString(); int index = fileName.lastIndexOf("."); if(index > 0) { String contentType = mimeMappings.getMimeType(fileName.substring(index + 1)); if(contentType != null) { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, contentType); } } exchange.dispatch(new Runnable() { @Override public void run() { final FileChannel fileChannel; try { try { fileChannel = FileChannel.open(file, StandardOpenOption.READ); } catch (FileNotFoundException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); exchange.endExchange(); return; } } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); exchange.endExchange(); return; } long size; try { size = Files.size(file); } catch (IOException e) { throw new RuntimeException(e); } exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, size); final StreamSinkChannel response = exchange.getResponseChannel(); exchange.addExchangeCompleteListener(new ExchangeCompletionListener() { @Override public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { IoUtils.safeClose(fileChannel); nextListener.proceed(); } }); try { log.tracef("Serving file %s (blocking)", fileChannel); Channels.transferBlocking(response, fileChannel, 0, Files.size(file)); log.tracef("Finished serving %s, shutting down (blocking)", fileChannel); response.shutdownWrites(); log.tracef("Finished serving %s, flushing (blocking)", fileChannel); Channels.flushBlocking(response); log.tracef("Finished serving %s (complete)", fileChannel); exchange.endExchange(); } catch (IOException ignored) { log.tracef("Failed to serve %s: %s", fileChannel, ignored); exchange.endExchange(); IoUtils.safeClose(response); } finally { IoUtils.safeClose(fileChannel); } } }); } public HttpHandler getNext() { return next; } public FileErrorPageHandler setNext(final HttpHandler next) { Handlers.handlerNotNull(next); this.next = next; return this; } public Set getResponseCodes() { return Collections.unmodifiableSet(responseCodes); } public FileErrorPageHandler setResponseCodes(final Set responseCodes) { if (responseCodes == null) { this.responseCodes = Collections.emptySet(); } else { this.responseCodes = new HashSet<>(responseCodes); } return this; } public FileErrorPageHandler setResponseCodes(final Integer... responseCodes) { this.responseCodes = new HashSet<>(Arrays.asList(responseCodes)); return this; } public Path getFile() { return file; } public FileErrorPageHandler setFile(final Path file) { this.file = file; return this; } @Override public String toString() { return "response-codes( file='" + file.toString() + "', response-codes={ " + responseCodes.stream().map(s -> s.toString()).collect(Collectors.joining(", ")) + " } )"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "error-file"; } @Override public Map> parameters() { Map> params = new HashMap<>(); params.put("file", String.class); params.put("response-codes", Integer[].class); return params; } @Override public Set requiredParameters() { return new HashSet<>(Arrays.asList(new String[]{"file", "response-codes"})); } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { return new Wrapper((String)config.get("file"), (Integer[]) config.get("response-codes")); } } private static class Wrapper implements HandlerWrapper { private final String file; private final Integer[] responseCodes; private Wrapper(String file, Integer[] responseCodes) { this.file = file; this.responseCodes = responseCodes; } @Override public HttpHandler wrap(HttpHandler handler) { return new FileErrorPageHandler(handler, Paths.get(file), responseCodes); } } } SimpleErrorPageHandler.java000066400000000000000000000071771420065311100337310ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/error/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.error; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; import io.undertow.Handlers; import io.undertow.io.Sender; import io.undertow.server.DefaultResponseListener; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.ResponseCodeHandler; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; /** * Handler that generates an extremely simple no frills error page * * @author Stuart Douglas */ public class SimpleErrorPageHandler implements HttpHandler { private volatile HttpHandler next = ResponseCodeHandler.HANDLE_404; /** * The response codes that this handler will handle. If this is null then it will handle all 4xx and 5xx codes. */ private volatile Set responseCodes = null; public SimpleErrorPageHandler(final HttpHandler next) { this.next = next; } public SimpleErrorPageHandler() { } private final DefaultResponseListener responseListener = new DefaultResponseListener() { @Override public boolean handleDefaultResponse(final HttpServerExchange exchange) { if (!exchange.isResponseChannelAvailable()) { return false; } Set codes = responseCodes; if (codes == null ? exchange.getStatusCode() >= StatusCodes.BAD_REQUEST : codes.contains(Integer.valueOf(exchange.getStatusCode()))) { final String errorPage = "Error" + exchange.getStatusCode() + " - " + StatusCodes.getReason(exchange.getStatusCode()) + ""; exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, "" + errorPage.length()); exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/html"); Sender sender = exchange.getResponseSender(); sender.send(errorPage); return true; } return false; } }; @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.addDefaultResponseListener(responseListener); next.handleRequest(exchange); } public HttpHandler getNext() { return next; } public SimpleErrorPageHandler setNext(final HttpHandler next) { Handlers.handlerNotNull(next); this.next = next; return this; } public Set getResponseCodes() { return Collections.unmodifiableSet(responseCodes); } public SimpleErrorPageHandler setResponseCodes(final Set responseCodes) { this.responseCodes = new HashSet<>(responseCodes); return this; } public SimpleErrorPageHandler setResponseCodes(final Integer... responseCodes) { this.responseCodes = new HashSet<>(Arrays.asList(responseCodes)); return this; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/form/000077500000000000000000000000001420065311100264055ustar00rootroot00000000000000EagerFormParsingHandler.java000066400000000000000000000074171420065311100336730ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/form/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.form; import java.util.Collections; import java.util.Map; import java.util.Set; import io.undertow.Handlers; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.ResponseCodeHandler; import io.undertow.server.handlers.builder.HandlerBuilder; /** * Handler that eagerly parses form data. The request chain will pause while the data is being read, * and then continue when the form data is fully passed. *

*

* NOTE: This is not strictly compatible with servlet, as it removes the option for the user to * parse the request themselves, however in practice this requirement is probably rare, and * using this handler gives a significant performance advantage in that a thread is not blocked * for the duration of the upload. * * @author Stuart Douglas */ public class EagerFormParsingHandler implements HttpHandler { private volatile HttpHandler next = ResponseCodeHandler.HANDLE_404; private final FormParserFactory formParserFactory; public static final HandlerWrapper WRAPPER = new HandlerWrapper() { @Override public HttpHandler wrap(HttpHandler handler) { return new EagerFormParsingHandler(handler); } }; public EagerFormParsingHandler(final FormParserFactory formParserFactory) { this.formParserFactory = formParserFactory; } public EagerFormParsingHandler() { this.formParserFactory = FormParserFactory.builder().build(); } public EagerFormParsingHandler(HttpHandler next) { this(); this.next = next; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { FormDataParser parser = formParserFactory.createParser(exchange); if (parser == null) { next.handleRequest(exchange); return; } if(exchange.isBlocking()) { exchange.putAttachment(FormDataParser.FORM_DATA, parser.parseBlocking()); next.handleRequest(exchange); } else { parser.parse(next); } } public HttpHandler getNext() { return next; } public EagerFormParsingHandler setNext(final HttpHandler next) { Handlers.handlerNotNull(next); this.next = next; return this; } @Override public String toString() { return "eager-form-parser()"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "eager-form-parser"; } @Override public Map> parameters() { return Collections.emptyMap(); } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { return WRAPPER; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/form/FormData.java000066400000000000000000000252711420065311100307540ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.form; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.ArrayDeque; import java.util.Deque; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import io.undertow.UndertowMessages; import io.undertow.util.HeaderMap; /** * Representation of form data. *

* TODO: add representation of multipart data */ public final class FormData implements Iterable { private final Map> values = new LinkedHashMap<>(); private final int maxValues; private int valueCount = 0; public FormData(final int maxValues) { this.maxValues = maxValues; } public Iterator iterator() { return values.keySet().iterator(); } public FormValue getFirst(String name) { final Deque deque = values.get(name); return deque == null ? null : deque.peekFirst(); } public FormValue getLast(String name) { final Deque deque = values.get(name); return deque == null ? null : deque.peekLast(); } public Deque get(String name) { return values.get(name); } public void add(String name, byte[] value, String fileName, HeaderMap headers) { Deque values = this.values.get(name); if (values == null) { this.values.put(name, values = new ArrayDeque<>(1)); } values.add(new FormValueImpl(value, fileName, headers)); if (++valueCount > maxValues) { throw new RuntimeException(UndertowMessages.MESSAGES.tooManyParameters(maxValues)); } } public void add(String name, String value) { add(name, value, null, null); } public void add(String name, String value, final HeaderMap headers) { add(name, value, null, headers); } public void add(String name, String value, String charset, final HeaderMap headers) { Deque values = this.values.get(name); if (values == null) { this.values.put(name, values = new ArrayDeque<>(1)); } values.add(new FormValueImpl(value, charset, headers)); if (++valueCount > maxValues) { throw new RuntimeException(UndertowMessages.MESSAGES.tooManyParameters(maxValues)); } } public void add(String name, Path value, String fileName, final HeaderMap headers) { Deque values = this.values.get(name); if (values == null) { this.values.put(name, values = new ArrayDeque<>(1)); } values.add(new FormValueImpl(value, fileName, headers)); if (values.size() > maxValues) { throw new RuntimeException(UndertowMessages.MESSAGES.tooManyParameters(maxValues)); } if (++valueCount > maxValues) { throw new RuntimeException(UndertowMessages.MESSAGES.tooManyParameters(maxValues)); } } public void put(String name, String value, final HeaderMap headers) { Deque values = new ArrayDeque<>(1); Deque old = this.values.put(name, values); if (old != null) { valueCount -= old.size(); } values.add(new FormValueImpl(value, headers)); if (++valueCount > maxValues) { throw new RuntimeException(UndertowMessages.MESSAGES.tooManyParameters(maxValues)); } } public Deque remove(String name) { Deque old = values.remove(name); if (old != null) { valueCount -= old.size(); } return old; } public boolean contains(String name) { final Deque value = values.get(name); return value != null && !value.isEmpty(); } @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final FormData strings = (FormData) o; if (values != null ? !values.equals(strings.values) : strings.values != null) return false; return true; } @Override public int hashCode() { return values != null ? values.hashCode() : 0; } @Override public String toString() { return "FormData{" + "values=" + values + '}'; } public interface FormValue { /** * @return the simple string value. * @throws IllegalStateException If this is not a simple string value */ String getValue(); /** * @return The charset of the simple string value */ String getCharset(); /** * Returns true if this is a file and not a simple string * * @return */ @Deprecated boolean isFile(); /** * @return The temp file that the file data was saved to * * @throws IllegalStateException if this is not a file */ @Deprecated Path getPath(); @Deprecated File getFile(); FileItem getFileItem(); boolean isFileItem(); /** * @return The filename specified in the disposition header. */ String getFileName(); /** * @return The headers that were present in the multipart request, or null if this was not a multipart request */ HeaderMap getHeaders(); } public static class FileItem { private final Path file; private final byte[] content; public FileItem(Path file) { this.file = file; this.content = null; } public FileItem(byte[] content) { this.file = null; this.content = content; } public boolean isInMemory() { return file == null; } public Path getFile() { return file; } public long getFileSize() throws IOException { if (isInMemory()) { return content.length; } else { return Files.size(file); } } public InputStream getInputStream() throws IOException { if (file != null) { return new BufferedInputStream(Files.newInputStream(file)); } else { return new ByteArrayInputStream(content); } } public void delete() throws IOException { if (file != null) { try { Files.delete(file); } catch (NoSuchFileException e) { //already deleted } } } public void write(Path target) throws IOException { if (file != null) { try { Files.move(file, target, StandardCopyOption.REPLACE_EXISTING); return; } catch (IOException e) { // ignore and let the Files.copy, outside // this if block, take over and attempt to copy it } } try (InputStream is = getInputStream()) { Files.copy(is, target, StandardCopyOption.REPLACE_EXISTING); } } } static class FormValueImpl implements FormValue { private final String value; private final String fileName; private final HeaderMap headers; private final FileItem fileItem; private final String charset; FormValueImpl(String value, HeaderMap headers) { this.value = value; this.headers = headers; this.fileName = null; this.fileItem = null; this.charset = null; } FormValueImpl(String value, String charset, HeaderMap headers) { this.value = value; this.charset = charset; this.headers = headers; this.fileName = null; this.fileItem = null; } FormValueImpl(Path file, final String fileName, HeaderMap headers) { this.fileItem = new FileItem(file); this.headers = headers; this.fileName = fileName; this.value = null; this.charset = null; } FormValueImpl(byte[] data, String fileName, HeaderMap headers) { this.fileItem = new FileItem(data); this.fileName = fileName; this.headers = headers; this.value = null; this.charset = null; } @Override public String getValue() { if (value == null) { throw UndertowMessages.MESSAGES.formValueIsAFile(); } return value; } @Override public String getCharset() { return charset; } @Override public boolean isFile() { return fileItem != null && !fileItem.isInMemory(); } @Override public Path getPath() { if (fileItem == null) { throw UndertowMessages.MESSAGES.formValueIsAString(); } if (fileItem.isInMemory()) { throw UndertowMessages.MESSAGES.formValueIsInMemoryFile(); } return fileItem.getFile(); } @Override public File getFile() { return getPath().toFile(); } @Override public FileItem getFileItem() { if (fileItem == null) { throw UndertowMessages.MESSAGES.formValueIsAString(); } return fileItem; } @Override public boolean isFileItem() { return fileItem != null; } @Override public HeaderMap getHeaders() { return headers; } public String getFileName() { return fileName; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/form/FormDataParser.java000066400000000000000000000052501420065311100321240ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.form; import java.io.Closeable; import java.io.IOException; import io.undertow.server.HttpHandler; import io.undertow.util.AttachmentKey; /** * Parser for form data. This can be used by down-stream handlers to parse * form data. *

* This parser must be closed to make sure any temporary files have been cleaned up. * * @author Stuart Douglas */ public interface FormDataParser extends Closeable { /** * When the form data is parsed it will be attached under this key. */ AttachmentKey FORM_DATA = AttachmentKey.create(FormData.class); /** * Parse the form data asynchronously. If all the data cannot be read immediately then a read listener will be * registered, and the data will be parsed by the read thread. *

* When this method completes the handler will be invoked, and the data * will be attached under {@link #FORM_DATA}. *

* The method can either invoke the next handler directly, or may delegate to the IO thread * to perform the parsing. */ void parse(final HttpHandler next) throws Exception; /** * Parse the data, blocking the current thread until parsing is complete. For blocking handlers this method is * more efficient than {@link #parse(io.undertow.server.HttpHandler next)}, as the calling thread should do that * actual parsing, rather than the read thread * * @return The parsed form data * @throws IOException If the data could not be read */ FormData parseBlocking() throws IOException; /** * Closes the parser, and removes and temporary files that may have been created. * * @throws IOException */ void close() throws IOException; /** * Sets the character encoding that will be used by this parser. If the request is already processed this will have * no effect * * @param encoding The encoding */ void setCharacterEncoding(String encoding); } FormEncodedDataDefinition.java000066400000000000000000000314031420065311100341620ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/form/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.form; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.UndertowOptions; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.UrlDecodeException; import io.undertow.util.Headers; import io.undertow.util.SameThreadExecutor; import io.undertow.util.URLUtils; import org.xnio.ChannelListener; import org.xnio.IoUtils; import org.xnio.channels.StreamSourceChannel; import java.io.IOException; import java.nio.ByteBuffer; /** * Parser definition for form encoded data. This handler takes effect for any request that has a mime type * of application/x-www-form-urlencoded. The handler attaches a {@link FormDataParser} to the chain * that can parse the underlying form data asynchronously. * * @author Stuart Douglas */ public class FormEncodedDataDefinition implements FormParserFactory.ParserDefinition { public static final String APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded"; private static boolean parseExceptionLogAsDebug = false; private String defaultEncoding = "ISO-8859-1"; private boolean forceCreation = false; //if the parser should be created even if the correct headers are missing public FormEncodedDataDefinition() { } @Override public FormDataParser create(final HttpServerExchange exchange) { String mimeType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE); if (forceCreation || (mimeType != null && mimeType.startsWith(APPLICATION_X_WWW_FORM_URLENCODED))) { String charset = defaultEncoding; String contentType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE); if (contentType != null) { String cs = Headers.extractQuotedValueFromHeader(contentType, "charset"); if (cs != null) { charset = cs; } } UndertowLogger.REQUEST_LOGGER.tracef("Created form encoded parser for %s", exchange); return new FormEncodedDataParser(charset, exchange); } return null; } public String getDefaultEncoding() { return defaultEncoding; } public boolean isForceCreation() { return forceCreation; } public FormEncodedDataDefinition setForceCreation(boolean forceCreation) { this.forceCreation = forceCreation; return this; } public FormEncodedDataDefinition setDefaultEncoding(final String defaultEncoding) { this.defaultEncoding = defaultEncoding; return this; } private static final class FormEncodedDataParser implements ChannelListener, FormDataParser { private final HttpServerExchange exchange; private final FormData data; private final StringBuilder builder = new StringBuilder(); private String name = null; private String charset; private HttpHandler handler; //0= parsing name //1=parsing name, decode required //2=parsing value //3=parsing value, decode required //4=finished private int state = 0; private FormEncodedDataParser(final String charset, final HttpServerExchange exchange) { this.exchange = exchange; this.charset = charset; this.data = new FormData(exchange.getConnection().getUndertowOptions().get(UndertowOptions.MAX_PARAMETERS, 1000)); } @Override public void handleEvent(final StreamSourceChannel channel) { try { doParse(channel); if (state == 4) { exchange.dispatch(SameThreadExecutor.INSTANCE, handler); } } catch (IOException e) { IoUtils.safeClose(channel); UndertowLogger.REQUEST_IO_LOGGER.ioExceptionReadingFromChannel(e); exchange.endExchange(); } } private void doParse(final StreamSourceChannel channel) throws IOException { int c = 0; final PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().allocate(); try { final ByteBuffer buffer = pooled.getBuffer(); do { buffer.clear(); c = channel.read(buffer); if (c > 0) { buffer.flip(); while (buffer.hasRemaining()) { byte n = buffer.get(); switch (state) { case 0: { if (n == '=') { name = builder.toString(); builder.setLength(0); state = 2; } else if (n == '&') { addPair(builder.toString(), ""); builder.setLength(0); state = 0; } else if (n == '%' || n == '+' || n < 0) { state = 1; builder.append((char) (n & 0xFF)); } else { builder.append((char) n); } break; } case 1: { if (n == '=') { name = decodeParameterName(builder.toString(), charset, true, new StringBuilder()); builder.setLength(0); state = 2; } else if (n == '&') { addPair(decodeParameterName(builder.toString(), charset, true, new StringBuilder()), ""); builder.setLength(0); state = 0; } else { builder.append((char) (n & 0xFF)); } break; } case 2: { if (n == '&') { addPair(name, builder.toString()); builder.setLength(0); state = 0; } else if (n == '%' || n == '+' || n < 0) { state = 3; builder.append((char) (n & 0xFF)); } else { builder.append((char) n); } break; } case 3: { if (n == '&') { addPair(name, decodeParameterValue(name, builder.toString(), charset, true, new StringBuilder())); builder.setLength(0); state = 0; } else { builder.append((char) (n & 0xFF)); } break; } } } } } while (c > 0); if (c == -1) { if (state == 2) { addPair(name, builder.toString()); } else if (state == 3) { addPair(name, decodeParameterValue(name, builder.toString(), charset, true, new StringBuilder())); } else if(builder.length() > 0) { if(state == 1) { addPair(decodeParameterName(builder.toString(), charset, true, new StringBuilder()), ""); } else { addPair(builder.toString(), ""); } } state = 4; exchange.putAttachment(FORM_DATA, data); } } finally { pooled.close(); } } private void addPair(String name, String value) { //if there was exception during decoding ignore the parameter [UNDERTOW-1554] if(name != null && value != null) { data.add(name, value); } } private String decodeParameterValue(String name, String value, String charset, boolean decodeSlash, StringBuilder stringBuilder) { String decodedValue = null; try { decodedValue = URLUtils.decode(value, charset, decodeSlash, stringBuilder); } catch (UrlDecodeException e) { if (!parseExceptionLogAsDebug) { UndertowLogger.REQUEST_LOGGER.errorf(UndertowMessages.MESSAGES.failedToDecodeParameterValue(name, value, e)); parseExceptionLogAsDebug = true; } else { UndertowLogger.REQUEST_LOGGER.debugf(UndertowMessages.MESSAGES.failedToDecodeParameterValue(name, value, e)); } } return decodedValue; } private String decodeParameterName(String name, String charset, boolean decodeSlash, StringBuilder stringBuilder) { String decodedName = null; try { decodedName = URLUtils.decode(name, charset, decodeSlash, stringBuilder); } catch (UrlDecodeException e) { if (!parseExceptionLogAsDebug) { UndertowLogger.REQUEST_LOGGER.errorf(UndertowMessages.MESSAGES.failedToDecodeParameterName(name, e)); parseExceptionLogAsDebug = true; } else { UndertowLogger.REQUEST_LOGGER.debugf(UndertowMessages.MESSAGES.failedToDecodeParameterName(name, e)); } } return decodedName; } @Override public void parse(HttpHandler handler) throws Exception { if (exchange.getAttachment(FORM_DATA) != null) { handler.handleRequest(exchange); return; } this.handler = handler; StreamSourceChannel channel = exchange.getRequestChannel(); if (channel == null) { throw new IOException(UndertowMessages.MESSAGES.requestChannelAlreadyProvided()); } else { doParse(channel); if (state != 4) { channel.getReadSetter().set(this); channel.resumeReads(); } else { exchange.dispatch(SameThreadExecutor.INSTANCE, handler); } } } @Override public FormData parseBlocking() throws IOException { final FormData existing = exchange.getAttachment(FORM_DATA); if (existing != null) { return existing; } StreamSourceChannel channel = exchange.getRequestChannel(); if (channel == null) { throw new IOException(UndertowMessages.MESSAGES.requestChannelAlreadyProvided()); } else { while (state != 4) { doParse(channel); if (state != 4) { channel.awaitReadable(); } } } return data; } @Override public void close() throws IOException { } @Override public void setCharacterEncoding(final String encoding) { this.charset = encoding; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/form/FormParserFactory.java000066400000000000000000000111671420065311100326660ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.form; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import io.undertow.server.HttpServerExchange; import io.undertow.util.AttachmentKey; /** * Factory class that can create a form data parser for a given request. *

* It does this by iterating the available parser definitions, and returning * the first parser that is created. * * @author Stuart Douglas */ public class FormParserFactory { private static final AttachmentKey ATTACHMENT_KEY = AttachmentKey.create(FormDataParser.class); private final ParserDefinition[] parserDefinitions; FormParserFactory(final List parserDefinitions) { this.parserDefinitions = parserDefinitions.toArray(new ParserDefinition[parserDefinitions.size()]); } /** * Creates a form data parser for this request. If a parser has already been created for the request the * existing parser will be returned rather than creating a new one. * * @param exchange The exchange * @return A form data parser, or null if there is no parser registered for the request content type */ public FormDataParser createParser(final HttpServerExchange exchange) { FormDataParser existing = exchange.getAttachment(ATTACHMENT_KEY); if(existing != null) { return existing; } for (int i = 0; i < parserDefinitions.length; ++i) { FormDataParser parser = parserDefinitions[i].create(exchange); if (parser != null) { exchange.putAttachment(ATTACHMENT_KEY, parser); return parser; } } return null; } public interface ParserDefinition { FormDataParser create(final HttpServerExchange exchange); T setDefaultEncoding(String charset); } public static Builder builder() { return builder(true); } public static Builder builder(boolean includeDefault) { Builder builder = new Builder(); if (includeDefault) { builder.addParsers(new FormEncodedDataDefinition(), new MultiPartParserDefinition()); } return builder; } public static class Builder { private List parsers = new ArrayList<>(); private String defaultCharset = null; public Builder addParser(final ParserDefinition definition) { parsers.add(definition); return this; } public Builder addParsers(final ParserDefinition... definition) { parsers.addAll(Arrays.asList(definition)); return this; } public Builder addParsers(final List definition) { parsers.addAll(definition); return this; } public List getParsers() { return parsers; } public void setParsers(List parsers) { this.parsers = parsers; } /** * A chainable version of {@link #setParsers}. */ public Builder withParsers(List parsers) { setParsers(parsers); return this; } public String getDefaultCharset() { return defaultCharset; } public void setDefaultCharset(String defaultCharset) { this.defaultCharset = defaultCharset; } /** * A chainable version of {@link #setDefaultCharset}. */ public Builder withDefaultCharset(String defaultCharset) { setDefaultCharset(defaultCharset); return this; } public FormParserFactory build() { if(defaultCharset != null) { for (ParserDefinition parser : parsers) { parser.setDefaultEncoding(defaultCharset); } } return new FormParserFactory(parsers); } } } MultiPartParserDefinition.java000066400000000000000000000506041420065311100343050ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/form/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.form; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.UndertowOptions; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.HeaderMap; import io.undertow.util.Headers; import io.undertow.util.MalformedMessageException; import io.undertow.util.MultipartParser; import io.undertow.util.SameThreadExecutor; import io.undertow.util.StatusCodes; import org.xnio.ChannelListener; import org.xnio.IoUtils; import io.undertow.connector.PooledByteBuffer; import org.xnio.channels.StreamSourceChannel; import java.io.ByteArrayOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.FileAttribute; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Executor; /** * @author Stuart Douglas */ public class MultiPartParserDefinition implements FormParserFactory.ParserDefinition { public static final String MULTIPART_FORM_DATA = "multipart/form-data"; private Executor executor; private Path tempFileLocation; private String defaultEncoding = StandardCharsets.ISO_8859_1.displayName(); private long maxIndividualFileSize = -1; private long fileSizeThreshold; public MultiPartParserDefinition() { tempFileLocation = Paths.get(System.getProperty("java.io.tmpdir")); } public MultiPartParserDefinition(final Path tempDir) { tempFileLocation = tempDir; } @Override public FormDataParser create(final HttpServerExchange exchange) { String mimeType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE); if (mimeType != null && mimeType.startsWith(MULTIPART_FORM_DATA)) { String boundary = Headers.extractQuotedValueFromHeader(mimeType, "boundary"); if (boundary == null) { UndertowLogger.REQUEST_LOGGER.debugf("Could not find boundary in multipart request with ContentType: %s, multipart data will not be available", mimeType); return null; } final MultiPartUploadHandler parser = new MultiPartUploadHandler(exchange, boundary, maxIndividualFileSize, fileSizeThreshold, defaultEncoding); exchange.addExchangeCompleteListener(new ExchangeCompletionListener() { @Override public void exchangeEvent(final HttpServerExchange exchange, final NextListener nextListener) { IoUtils.safeClose(parser); nextListener.proceed(); } }); Long sizeLimit = exchange.getConnection().getUndertowOptions().get(UndertowOptions.MULTIPART_MAX_ENTITY_SIZE); if(sizeLimit != null) { exchange.setMaxEntitySize(sizeLimit); } UndertowLogger.REQUEST_LOGGER.tracef("Created multipart parser for %s", exchange); return parser; } return null; } public Executor getExecutor() { return executor; } public MultiPartParserDefinition setExecutor(final Executor executor) { this.executor = executor; return this; } public Path getTempFileLocation() { return tempFileLocation; } public MultiPartParserDefinition setTempFileLocation(Path tempFileLocation) { this.tempFileLocation = tempFileLocation; return this; } public String getDefaultEncoding() { return defaultEncoding; } public MultiPartParserDefinition setDefaultEncoding(final String defaultEncoding) { this.defaultEncoding = defaultEncoding; return this; } public long getMaxIndividualFileSize() { return maxIndividualFileSize; } public void setMaxIndividualFileSize(final long maxIndividualFileSize) { this.maxIndividualFileSize = maxIndividualFileSize; } public void setFileSizeThreshold(long fileSizeThreshold) { this.fileSizeThreshold = fileSizeThreshold; } private final class MultiPartUploadHandler implements FormDataParser, MultipartParser.PartHandler { private final HttpServerExchange exchange; private final FormData data; private final List createdFiles = new ArrayList<>(); private final long maxIndividualFileSize; private final long fileSizeThreshold; private String defaultEncoding; private final ByteArrayOutputStream contentBytes = new ByteArrayOutputStream(); private String currentName; private String fileName; private Path file; private FileChannel fileChannel; private HeaderMap headers; private HttpHandler handler; private long currentFileSize; private final MultipartParser.ParseState parser; private MultiPartUploadHandler(final HttpServerExchange exchange, final String boundary, final long maxIndividualFileSize, final long fileSizeThreshold, final String defaultEncoding) { this.exchange = exchange; this.maxIndividualFileSize = maxIndividualFileSize; this.defaultEncoding = defaultEncoding; this.fileSizeThreshold = fileSizeThreshold; this.data = new FormData(exchange.getConnection().getUndertowOptions().get(UndertowOptions.MAX_PARAMETERS, 1000)); String charset = defaultEncoding; String contentType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE); if (contentType != null) { String value = Headers.extractQuotedValueFromHeader(contentType, "charset"); if (value != null) { charset = value; } } this.parser = MultipartParser.beginParse(exchange.getConnection().getByteBufferPool(), this, boundary.getBytes(StandardCharsets.US_ASCII), charset); } @Override public void parse(final HttpHandler handler) throws Exception { if (exchange.getAttachment(FORM_DATA) != null) { handler.handleRequest(exchange); return; } this.handler = handler; //we need to delegate to a thread pool //as we parse with blocking operations StreamSourceChannel requestChannel = exchange.getRequestChannel(); if (requestChannel == null) { throw new IOException(UndertowMessages.MESSAGES.requestChannelAlreadyProvided()); } if (executor == null) { exchange.dispatch(new NonBlockingParseTask(exchange.getConnection().getWorker(), requestChannel)); } else { exchange.dispatch(executor, new NonBlockingParseTask(executor, requestChannel)); } } @Override public FormData parseBlocking() throws IOException { final FormData existing = exchange.getAttachment(FORM_DATA); if (existing != null) { return existing; } InputStream inputStream = exchange.getInputStream(); if (inputStream == null) { throw new IOException(UndertowMessages.MESSAGES.requestChannelAlreadyProvided()); } try (PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().getArrayBackedPool().allocate()){ ByteBuffer buf = pooled.getBuffer(); while (true) { buf.clear(); int c = inputStream.read(buf.array(), buf.arrayOffset(), buf.remaining()); if (c == -1) { if (parser.isComplete()) { break; } else { throw UndertowMessages.MESSAGES.connectionTerminatedReadingMultiPartData(); } } else if (c != 0) { buf.limit(c); parser.parse(buf); } } exchange.putAttachment(FORM_DATA, data); } catch (MalformedMessageException e) { throw new IOException(e); } return exchange.getAttachment(FORM_DATA); } @Override public void beginPart(final HeaderMap headers) { this.currentFileSize = 0; this.headers = headers; final String disposition = headers.getFirst(Headers.CONTENT_DISPOSITION); if (disposition != null) { if (disposition.startsWith("form-data")) { currentName = Headers.extractQuotedValueFromHeader(disposition, "name"); fileName = Headers.extractQuotedValueFromHeaderWithEncoding(disposition, "filename"); if (fileName != null && fileSizeThreshold == 0) { try { if (tempFileLocation != null) { //Files impl is buggy, hence zero len final FileAttribute[] emptyFA = new FileAttribute[] {}; final LinkOption[] emptyLO = new LinkOption[] {}; final Path normalized = tempFileLocation.normalize(); if (!Files.exists(normalized)) { final int pathElementsCount = normalized.getNameCount(); Path tmp = normalized; LinkedList dirsToGuard = new LinkedList<>(); for(int i=0;i 0 && this.currentFileSize > this.maxIndividualFileSize) { throw UndertowMessages.MESSAGES.maxFileSizeExceeded(this.maxIndividualFileSize); } if (file == null && fileName != null && fileSizeThreshold < this.currentFileSize) { try { if (tempFileLocation != null) { file = Files.createTempFile(tempFileLocation, "undertow", "upload"); } else { file = Files.createTempFile("undertow", "upload"); } createdFiles.add(file); FileOutputStream fileOutputStream = new FileOutputStream(file.toFile()); contentBytes.writeTo(fileOutputStream); fileChannel = fileOutputStream.getChannel(); } catch (IOException e) { throw new RuntimeException(e); } } if (file == null) { while (buffer.hasRemaining()) { contentBytes.write(buffer.get()); } } else { fileChannel.write(buffer); } } @Override public void endPart() { if (file != null) { data.add(currentName, file, fileName, headers); file = null; contentBytes.reset(); try { fileChannel.close(); fileChannel = null; } catch (IOException e) { throw new RuntimeException(e); } } else if (fileName != null) { data.add(currentName, Arrays.copyOf(contentBytes.toByteArray(), contentBytes.size()), fileName, headers); contentBytes.reset(); } else { try { String charset = defaultEncoding; String contentType = headers.getFirst(Headers.CONTENT_TYPE); if (contentType != null) { String cs = Headers.extractQuotedValueFromHeader(contentType, "charset"); if (cs != null) { charset = cs; } } data.add(currentName, new String(contentBytes.toByteArray(), charset), charset, headers); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } contentBytes.reset(); } } public List getCreatedFiles() { return createdFiles; } @Override public void close() throws IOException { IoUtils.safeClose(fileChannel); //we have to dispatch this, as it may result in file IO final List files = new ArrayList<>(getCreatedFiles()); exchange.getConnection().getWorker().execute(new Runnable() { @Override public void run() { for (final Path file : files) { if (Files.exists(file)) { try { Files.delete(file); } catch (NoSuchFileException e) { // ignore } catch (IOException e) { UndertowLogger.REQUEST_LOGGER.cannotRemoveUploadedFile(file); } } } } }); } @Override public void setCharacterEncoding(final String encoding) { this.defaultEncoding = encoding; parser.setCharacterEncoding(encoding); } private final class NonBlockingParseTask implements Runnable { private final Executor executor; private final StreamSourceChannel requestChannel; private NonBlockingParseTask(Executor executor, StreamSourceChannel requestChannel) { this.executor = executor; this.requestChannel = requestChannel; } @Override public void run() { try { final FormData existing = exchange.getAttachment(FORM_DATA); if (existing != null) { exchange.dispatch(SameThreadExecutor.INSTANCE, handler); return; } PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().allocate(); try { while (true) { int c = requestChannel.read(pooled.getBuffer()); if(c == 0) { requestChannel.getReadSetter().set(new ChannelListener() { @Override public void handleEvent(StreamSourceChannel channel) { channel.suspendReads(); executor.execute(NonBlockingParseTask.this); } }); requestChannel.resumeReads(); return; } else if (c == -1) { if (parser.isComplete()) { exchange.putAttachment(FORM_DATA, data); exchange.dispatch(SameThreadExecutor.INSTANCE, handler); } else { UndertowLogger.REQUEST_IO_LOGGER.ioException(UndertowMessages.MESSAGES.connectionTerminatedReadingMultiPartData()); exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); exchange.endExchange(); } return; } else { pooled.getBuffer().flip(); parser.parse(pooled.getBuffer()); pooled.getBuffer().compact(); } } } catch (MalformedMessageException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); exchange.endExchange(); } finally { pooled.close(); } } catch (Throwable e) { UndertowLogger.REQUEST_IO_LOGGER.debug("Exception parsing data", e); exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); exchange.endExchange(); } } } } public static class FileTooLargeException extends IOException { public FileTooLargeException() { super(); } public FileTooLargeException(String message) { super(message); } public FileTooLargeException(String message, Throwable cause) { super(message, cause); } public FileTooLargeException(Throwable cause) { super(cause); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/000077500000000000000000000000001420065311100266235ustar00rootroot00000000000000ConnectionPoolErrorHandler.java000066400000000000000000000102051420065311100346460ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy; /** * The connection pool error handler is intended to be used per node and will therefore be shared across I/O threads. * * @author Emanuel Muckenhuber */ public interface ConnectionPoolErrorHandler { /** * Check whether pool is available * * @return whether the pool is available */ boolean isAvailable(); /** * Handle a connection error. * * @return {@code true} if the pool is still available, {@code false} otherwise */ boolean handleError(); /** * Clear the connection errors. * * @return {@code true} if the pool is available again, {@code false} otherwise */ boolean clearError(); class SimpleConnectionPoolErrorHandler implements ConnectionPoolErrorHandler { private volatile boolean problem; @Override public boolean isAvailable() { return !problem; } @Override public boolean handleError() { problem = true; return false; } @Override public boolean clearError() { problem = false; return true; } } /** * Counting error handler, this only propagates the state to the delegate handler after reaching a given limit. */ class CountingErrorHandler implements ConnectionPoolErrorHandler { private int count; private long timeout; private final long interval; private final int errorCount; private final int successCount; private final ConnectionPoolErrorHandler delegate; public CountingErrorHandler(int errorCount, int successCount, long interval) { this(errorCount, successCount, interval, new SimpleConnectionPoolErrorHandler()); } public CountingErrorHandler(int errorCount, int successCount, long interval, ConnectionPoolErrorHandler delegate) { this.errorCount = Math.max(errorCount, 1); this.successCount = Math.max(successCount, 1); this.interval = Math.max(interval, 0); this.delegate = delegate; } @Override public boolean isAvailable() { return delegate.isAvailable(); } @Override public synchronized boolean handleError() { if (delegate.isAvailable()) { final long time = System.currentTimeMillis(); // If the timeout is reached reset the error count if (time >= timeout) { count = 1; timeout = time + interval; } else { if (count++ == 1) { timeout = time + interval; } } if (count >= errorCount) { return delegate.handleError(); } return true; } else { count = 0; // if in error reset the successful count return false; } } @Override public synchronized boolean clearError() { if (delegate.isAvailable()) { count = 0; // Just reset the error count return true; } else { // Count the successful attempts if (count++ == successCount) { return delegate.clearError(); } return false; } } } } ConnectionPoolManager.java000066400000000000000000000021361420065311100336350ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy; /** * Manager that controls the behaviour of a {@link ProxyConnectionPool} * * @author Stuart Douglas */ public interface ConnectionPoolManager extends ProxyConnectionPoolConfig, ConnectionPoolErrorHandler { /** * * @return The amount of time that we should wait before re-testing a problem server */ int getProblemServerRetry(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/ExclusivityChecker.java000066400000000000000000000022441420065311100333050ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy; import io.undertow.server.HttpServerExchange; /** * Interface that is used to determine if a connection should be exclusive. * * If a connection is exclusive then it is removed from the connection pool, and a one * to one mapping will be maintained between the front and back end servers. * * @author Stuart Douglas */ public interface ExclusivityChecker { boolean isExclusivityRequired(HttpServerExchange exchange); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/HostTable.java000066400000000000000000000110741420065311100313560ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy; import io.undertow.UndertowMessages; import io.undertow.util.CopyOnWriteMap; import io.undertow.util.PathMatcher; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; /** * Class that maintains a table of remote hosts that this proxy knows about. * * Basically this maps a virtual host + context path pair to a set of hosts. * * Note that this class does not have any knowledge of connection pooling * * @author Stuart Douglas */ public class HostTable { private final Map> hosts = new CopyOnWriteMap<>(); private final Map>> targets = new CopyOnWriteMap<>(); public synchronized HostTable addHost(H host) { if(hosts.containsKey(host)) { throw UndertowMessages.MESSAGES.hostAlreadyRegistered(host); } hosts.put(host, new CopyOnWriteArraySet()); return this; } public synchronized HostTable removeHost(H host) { Set targets = hosts.remove(host); for(Target target : targets) { removeRoute(host, target.virtualHost, target.contextPath); } return this; } public synchronized HostTable addRoute(H host, String virtualHost, String contextPath) { Set hostData = hosts.get(host); if(hostData == null) { throw UndertowMessages.MESSAGES.hostHasNotBeenRegistered(host); } hostData.add(new Target(virtualHost, contextPath)); PathMatcher> paths = targets.get(virtualHost); if(paths == null) { paths = new PathMatcher<>(); targets.put(virtualHost, paths); } Set hostSet = paths.getPrefixPath(contextPath); if(hostSet == null) { hostSet = new CopyOnWriteArraySet<>(); paths.addPrefixPath(contextPath, hostSet); } hostSet.add(host); return this; } public synchronized HostTable removeRoute(H host, String virtualHost, String contextPath) { Set hostData = hosts.get(host); if(hostData != null) { hostData.remove(new Target(virtualHost, contextPath)); } PathMatcher> paths = targets.get(virtualHost); if(paths == null) { return this; } Set hostSet = paths.getPrefixPath(contextPath); if(hostSet == null) { return this; } hostSet.remove(host); if(hostSet.isEmpty()) { paths.removePrefixPath(contextPath); } return this; } public Set getHostsForTarget(final String hostName, final String path) { PathMatcher> matcher = targets.get(hostName); if(matcher == null) { return null; } return matcher.match(path).getValue(); } private static final class Target { final String virtualHost; final String contextPath; private Target(String virtualHost, String contextPath) { this.virtualHost = virtualHost; this.contextPath = contextPath; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Target target = (Target) o; if (contextPath != null ? !contextPath.equals(target.contextPath) : target.contextPath != null) return false; if (virtualHost != null ? !virtualHost.equals(target.virtualHost) : target.virtualHost != null) return false; return true; } @Override public int hashCode() { int result = virtualHost != null ? virtualHost.hashCode() : 0; result = 31 * result + (contextPath != null ? contextPath.hashCode() : 0); return result; } } } LoadBalancingProxyClient.java000066400000000000000000000417561420065311100343030ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy; import io.undertow.UndertowLogger; import io.undertow.client.ClientConnection; import io.undertow.client.UndertowClient; import io.undertow.server.HttpServerExchange; import io.undertow.server.ServerConnection; import io.undertow.server.handlers.Cookie; import io.undertow.util.AttachmentKey; import io.undertow.util.AttachmentList; import io.undertow.util.CopyOnWriteMap; import org.xnio.OptionMap; import org.xnio.ssl.XnioSsl; import java.net.InetSocketAddress; import java.net.URI; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static io.undertow.server.handlers.proxy.ProxyConnectionPool.AvailabilityType.*; import static io.undertow.server.handlers.proxy.RouteIteratorFactory.*; import java.util.ArrayList; import java.util.List; import static org.xnio.IoUtils.safeClose; /** * Initial implementation of a load balancing proxy client. This initial implementation is rather simplistic, and * will likely change. * * @author Stuart Douglas * @author Richard Opalka */ public class LoadBalancingProxyClient implements ProxyClient { /** * The attachment key that is used to attach the proxy connection to the exchange. *

* This cannot be static as otherwise a connection from a different client could be re-used. */ private final AttachmentKey exclusiveConnectionKey = AttachmentKey.create(ExclusiveConnectionHolder.class); private static final AttachmentKey> ATTEMPTED_HOSTS = AttachmentKey.createList(Host.class); /** * Time in seconds between retries for problem servers */ private volatile int problemServerRetry = 10; private final Set sessionCookieNames = new CopyOnWriteArraySet<>(); /** * The number of connections to create per thread */ private volatile int connectionsPerThread = 10; private volatile int maxQueueSize = 0; private volatile int softMaxConnectionsPerThread = 5; private volatile int ttl = -1; /** * The hosts list. */ private volatile Host[] hosts = {}; private final HostSelector hostSelector; private final UndertowClient client; private final Map routes = new CopyOnWriteMap<>(); private RouteIteratorFactory routeIteratorFactory = new RouteIteratorFactory(RouteParsingStrategy.SINGLE, ParsingCompatibility.MOD_JK); private final ExclusivityChecker exclusivityChecker; private static final ProxyTarget PROXY_TARGET = new ProxyTarget() { }; @Override public List getAllTargets() { List arr = new ArrayList(); for (Host host : hosts) { HostProxyTarget proxyTarget = new HostProxyTarget() { Host host; public void setHost(Host host) { this.host = host; } public String toString(){ return host.getUri().toString(); } }; proxyTarget.setHost(host); arr.add(proxyTarget); } return arr; } public LoadBalancingProxyClient() { this(UndertowClient.getInstance()); } public LoadBalancingProxyClient(UndertowClient client) { this(client, null, null); } public LoadBalancingProxyClient(ExclusivityChecker client) { this(UndertowClient.getInstance(), client, null); } public LoadBalancingProxyClient(UndertowClient client, ExclusivityChecker exclusivityChecker) { this(client, exclusivityChecker, null); } public LoadBalancingProxyClient(UndertowClient client, ExclusivityChecker exclusivityChecker, HostSelector hostSelector) { this.client = client; this.exclusivityChecker = exclusivityChecker; sessionCookieNames.add("JSESSIONID"); if(hostSelector == null) { this.hostSelector = new RoundRobinHostSelector(); } else { this.hostSelector = hostSelector; } } public LoadBalancingProxyClient addSessionCookieName(final String sessionCookieName) { sessionCookieNames.add(sessionCookieName); return this; } public LoadBalancingProxyClient removeSessionCookieName(final String sessionCookieName) { sessionCookieNames.remove(sessionCookieName); return this; } public LoadBalancingProxyClient setProblemServerRetry(int problemServerRetry) { this.problemServerRetry = problemServerRetry; return this; } public int getProblemServerRetry() { return problemServerRetry; } public int getConnectionsPerThread() { return connectionsPerThread; } public LoadBalancingProxyClient setConnectionsPerThread(int connectionsPerThread) { this.connectionsPerThread = connectionsPerThread; return this; } public int getMaxQueueSize() { return maxQueueSize; } public LoadBalancingProxyClient setMaxQueueSize(int maxQueueSize) { this.maxQueueSize = maxQueueSize; return this; } public LoadBalancingProxyClient setTtl(int ttl) { this.ttl = ttl; return this; } public LoadBalancingProxyClient setSoftMaxConnectionsPerThread(int softMaxConnectionsPerThread) { this.softMaxConnectionsPerThread = softMaxConnectionsPerThread; return this; } public LoadBalancingProxyClient setRouteParsingStrategy(RouteParsingStrategy routeParsingStrategy) { this.routeIteratorFactory = new RouteIteratorFactory(routeParsingStrategy, ParsingCompatibility.MOD_JK, null); return this; } /** * Configures ranked route delimiter, enabling ranked routing parsing strategy. */ public LoadBalancingProxyClient setRankedRoutingDelimiter(String rankedRoutingDelimiter) { this.routeIteratorFactory = new RouteIteratorFactory(RouteParsingStrategy.RANKED, ParsingCompatibility.MOD_JK, rankedRoutingDelimiter); return this; } public synchronized LoadBalancingProxyClient addHost(final URI host) { return addHost(host, null, null); } public synchronized LoadBalancingProxyClient addHost(final URI host, XnioSsl ssl) { return addHost(host, null, ssl); } public synchronized LoadBalancingProxyClient addHost(final URI host, String jvmRoute) { return addHost(host, jvmRoute, null); } public synchronized LoadBalancingProxyClient addHost(final URI host, String jvmRoute, XnioSsl ssl) { Host h = new Host(jvmRoute, null, host, ssl, OptionMap.EMPTY); Host[] existing = hosts; Host[] newHosts = new Host[existing.length + 1]; System.arraycopy(existing, 0, newHosts, 0, existing.length); newHosts[existing.length] = h; this.hosts = newHosts; if (jvmRoute != null) { this.routes.put(jvmRoute, h); } return this; } public synchronized LoadBalancingProxyClient addHost(final URI host, String jvmRoute, XnioSsl ssl, OptionMap options) { return addHost(null, host, jvmRoute, ssl, options); } public synchronized LoadBalancingProxyClient addHost(final InetSocketAddress bindAddress, final URI host, String jvmRoute, XnioSsl ssl, OptionMap options) { Host h = new Host(jvmRoute, bindAddress, host, ssl, options); Host[] existing = hosts; Host[] newHosts = new Host[existing.length + 1]; System.arraycopy(existing, 0, newHosts, 0, existing.length); newHosts[existing.length] = h; this.hosts = newHosts; if (jvmRoute != null) { this.routes.put(jvmRoute, h); } return this; } public synchronized LoadBalancingProxyClient removeHost(final URI uri) { int found = -1; Host[] existing = hosts; Host removedHost = null; for (int i = 0; i < existing.length; ++i) { if (existing[i].uri.equals(uri)) { found = i; removedHost = existing[i]; break; } } if (found == -1) { return this; } Host[] newHosts = new Host[existing.length - 1]; System.arraycopy(existing, 0, newHosts, 0, found); System.arraycopy(existing, found + 1, newHosts, found, existing.length - found - 1); this.hosts = newHosts; removedHost.connectionPool.close(); if (removedHost.jvmRoute != null) { routes.remove(removedHost.jvmRoute); } return this; } @Override public ProxyTarget findTarget(HttpServerExchange exchange) { return PROXY_TARGET; } @Override public void getConnection(ProxyTarget target, HttpServerExchange exchange, final ProxyCallback callback, long timeout, TimeUnit timeUnit) { final ExclusiveConnectionHolder holder = exchange.getConnection().getAttachment(exclusiveConnectionKey); if (holder != null && holder.connection.getConnection().isOpen()) { // Something has already caused an exclusive connection to be allocated so keep using it. callback.completed(exchange, holder.connection); return; } final Host host = selectHost(exchange); if (host == null) { callback.couldNotResolveBackend(exchange); } else { exchange.addToAttachmentList(ATTEMPTED_HOSTS, host); if (holder != null || (exclusivityChecker != null && exclusivityChecker.isExclusivityRequired(exchange))) { // If we have a holder, even if the connection was closed we now exclusivity was already requested so our client // may be assuming it still exists. host.connectionPool.connect(target, exchange, new ProxyCallback() { @Override public void completed(HttpServerExchange exchange, ProxyConnection result) { if (holder != null) { holder.connection = result; } else { final ExclusiveConnectionHolder newHolder = new ExclusiveConnectionHolder(); newHolder.connection = result; ServerConnection connection = exchange.getConnection(); connection.putAttachment(exclusiveConnectionKey, newHolder); connection.addCloseListener(new ServerConnection.CloseListener() { @Override public void closed(ServerConnection connection) { ClientConnection clientConnection = newHolder.connection.getConnection(); if (clientConnection.isOpen()) { safeClose(clientConnection); } } }); } callback.completed(exchange, result); } @Override public void queuedRequestFailed(HttpServerExchange exchange) { callback.queuedRequestFailed(exchange); } @Override public void failed(HttpServerExchange exchange) { UndertowLogger.PROXY_REQUEST_LOGGER.proxyFailedToConnectToBackend(exchange.getRequestURI(), host.uri); callback.failed(exchange); } @Override public void couldNotResolveBackend(HttpServerExchange exchange) { callback.couldNotResolveBackend(exchange); } }, timeout, timeUnit, true); } else { host.connectionPool.connect(target, exchange, callback, timeout, timeUnit, false); } } } protected Host selectHost(HttpServerExchange exchange) { AttachmentList attempted = exchange.getAttachment(ATTEMPTED_HOSTS); Host[] hosts = this.hosts; if (hosts.length == 0) { return null; } Iterator parsedRoutes = parseRoutes(exchange); while (parsedRoutes.hasNext()) { // Attempt to find the first existing host which was not yet attempted Host host = this.routes.get(parsedRoutes.next().toString()); if (host != null) { if(attempted == null || !attempted.contains(host)) { return host; } } } int host = hostSelector.selectHost(hosts); final int startHost = host; //if the all hosts have problems we come back to this one Host full = null; Host problem = null; do { Host selected = hosts[host]; if(attempted == null || !attempted.contains(selected)) { ProxyConnectionPool.AvailabilityType available = selected.connectionPool.available(); if (available == AVAILABLE) { return selected; } else if (available == FULL && full == null) { full = selected; } else if ((available == PROBLEM || available == FULL_QUEUE) && problem == null) { problem = selected; } } host = (host + 1) % hosts.length; } while (host != startHost); if (full != null) { return full; } if (problem != null) { return problem; } //no available hosts return null; } protected Iterator parseRoutes(HttpServerExchange exchange) { for (String cookieName : sessionCookieNames) { for (Cookie cookie : exchange.requestCookies()) { if (cookieName.equals(cookie.getName())) { return routeIteratorFactory.iterator(cookie.getValue()); } } } return routeIteratorFactory.iterator(null); } /** * Should only be used for tests * * DO NOT CALL THIS METHOD WHEN REQUESTS ARE IN PROGRESS * * It is not thread safe so internal state can get messed up. */ public void closeCurrentConnections() { for(Host host : hosts) { host.closeCurrentConnections(); } } public final class Host extends ConnectionPoolErrorHandler.SimpleConnectionPoolErrorHandler implements ConnectionPoolManager { final ProxyConnectionPool connectionPool; final String jvmRoute; final URI uri; final XnioSsl ssl; private Host(String jvmRoute, InetSocketAddress bindAddress, URI uri, XnioSsl ssl, OptionMap options) { this.connectionPool = new ProxyConnectionPool(this, bindAddress, uri, ssl, client, options); this.jvmRoute = jvmRoute; this.uri = uri; this.ssl = ssl; } @Override public int getProblemServerRetry() { return problemServerRetry; } @Override public int getMaxConnections() { return connectionsPerThread; } @Override public int getMaxCachedConnections() { return connectionsPerThread; } @Override public int getSMaxConnections() { return softMaxConnectionsPerThread; } @Override public long getTtl() { return ttl; } @Override public int getMaxQueueSize() { return maxQueueSize; } public URI getUri() { return uri; } void closeCurrentConnections() { connectionPool.closeCurrentConnections(); } } private static class ExclusiveConnectionHolder { private ProxyConnection connection; } public interface HostSelector { int selectHost(Host[] availableHosts); } static class RoundRobinHostSelector implements HostSelector { private final AtomicInteger currentHost = new AtomicInteger(0); @Override public int selectHost(Host[] availableHosts) { return currentHost.incrementAndGet() % availableHosts.length; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/ProxyCallback.java000066400000000000000000000034431420065311100322300ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy; import io.undertow.server.HttpServerExchange; /** * Yet another callback class, this one used by the proxy handler * * @author Stuart Douglas */ public interface ProxyCallback { void completed(final HttpServerExchange exchange, T result); /** * Callback if establishing the connection to a backend server fails. * * @param exchange the http server exchange */ void failed(final HttpServerExchange exchange); /** * Callback if no backend server could be found. * * @param exchange the http server exchange */ void couldNotResolveBackend(final HttpServerExchange exchange); /** * This is invoked when the target connection pool transitions to problem status. It will be called once for each queued request * that has not yet been allocated a connection. The manager can redistribute these requests to other hosts, or can end the * exchange with an error status. * * @param exchange The exchange */ void queuedRequestFailed(HttpServerExchange exchange); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/ProxyClient.java000066400000000000000000000047411420065311100317540ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.proxy.LoadBalancingProxyClient.Host; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; /** * A client that provides connections for the proxy handler. The provided connection is valid for the duration of the * current exchange. * * Note that implementation are required to manage the lifecycle of these connections themselves, generally by registering callbacks * on the exchange. * * * * * @author Stuart Douglas */ public interface ProxyClient { /** * Finds a proxy target for this request, returning null if none can be found. * * If this method returns null it means that there is no backend available to handle * this request, and it should proceed as normal. * * @param exchange The exchange * @return The proxy target */ ProxyTarget findTarget(final HttpServerExchange exchange); /** * Gets a proxy connection for the given request. * * @param exchange The exchange * @param callback The callback * @param timeout The timeout * @param timeUnit Time unit for the timeout */ void getConnection(final ProxyTarget target, final HttpServerExchange exchange, final ProxyCallback callback, long timeout, TimeUnit timeUnit); /** * An opaque interface that may contain information about the proxy target */ interface ProxyTarget { } interface MaxRetriesProxyTarget extends ProxyTarget { int getMaxRetries(); } interface HostProxyTarget extends ProxyTarget { void setHost(Host host); } default List getAllTargets(){ return new ArrayList(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/ProxyConnection.java000066400000000000000000000024271420065311100326340ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy; import io.undertow.client.ClientConnection; /** * A connection to a backend proxy. * * @author Stuart Douglas */ public class ProxyConnection { private final ClientConnection connection; private final String targetPath; public ProxyConnection(ClientConnection connection, String targetPath) { this.connection = connection; this.targetPath = targetPath; } public ClientConnection getConnection() { return connection; } public String getTargetPath() { return targetPath; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/ProxyConnectionPool.java000066400000000000000000000673751420065311100335030ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy; import java.io.Closeable; import java.io.IOException; import java.net.InetSocketAddress; import java.net.URI; import java.util.ArrayDeque; import java.util.Deque; import java.util.Map; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientStatistics; import io.undertow.client.UndertowClient; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HttpServerExchange; import io.undertow.util.CopyOnWriteMap; import io.undertow.util.Headers; import io.undertow.util.WorkerUtils; import org.xnio.ChannelListener; import org.xnio.IoUtils; import org.xnio.OptionMap; import org.xnio.XnioExecutor; import org.xnio.XnioIoThread; import org.xnio.ssl.XnioSsl; /** * A pool of connections to a target host. * * This pool can also be used to open connections in exclusive mode, in which case they will not be added to the connection pool. * * In this case the caller is responsible for closing any connections. * * @author Stuart Douglas */ public class ProxyConnectionPool implements Closeable { private final URI uri; private final InetSocketAddress bindAddress; private final XnioSsl ssl; private final UndertowClient client; private final ConnectionPoolManager connectionPoolManager; private final OptionMap options; /** * Set to true when the connection pool is closed. */ private volatile boolean closed; /** * The maximum number of connections that can be established to the target */ private final int maxConnections; /** * The maximum number of connections that will be kept alive once they are idle. If a time to live is set * these connections may be timed out, depending on the value of {@link #coreCachedConnections}. * * NOTE: This value is per IO thread, so to get the actual value this must be multiplied by the number of IO threads */ private final int maxCachedConnections; /** * The minimum number of connections that this proxy connection pool will try and keep established. Once the pool * is down to this number of connections no more connections will be timed out. * * NOTE: This value is per IO thread, so to get the actual value this must be multiplied by the number of IO threads */ private final int coreCachedConnections; /** * The timeout for idle connections. Note that if {@code #coreCachedConnections} is set then once the pool is down * to the core size no more connections will be timed out. */ private final long timeToLive; /** * The total number of open connections, across all threads */ private final AtomicInteger openConnections = new AtomicInteger(0); /** * request count for all closed connections */ private final AtomicLong requestCount = new AtomicLong(); /** * read bytes for all closed connections */ private final AtomicLong read = new AtomicLong(); /** * written bytes for all closed connections */ private final AtomicLong written = new AtomicLong(); private final ConcurrentMap hostThreadData = new CopyOnWriteMap<>(); public ProxyConnectionPool(ConnectionPoolManager connectionPoolManager, URI uri, UndertowClient client, OptionMap options) { this(connectionPoolManager, uri, null, client, options); } public ProxyConnectionPool(ConnectionPoolManager connectionPoolManager,InetSocketAddress bindAddress, URI uri, UndertowClient client, OptionMap options) { this(connectionPoolManager, bindAddress, uri, null, client, options); } public ProxyConnectionPool(ConnectionPoolManager connectionPoolManager, URI uri, XnioSsl ssl, UndertowClient client, OptionMap options) { this(connectionPoolManager, null, uri, ssl, client, options); } public ProxyConnectionPool(ConnectionPoolManager connectionPoolManager, InetSocketAddress bindAddress,URI uri, XnioSsl ssl, UndertowClient client, OptionMap options) { this.connectionPoolManager = connectionPoolManager; this.maxConnections = Math.max(connectionPoolManager.getMaxConnections(), 1); this.maxCachedConnections = Math.max(connectionPoolManager.getMaxCachedConnections(), 0); this.coreCachedConnections = Math.max(connectionPoolManager.getSMaxConnections(), 0); this.timeToLive = connectionPoolManager.getTtl(); this.bindAddress = bindAddress; this.uri = uri; this.ssl = ssl; this.client = client; this.options = options; } public URI getUri() { return uri; } public InetSocketAddress getBindAddress() { return bindAddress; } public void close() { this.closed = true; for (HostThreadData data : hostThreadData.values()) { final ConnectionHolder holder = data.availableConnections.poll(); if (holder != null) { holder.clientConnection.getIoThread().execute(new Runnable() { @Override public void run() { IoUtils.safeClose(holder.clientConnection); } }); } } } /** * Called when the IO thread has completed a successful request * * @param connectionHolder The client connection holder */ private void returnConnection(final ConnectionHolder connectionHolder) { ClientStatistics stats = connectionHolder.clientConnection.getStatistics(); this.requestCount.incrementAndGet(); if(stats != null) { //we update the stats when the connection is closed this.read.addAndGet(stats.getRead()); this.written.addAndGet(stats.getWritten()); stats.reset(); } HostThreadData hostData = getData(); if (closed) { //the host has been closed IoUtils.safeClose(connectionHolder.clientConnection); ConnectionHolder con = hostData.availableConnections.poll(); while (con != null) { IoUtils.safeClose(con.clientConnection); con = hostData.availableConnections.poll(); } redistributeQueued(hostData); return; } //only do something if the connection is open. If it is closed then //the close setter will handle creating a new connection and decrementing //the connection count final ClientConnection connection = connectionHolder.clientConnection; if (connection.isOpen() && !connection.isUpgraded()) { CallbackHolder callback = hostData.awaitingConnections.poll(); while (callback != null && callback.isCancelled()) { callback = hostData.awaitingConnections.poll(); } if (callback != null) { if (callback.getTimeoutKey() != null) { callback.getTimeoutKey().remove(); } // Anything waiting for a connection is not expecting exclusivity. connectionReady(connectionHolder, callback.getCallback(), callback.getExchange(), false); } else { final int cachedConnectionCount = hostData.availableConnections.size(); if (cachedConnectionCount >= maxCachedConnections) { // Close the longest idle connection instead of the current one final ConnectionHolder holder = hostData.availableConnections.poll(); if (holder != null) { IoUtils.safeClose(holder.clientConnection); } } hostData.availableConnections.add(connectionHolder); // If the soft max and ttl are configured if (timeToLive > 0) { //we only start the timeout process once we have hit the core pool size //otherwise connections could start timing out immediately once the core pool size is hit //and if we never hit the core pool size then it does not make sense to start timers which are never //used (as timers are expensive) final long currentTime = System.currentTimeMillis(); connectionHolder.timeout = currentTime + timeToLive; if(hostData.availableConnections.size() > coreCachedConnections) { if (hostData.nextTimeout <= 0) { hostData.timeoutKey = WorkerUtils.executeAfter(connection.getIoThread(), hostData.timeoutTask, timeToLive, TimeUnit.MILLISECONDS); hostData.nextTimeout = connectionHolder.timeout; } } } } } else if (connection.isOpen() && connection.isUpgraded()) { //we treat upgraded connections as closed //as we do not want the connection pool filled with upgraded connections //if the connection is actually closed the close setter will handle it connection.getCloseSetter().set(null); handleClosedConnection(hostData, connectionHolder); } } private void handleClosedConnection(HostThreadData hostData, final ConnectionHolder connection) { openConnections.decrementAndGet(); int connections = --hostData.connections; hostData.availableConnections.remove(connection); if (connections < maxConnections) { CallbackHolder task = hostData.awaitingConnections.poll(); while (task != null && task.isCancelled()) { task = hostData.awaitingConnections.poll(); } if (task != null) { openConnection(task.exchange, task.callback, hostData, false); } } } private void openConnection(final HttpServerExchange exchange, final ProxyCallback callback, final HostThreadData data, final boolean exclusive) { if (!exclusive) { data.connections++; } try { client.connect(new ClientCallback() { @Override public void completed(final ClientConnection result) { openConnections.incrementAndGet(); final ConnectionHolder connectionHolder = new ConnectionHolder(result); if (!exclusive) { result.getCloseSetter().set(new ChannelListener() { @Override public void handleEvent(ClientConnection channel) { handleClosedConnection(data, connectionHolder); } }); } connectionReady(connectionHolder, callback, exchange, exclusive); } @Override public void failed(IOException e) { if (!exclusive) { data.connections--; } UndertowLogger.REQUEST_LOGGER.debug("Failed to connect", e); if (!connectionPoolManager.handleError()) { redistributeQueued(getData()); scheduleFailedHostRetry(exchange); } callback.failed(exchange); } }, bindAddress, getUri(), exchange.getIoThread(), ssl, exchange.getConnection().getByteBufferPool(), options); } catch (RuntimeException e) { // UNDERTOW-1611 // even though exception is unchecked, we still need // to update counts and notify callback and pool manager if (!exclusive) { data.connections--; } // skip scheduling retry, a runtime exception represents the result of a programming problem, // and as such, the client code of such exception cannot reasonably be expected to recover from them // or to handle them in any way connectionPoolManager.handleError(); callback.failed(exchange); throw e; } } private void redistributeQueued(HostThreadData hostData) { CallbackHolder callback = hostData.awaitingConnections.poll(); while (callback != null) { if (callback.getTimeoutKey() != null) { callback.getTimeoutKey().remove(); } if (!callback.isCancelled()) { long time = System.currentTimeMillis(); if (callback.getExpireTime() > 0 && callback.getExpireTime() < time) { callback.getCallback().failed(callback.getExchange()); } else { callback.getCallback().queuedRequestFailed(callback.getExchange()); } } callback = hostData.awaitingConnections.poll(); } } private void connectionReady(final ConnectionHolder result, final ProxyCallback callback, final HttpServerExchange exchange, final boolean exclusive) { try { exchange.addExchangeCompleteListener(new ExchangeCompletionListener() { @Override public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { if (!exclusive) { returnConnection(result); } nextListener.proceed(); } }); } catch (Exception e) { returnConnection(result); callback.failed(exchange); return; } callback.completed(exchange, new ProxyConnection(result.clientConnection, uri.getPath() == null ? "/" : uri.getPath())); } public AvailabilityType available() { if (closed) { return AvailabilityType.CLOSED; } if (!connectionPoolManager.isAvailable()) { return AvailabilityType.PROBLEM; } HostThreadData data = getData(); if (data.connections < maxConnections) { return AvailabilityType.AVAILABLE; } if (!data.availableConnections.isEmpty()) { return AvailabilityType.AVAILABLE; } if (data.awaitingConnections.size() >= connectionPoolManager.getMaxQueueSize()) { return AvailabilityType.FULL_QUEUE; } return AvailabilityType.FULL; } /** * If a host fails we periodically retry * * @param exchange The server exchange */ private void scheduleFailedHostRetry(final HttpServerExchange exchange) { final int retry = connectionPoolManager.getProblemServerRetry(); // only schedule a retry task if the node is not available if (retry > 0 && !connectionPoolManager.isAvailable()) { WorkerUtils.executeAfter(exchange.getIoThread(), new Runnable() { @Override public void run() { if (closed) { return; } UndertowLogger.PROXY_REQUEST_LOGGER.debugf("Attempting to reconnect to failed host %s", getUri()); try { client.connect(new ClientCallback() { @Override public void completed(ClientConnection result) { UndertowLogger.PROXY_REQUEST_LOGGER.debugf("Connected to previously failed host %s, returning to service", getUri()); if (connectionPoolManager.clearError()) { // In case the node is available now, return the connection final ConnectionHolder connectionHolder = new ConnectionHolder(result); final HostThreadData data = getData(); result.getCloseSetter().set(new ChannelListener() { @Override public void handleEvent(ClientConnection channel) { handleClosedConnection(data, connectionHolder); } }); data.connections++; returnConnection(connectionHolder); } else { // Otherwise reschedule the retry task scheduleFailedHostRetry(exchange); } } @Override public void failed(IOException e) { UndertowLogger.PROXY_REQUEST_LOGGER.debugf("Failed to reconnect to failed host %s", getUri()); connectionPoolManager.handleError(); scheduleFailedHostRetry(exchange); } }, bindAddress, getUri(), exchange.getIoThread(), ssl, exchange.getConnection().getByteBufferPool(), options); } catch (RuntimeException e) { // UNDERTOW-1611 // even though exception is unchecked, we still need // to handle the failed to connect error connectionPoolManager.handleError(); scheduleFailedHostRetry(exchange); } } }, retry, TimeUnit.SECONDS); } } /** * Timeout idle connections which are above the soft max cached connections limit. * * @param currentTime the current time * @param data the local host thread data */ private void timeoutConnections(final long currentTime, final HostThreadData data) { int idleConnections = data.availableConnections.size(); for (;;) { ConnectionHolder holder; if (idleConnections > 0 && idleConnections > coreCachedConnections && (holder = data.availableConnections.peek()) != null) { if (!holder.clientConnection.isOpen()) { // Already closed connections decrease the available connections idleConnections--; } else if (currentTime >= holder.timeout) { // If the timeout is reached already, just close holder = data.availableConnections.poll(); IoUtils.safeClose(holder.clientConnection); idleConnections--; } else { if (data.timeoutKey != null) { data.timeoutKey.remove(); data.timeoutKey = null; } // Schedule a timeout task final long remaining = holder.timeout - currentTime + 1; data.nextTimeout = holder.timeout; data.timeoutKey = WorkerUtils.executeAfter(holder.clientConnection.getIoThread(), data.timeoutTask, remaining, TimeUnit.MILLISECONDS); return; } } else { // If we are below the soft limit, just cancel the task if (data.timeoutKey != null) { data.timeoutKey.remove(); data.timeoutKey = null; } data.nextTimeout = -1; return; } } } /** * Gets the host data for this thread * * @return The data for this thread */ private HostThreadData getData() { Thread thread = Thread.currentThread(); if (!(thread instanceof XnioIoThread)) { throw UndertowMessages.MESSAGES.canOnlyBeCalledByIoThread(); } XnioIoThread ioThread = (XnioIoThread) thread; HostThreadData data = hostThreadData.get(ioThread); if (data != null) { return data; } data = new HostThreadData(); HostThreadData existing = hostThreadData.putIfAbsent(ioThread, data); if (existing != null) { return existing; } return data; } public ClientStatistics getClientStatistics() { return new ClientStatistics() { @Override public long getRequests() { return requestCount.get(); } @Override public long getRead() { return read.get(); } @Override public long getWritten() { return written.get(); } @Override public void reset() { requestCount.set(0); read.set(0); written.set(0); } }; } /** * * @return The total number of open connections */ public int getOpenConnections() { return openConnections.get(); } /** * @param exclusive - Is connection for the exclusive use of one client? */ public void connect(ProxyClient.ProxyTarget proxyTarget, HttpServerExchange exchange, ProxyCallback callback, final long timeout, final TimeUnit timeUnit, boolean exclusive) { HostThreadData data = getData(); ConnectionHolder connectionHolder = data.availableConnections.poll(); while (connectionHolder != null && !connectionHolder.clientConnection.isOpen()) { connectionHolder = data.availableConnections.poll(); } boolean upgradeRequest = exchange.getRequestHeaders().contains(Headers.UPGRADE); if (connectionHolder != null && (!upgradeRequest || connectionHolder.clientConnection.isUpgradeSupported())) { if (exclusive) { data.connections--; } connectionReady(connectionHolder, callback, exchange, exclusive); } else if (exclusive || data.connections < maxConnections) { openConnection(exchange, callback, data, exclusive); } else { // Reject the request directly if we reached the max request queue size if (data.awaitingConnections.size() >= connectionPoolManager.getMaxQueueSize()) { callback.queuedRequestFailed(exchange); return; } CallbackHolder holder; if (timeout > 0) { long time = System.currentTimeMillis(); holder = new CallbackHolder(proxyTarget, callback, exchange, time + timeUnit.toMillis(timeout)); holder.setTimeoutKey(WorkerUtils.executeAfter(exchange.getIoThread(), holder, timeout, timeUnit)); } else { holder = new CallbackHolder(proxyTarget, callback, exchange, -1); } data.awaitingConnections.add(holder); } } /** * Should only be used for tests. * */ void closeCurrentConnections() { final CountDownLatch latch = new CountDownLatch(hostThreadData.size()); for(final Map.Entry data : hostThreadData.entrySet()) { data.getKey().execute(new Runnable() { @Override public void run() { ConnectionHolder d = data.getValue().availableConnections.poll(); while (d != null) { IoUtils.safeClose(d.clientConnection); d = data.getValue().availableConnections.poll(); } data.getValue().connections = 0; latch.countDown(); } }); } try { latch.await(10, TimeUnit.SECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } private final class HostThreadData { int connections = 0; XnioIoThread.Key timeoutKey; long nextTimeout = -1; final Deque availableConnections = new ArrayDeque<>(); final Deque awaitingConnections = new ArrayDeque<>(); final Runnable timeoutTask = new Runnable() { @Override public void run() { final long currentTime = System.currentTimeMillis(); timeoutConnections(currentTime, HostThreadData.this); } }; } private static final class ConnectionHolder { private long timeout; private final ClientConnection clientConnection; private ConnectionHolder(ClientConnection clientConnection) { this.clientConnection = clientConnection; } } private static final class CallbackHolder implements Runnable { final ProxyClient.ProxyTarget proxyTarget; final ProxyCallback callback; final HttpServerExchange exchange; final long expireTime; XnioExecutor.Key timeoutKey; boolean cancelled = false; private CallbackHolder(ProxyClient.ProxyTarget proxyTarget, ProxyCallback callback, HttpServerExchange exchange, long expireTime) { this.proxyTarget = proxyTarget; this.callback = callback; this.exchange = exchange; this.expireTime = expireTime; } private ProxyCallback getCallback() { return callback; } private HttpServerExchange getExchange() { return exchange; } private long getExpireTime() { return expireTime; } private XnioExecutor.Key getTimeoutKey() { return timeoutKey; } private boolean isCancelled() { return cancelled || exchange.isResponseStarted(); } private void setTimeoutKey(XnioExecutor.Key timeoutKey) { this.timeoutKey = timeoutKey; } @Override public void run() { cancelled = true; callback.failed(exchange); } public ProxyClient.ProxyTarget getProxyTarget() { return proxyTarget; } } public enum AvailabilityType { /** * The host is read to accept requests */ AVAILABLE, /** * The host is stopped. No request should be forwarded that are not tied * to this node via sticky sessions */ DRAIN, /** * All connections are in use, connections will be queued */ FULL, /** * All connections are in use and the queue is full. Requests will be rejected. */ FULL_QUEUE, /** * The host is probably down, only try as a last resort */ PROBLEM, /** * The host is closed. connections will always fail */ CLOSED; } } ProxyConnectionPoolConfig.java000066400000000000000000000030041420065311100345250ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy; /** * @author Emanuel Muckenhuber */ public interface ProxyConnectionPoolConfig { /** * Get the maximum number of connections per thread. * * @return */ int getMaxConnections(); /** * Get the maximum number of cached (idle) connections per thread. * * @return */ int getMaxCachedConnections(); /** * Get number of cached connections above which are closed after the time to live. * * @return */ int getSMaxConnections(); /** * Get the time to live for idle connections. * * @return */ long getTtl(); /** * Get the maximum number of requests which can be queued if there are no connections available. * * @return */ int getMaxQueueSize(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/ProxyHandler.java000066400000000000000000001305711420065311100321140ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy; import java.io.Closeable; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.channels.Channel; import java.nio.charset.StandardCharsets; import java.security.cert.Certificate; import java.util.Collections; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLPeerUnverifiedException; import java.security.cert.CertificateEncodingException; import io.undertow.UndertowMessages; import io.undertow.server.handlers.ResponseCodeHandler; import org.jboss.logging.Logger; import org.xnio.ChannelExceptionHandler; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.StreamConnection; import org.xnio.XnioExecutor; import org.xnio.channels.StreamSinkChannel; import io.undertow.UndertowLogger; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributes; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientExchange; import io.undertow.client.ClientRequest; import io.undertow.client.ClientResponse; import io.undertow.client.ContinueNotification; import io.undertow.client.ProxiedRequestAttachments; import io.undertow.client.PushCallback; import io.undertow.io.IoCallback; import io.undertow.io.Sender; import io.undertow.predicate.IdempotentPredicate; import io.undertow.predicate.Predicate; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpUpgradeListener; import io.undertow.server.RenegotiationRequiredException; import io.undertow.server.SSLSessionInfo; import io.undertow.server.protocol.http.HttpAttachments; import io.undertow.server.protocol.http.HttpContinue; import io.undertow.util.Attachable; import io.undertow.util.AttachmentKey; import io.undertow.util.Certificates; import io.undertow.util.CopyOnWriteMap; import io.undertow.util.HeaderMap; import io.undertow.util.HeaderValues; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.NetworkUtils; import io.undertow.util.SameThreadExecutor; import io.undertow.util.StatusCodes; import io.undertow.util.Transfer; import io.undertow.util.WorkerUtils; import java.util.List; import java.util.stream.Collectors; /** * An HTTP handler which proxies content to a remote server. *

* This handler acts like a filter. The {@link ProxyClient} has a chance to decide if it * knows how to proxy the request. If it does then it will provide a connection that can * used to connect to the remote server, otherwise the next handler will be invoked and the * request will proceed as normal. * * This handler uses non blocking IO * * @author David M. Lloyd */ public final class ProxyHandler implements HttpHandler { private static final int DEFAULT_MAX_RETRY_ATTEMPTS = Integer.getInteger("io.undertow.server.handlers.proxy.maxRetries", 1); private static final Logger log = Logger.getLogger(ProxyHandler.class.getPackage().getName()); public static final String UTF_8 = StandardCharsets.UTF_8.name(); private static final AttachmentKey CONNECTION = AttachmentKey.create(ProxyConnection.class); private static final AttachmentKey EXCHANGE = AttachmentKey.create(HttpServerExchange.class); private static final AttachmentKey TIMEOUT_KEY = AttachmentKey.create(XnioExecutor.Key.class); private final ProxyClient proxyClient; private final int maxRequestTime; /** * Map of additional headers to add to the request. */ private final Map requestHeaders = new CopyOnWriteMap<>(); private final HttpHandler next; private volatile boolean rewriteHostHeader; private volatile boolean reuseXForwarded; private volatile int maxConnectionRetries; private final Predicate idempotentRequestPredicate; @Deprecated public ProxyHandler(ProxyClient proxyClient, int maxRequestTime, HttpHandler next) { this(proxyClient, maxRequestTime, next, false, false); } /** * * @param proxyClient the client to use to make the proxy call * @param maxRequestTime the maximum amount of time to allow the request to be processed * @param next the next handler in line * @param rewriteHostHeader should the HOST header be rewritten to use the target host of the call. * @param reuseXForwarded should any existing X-Forwarded-For header be used or should it be overwritten. */ @Deprecated public ProxyHandler(ProxyClient proxyClient, int maxRequestTime, HttpHandler next, boolean rewriteHostHeader, boolean reuseXForwarded) { this(proxyClient, maxRequestTime, next, rewriteHostHeader, reuseXForwarded, DEFAULT_MAX_RETRY_ATTEMPTS); } /** * @param proxyClient the client to use to make the proxy call * @param maxRequestTime the maximum amount of time to allow the request to be processed * @param next the next handler in line * @param rewriteHostHeader should the HOST header be rewritten to use the target host of the call. * @param reuseXForwarded should any existing X-Forwarded-For header be used or should it be overwritten. * @param maxConnectionRetries */ @Deprecated public ProxyHandler(ProxyClient proxyClient, int maxRequestTime, HttpHandler next, boolean rewriteHostHeader, boolean reuseXForwarded, int maxConnectionRetries) { this.proxyClient = proxyClient; this.maxRequestTime = maxRequestTime; this.next = next; this.rewriteHostHeader = rewriteHostHeader; this.reuseXForwarded = reuseXForwarded; this.maxConnectionRetries = maxConnectionRetries; this.idempotentRequestPredicate = IdempotentPredicate.INSTANCE; } @Deprecated public ProxyHandler(ProxyClient proxyClient, HttpHandler next) { this(proxyClient, -1, next); } ProxyHandler(Builder builder) { this.proxyClient = builder.proxyClient; this.maxRequestTime = builder.maxRequestTime; this.next = builder.next; this.rewriteHostHeader = builder.rewriteHostHeader; this.reuseXForwarded = builder.reuseXForwarded; this.maxConnectionRetries = builder.maxConnectionRetries; this.idempotentRequestPredicate = builder.idempotentRequestPredicate; for(Map.Entry e : builder.requestHeaders.entrySet()) { requestHeaders.put(e.getKey(), e.getValue()); } } public void handleRequest(final HttpServerExchange exchange) throws Exception { final ProxyClient.ProxyTarget target = proxyClient.findTarget(exchange); if (target == null) { log.debugf("No proxy target for request to %s", exchange.getRequestURL()); next.handleRequest(exchange); return; } if(exchange.isResponseStarted()) { //we can't proxy a request that has already started, this is basically a server configuration error UndertowLogger.REQUEST_LOGGER.cannotProxyStartedRequest(exchange); exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); exchange.endExchange(); return; } final long timeout = maxRequestTime > 0 ? System.currentTimeMillis() + maxRequestTime : 0; int maxRetries = maxConnectionRetries; if(target instanceof ProxyClient.MaxRetriesProxyTarget) { maxRetries = Math.max(maxRetries, ((ProxyClient.MaxRetriesProxyTarget) target).getMaxRetries()); } final ProxyClientHandler clientHandler = new ProxyClientHandler(exchange, target, timeout, maxRetries, idempotentRequestPredicate); if (timeout > 0) { final XnioExecutor.Key key = WorkerUtils.executeAfter(exchange.getIoThread(), new Runnable() { @Override public void run() { clientHandler.cancel(exchange); } }, maxRequestTime, TimeUnit.MILLISECONDS); exchange.putAttachment(TIMEOUT_KEY, key); exchange.addExchangeCompleteListener(new ExchangeCompletionListener() { @Override public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { key.remove(); nextListener.proceed(); } }); } exchange.dispatch(exchange.isInIoThread() ? SameThreadExecutor.INSTANCE : exchange.getIoThread(), clientHandler); } /** * Adds a request header to the outgoing request. If the header resolves to null or an empty string * it will not be added, however any existing header with the same name will be removed. * * @param header The header name * @param attribute The header value attribute. * @return this */ @Deprecated public ProxyHandler addRequestHeader(final HttpString header, final ExchangeAttribute attribute) { requestHeaders.put(header, attribute); return this; } /** * Adds a request header to the outgoing request. If the header resolves to null or an empty string * it will not be added, however any existing header with the same name will be removed. * * @param header The header name * @param value The header value attribute. * @return this */ @Deprecated public ProxyHandler addRequestHeader(final HttpString header, final String value) { requestHeaders.put(header, ExchangeAttributes.constant(value)); return this; } /** * Adds a request header to the outgoing request. If the header resolves to null or an empty string * it will not be added, however any existing header with the same name will be removed. *

* The attribute value will be parsed, and the resulting exchange attribute will be used to create the actual header * value. * * @param header The header name * @param attribute The header value attribute. * @return this */ @Deprecated public ProxyHandler addRequestHeader(final HttpString header, final String attribute, final ClassLoader classLoader) { requestHeaders.put(header, ExchangeAttributes.parser(classLoader).parse(attribute)); return this; } /** * Removes a request header * * @param header the header * @return this */ @Deprecated public ProxyHandler removeRequestHeader(final HttpString header) { requestHeaders.remove(header); return this; } static void copyHeaders(final HeaderMap to, final HeaderMap from) { long f = from.fastIterateNonEmpty(); HeaderValues values; while (f != -1L) { values = from.fiCurrent(f); if(!to.contains(values.getHeaderName())) { //don't over write existing headers, normally the map will be empty, if it is not we assume it is not for a reason to.putAll(values.getHeaderName(), values); } f = from.fiNextNonEmpty(f); } } public ProxyClient getProxyClient() { return proxyClient; } @Override public String toString() { List proxyTargets = proxyClient.getAllTargets(); if (proxyTargets.isEmpty()){ return "ProxyHandler - "+proxyClient.getClass().getSimpleName(); } if(proxyTargets.size()==1 && !rewriteHostHeader){ return "reverse-proxy( '" + proxyTargets.get(0).toString() + "' )"; } else { String outputResult = "reverse-proxy( { '" + proxyTargets.stream().map(s -> s.toString()).collect(Collectors.joining("', '")) + "' }"; if(rewriteHostHeader){ outputResult += ", rewrite-host-header=true"; } return outputResult+" )"; } } private final class ProxyClientHandler implements ProxyCallback, Runnable { private int tries; private final long timeout; private final int maxRetryAttempts; private final HttpServerExchange exchange; private final Predicate idempotentPredicate; private ProxyClient.ProxyTarget target; ProxyClientHandler(HttpServerExchange exchange, ProxyClient.ProxyTarget target, long timeout, int maxRetryAttempts, Predicate idempotentPredicate) { this.exchange = exchange; this.timeout = timeout; this.maxRetryAttempts = maxRetryAttempts; this.target = target; this.idempotentPredicate = idempotentPredicate; } @Override public void run() { proxyClient.getConnection(target, exchange, this, -1, TimeUnit.MILLISECONDS); } @Override public void completed(final HttpServerExchange exchange, final ProxyConnection connection) { exchange.putAttachment(CONNECTION, connection); exchange.dispatch(SameThreadExecutor.INSTANCE, new ProxyAction(connection, exchange, requestHeaders, rewriteHostHeader, reuseXForwarded, exchange.isRequestComplete() ? this : null, idempotentPredicate)); } @Override public void failed(final HttpServerExchange exchange) { final long time = System.currentTimeMillis(); if (tries++ < maxRetryAttempts) { if (timeout > 0 && time > timeout) { cancel(exchange); } else { target = proxyClient.findTarget(exchange); if (target != null) { final long remaining = timeout > 0 ? timeout - time : -1; proxyClient.getConnection(target, exchange, this, remaining, TimeUnit.MILLISECONDS); } else { couldNotResolveBackend(exchange); // The context was registered when we started, so return 503 } } } else { couldNotResolveBackend(exchange); } } @Override public void queuedRequestFailed(HttpServerExchange exchange) { failed(exchange); } @Override public void couldNotResolveBackend(HttpServerExchange exchange) { if (exchange.isResponseStarted()) { IoUtils.safeClose(exchange.getConnection()); } else { exchange.setStatusCode(StatusCodes.SERVICE_UNAVAILABLE); exchange.endExchange(); } } void cancel(final HttpServerExchange exchange) { //NOTE: this method is called only in context of timeouts. final ProxyConnection connectionAttachment = exchange.getAttachment(CONNECTION); if (connectionAttachment != null) { ClientConnection clientConnection = connectionAttachment.getConnection(); UndertowLogger.PROXY_REQUEST_LOGGER.timingOutRequest(clientConnection.getPeerAddress() + "" + exchange.getRequestURI()); IoUtils.safeClose(clientConnection); } else { UndertowLogger.PROXY_REQUEST_LOGGER.timingOutRequest(exchange.getRequestURI()); } if (exchange.isResponseStarted()) { IoUtils.safeClose(exchange.getConnection()); } else { exchange.setStatusCode(StatusCodes.GATEWAY_TIME_OUT); exchange.endExchange(); } } } private static class ProxyAction implements Runnable { private final ProxyConnection clientConnection; private final HttpServerExchange exchange; private final Map requestHeaders; private final boolean rewriteHostHeader; private final boolean reuseXForwarded; private final ProxyClientHandler proxyClientHandler; private final Predicate idempotentPredicate; ProxyAction(final ProxyConnection clientConnection, final HttpServerExchange exchange, Map requestHeaders, boolean rewriteHostHeader, boolean reuseXForwarded, ProxyClientHandler proxyClientHandler, Predicate idempotentPredicate) { this.clientConnection = clientConnection; this.exchange = exchange; this.requestHeaders = requestHeaders; this.rewriteHostHeader = rewriteHostHeader; this.reuseXForwarded = reuseXForwarded; this.proxyClientHandler = proxyClientHandler; this.idempotentPredicate = idempotentPredicate; } @Override public void run() { final ClientRequest request = new ClientRequest(); String targetURI = exchange.getRequestURI(); if(exchange.isHostIncludedInRequestURI()) { int uriPart = targetURI.indexOf("//"); if(uriPart != -1) { uriPart = targetURI.indexOf("/", uriPart + 2); if(uriPart != -1) { targetURI = targetURI.substring(uriPart); } } } if(!exchange.getResolvedPath().isEmpty() && targetURI.startsWith(exchange.getResolvedPath())) { targetURI = targetURI.substring(exchange.getResolvedPath().length()); } StringBuilder requestURI = new StringBuilder(); if(!clientConnection.getTargetPath().isEmpty() && (!clientConnection.getTargetPath().equals("/") || targetURI.isEmpty())) { requestURI.append(clientConnection.getTargetPath()); } requestURI.append(targetURI); String qs = exchange.getQueryString(); if (qs != null && !qs.isEmpty()) { requestURI.append('?'); requestURI.append(qs); } request.setPath(requestURI.toString()) .setMethod(exchange.getRequestMethod()); final HeaderMap inboundRequestHeaders = exchange.getRequestHeaders(); final HeaderMap outboundRequestHeaders = request.getRequestHeaders(); copyHeaders(outboundRequestHeaders, inboundRequestHeaders); if (!exchange.isPersistent()) { //just because the client side is non-persistent //we don't want to close the connection to the backend outboundRequestHeaders.put(Headers.CONNECTION, "keep-alive"); } if("h2c".equals(exchange.getRequestHeaders().getFirst(Headers.UPGRADE))) { //we don't allow h2c upgrade requests to be passed through to the backend exchange.getRequestHeaders().remove(Headers.UPGRADE); outboundRequestHeaders.put(Headers.CONNECTION, "keep-alive"); } for (Map.Entry entry : requestHeaders.entrySet()) { String headerValue = entry.getValue().readAttribute(exchange); if (headerValue == null || headerValue.isEmpty()) { outboundRequestHeaders.remove(entry.getKey()); } else { outboundRequestHeaders.put(entry.getKey(), headerValue.replace('\n', ' ')); } } final String remoteHost; final SocketAddress address = exchange.getSourceAddress(); if (address != null) { remoteHost = ((InetSocketAddress) address).getHostString(); if(!((InetSocketAddress) address).isUnresolved()) { request.putAttachment(ProxiedRequestAttachments.REMOTE_ADDRESS, ((InetSocketAddress) address).getAddress().getHostAddress()); } } else { //should never happen, unless this is some form of mock request remoteHost = "localhost"; } request.putAttachment(ProxiedRequestAttachments.REMOTE_HOST, remoteHost); if (reuseXForwarded && request.getRequestHeaders().contains(Headers.X_FORWARDED_FOR)) { // We have an existing header so we shall simply append the host to the existing list final String current = request.getRequestHeaders().getFirst(Headers.X_FORWARDED_FOR); if (current == null || current.isEmpty()) { // It was empty so just add it request.getRequestHeaders().put(Headers.X_FORWARDED_FOR, remoteHost); } else { // Add the new entry and reset the existing header request.getRequestHeaders().put(Headers.X_FORWARDED_FOR, current + "," + remoteHost); } } else { // No existing header or not allowed to reuse the header so set it here request.getRequestHeaders().put(Headers.X_FORWARDED_FOR, remoteHost); } //if we don't support push set a header saying so //this is non standard, and a problem with the HTTP2 spec, but they did not want to listen if(!exchange.getConnection().isPushSupported() && clientConnection.getConnection().isPushSupported()) { request.getRequestHeaders().put(Headers.X_DISABLE_PUSH, "true"); } // Set the protocol header and attachment if(reuseXForwarded && exchange.getRequestHeaders().contains(Headers.X_FORWARDED_PROTO)) { final String proto = exchange.getRequestHeaders().getFirst(Headers.X_FORWARDED_PROTO); request.putAttachment(ProxiedRequestAttachments.IS_SSL, proto.equals("https")); } else { final String proto = exchange.getRequestScheme().equals("https") ? "https" : "http"; request.getRequestHeaders().put(Headers.X_FORWARDED_PROTO, proto); request.putAttachment(ProxiedRequestAttachments.IS_SSL, proto.equals("https")); } // Set the server name if(reuseXForwarded && exchange.getRequestHeaders().contains(Headers.X_FORWARDED_SERVER)) { final String hostName = exchange.getRequestHeaders().getFirst(Headers.X_FORWARDED_SERVER); request.putAttachment(ProxiedRequestAttachments.SERVER_NAME, hostName); } else { final String hostName = exchange.getHostName(); request.getRequestHeaders().put(Headers.X_FORWARDED_SERVER, hostName); request.putAttachment(ProxiedRequestAttachments.SERVER_NAME, hostName); } if(!exchange.getRequestHeaders().contains(Headers.X_FORWARDED_HOST)) { final String hostName = exchange.getHostName(); if(hostName != null) { request.getRequestHeaders().put(Headers.X_FORWARDED_HOST, NetworkUtils.formatPossibleIpv6Address(hostName)); } } // Set the port if(reuseXForwarded && exchange.getRequestHeaders().contains(Headers.X_FORWARDED_PORT)) { try { int port = Integer.parseInt(exchange.getRequestHeaders().getFirst(Headers.X_FORWARDED_PORT)); request.putAttachment(ProxiedRequestAttachments.SERVER_PORT, port); } catch (NumberFormatException e) { int port = exchange.getConnection().getLocalAddress(InetSocketAddress.class).getPort(); request.getRequestHeaders().put(Headers.X_FORWARDED_PORT, port); request.putAttachment(ProxiedRequestAttachments.SERVER_PORT, port); } } else { int port = exchange.getHostPort(); request.getRequestHeaders().put(Headers.X_FORWARDED_PORT, port); request.putAttachment(ProxiedRequestAttachments.SERVER_PORT, port); } SSLSessionInfo sslSessionInfo = exchange.getConnection().getSslSessionInfo(); if (sslSessionInfo != null) { Certificate[] peerCertificates; try { peerCertificates = sslSessionInfo.getPeerCertificates(); if (peerCertificates.length > 0) { request.putAttachment(ProxiedRequestAttachments.SSL_CERT, Certificates.toPem(peerCertificates[0])); } } catch (SSLPeerUnverifiedException | CertificateEncodingException | RenegotiationRequiredException e) { //ignore } request.putAttachment(ProxiedRequestAttachments.SSL_CYPHER, sslSessionInfo.getCipherSuite()); request.putAttachment(ProxiedRequestAttachments.SSL_SESSION_ID, sslSessionInfo.getSessionId()); request.putAttachment(ProxiedRequestAttachments.SSL_KEY_SIZE, sslSessionInfo.getKeySize()); } if(rewriteHostHeader) { InetSocketAddress targetAddress = clientConnection.getConnection().getPeerAddress(InetSocketAddress.class); request.getRequestHeaders().put(Headers.HOST, targetAddress.getHostString() + ":" + targetAddress.getPort()); request.getRequestHeaders().put(Headers.X_FORWARDED_HOST, exchange.getRequestHeaders().getFirst(Headers.HOST)); } if(log.isDebugEnabled()) { log.debugf("Sending request %s to target %s for exchange %s", request, clientConnection.getConnection().getPeerAddress(), exchange); } //handle content //if the frontend is HTTP/2 then we may need to add a Transfer-Encoding header, to indicate to the backend //that there is content if(!request.getRequestHeaders().contains(Headers.TRANSFER_ENCODING) && !request.getRequestHeaders().contains(Headers.CONTENT_LENGTH)) { if(!exchange.isRequestComplete()) { request.getRequestHeaders().put(Headers.TRANSFER_ENCODING, Headers.CHUNKED.toString()); } } clientConnection.getConnection().sendRequest(request, new ClientCallback() { @Override public void completed(final ClientExchange result) { if(log.isDebugEnabled()) { log.debugf("Sent request %s to target %s for exchange %s", request, remoteHost, exchange); } result.putAttachment(EXCHANGE, exchange); boolean requiresContinueResponse = HttpContinue.requiresContinueResponse(exchange); if (requiresContinueResponse) { result.setContinueHandler(new ContinueNotification() { @Override public void handleContinue(final ClientExchange clientExchange) { if(log.isDebugEnabled()) { log.debugf("Received continue response to request %s to target %s for exchange %s", request, clientConnection.getConnection().getPeerAddress(), exchange); } HttpContinue.sendContinueResponse(exchange, new IoCallback() { @Override public void onComplete(final HttpServerExchange exchange, final Sender sender) { //don't care } @Override public void onException(final HttpServerExchange exchange, final Sender sender, final IOException exception) { IoUtils.safeClose(clientConnection.getConnection()); exchange.endExchange(); UndertowLogger.REQUEST_IO_LOGGER.ioException(exception); } }); } }); } //handle server push if(exchange.getConnection().isPushSupported() && result.getConnection().isPushSupported()) { result.setPushHandler(new PushCallback() { @Override public boolean handlePush(ClientExchange originalRequest, final ClientExchange pushedRequest) { if(log.isDebugEnabled()) { log.debugf("Sending push request %s received from %s to target %s for exchange %s", pushedRequest.getRequest(), request, remoteHost, exchange); } final ClientRequest request = pushedRequest.getRequest(); exchange.getConnection().pushResource(request.getPath(), request.getMethod(), request.getRequestHeaders(), new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { String path = request.getPath(); int i = path.indexOf("?"); if(i > 0) { path = path.substring(0, i); } exchange.dispatch(SameThreadExecutor.INSTANCE, new ProxyAction(new ProxyConnection(pushedRequest.getConnection(), path), exchange, requestHeaders, rewriteHostHeader, reuseXForwarded, null, idempotentPredicate)); } }); return true; } }); } result.setResponseListener(new ResponseCallback(exchange, proxyClientHandler, idempotentPredicate)); final IoExceptionHandler handler = new IoExceptionHandler(exchange, clientConnection.getConnection()); if(requiresContinueResponse) { try { if(!result.getRequestChannel().flush()) { result.getRequestChannel().getWriteSetter().set(ChannelListeners.flushingChannelListener(new ChannelListener() { @Override public void handleEvent(StreamSinkChannel channel) { Transfer.initiateTransfer(exchange.getRequestChannel(), result.getRequestChannel(), ChannelListeners.closingChannelListener(), new HTTPTrailerChannelListener(exchange, result, exchange, proxyClientHandler, idempotentPredicate), handler, handler, exchange.getConnection().getByteBufferPool()); } }, handler)); result.getRequestChannel().resumeWrites(); return; } } catch (IOException e) { handler.handleException(result.getRequestChannel(), e); } } HTTPTrailerChannelListener trailerListener = new HTTPTrailerChannelListener(exchange, result, exchange, proxyClientHandler, idempotentPredicate); if(!exchange.isRequestComplete()) { Transfer.initiateTransfer(exchange.getRequestChannel(), result.getRequestChannel(), ChannelListeners.closingChannelListener(), trailerListener, handler, handler, exchange.getConnection().getByteBufferPool()); } else { trailerListener.handleEvent(result.getRequestChannel()); } } @Override public void failed(IOException e) { handleFailure(exchange, proxyClientHandler, idempotentPredicate, e); } }); } } static void handleFailure(HttpServerExchange exchange, ProxyClientHandler proxyClientHandler, Predicate idempotentRequestPredicate, IOException e) { UndertowLogger.PROXY_REQUEST_LOGGER.proxyRequestFailed(exchange.getRequestURI(), e); if(exchange.isResponseStarted()) { IoUtils.safeClose(exchange.getConnection()); } else if(idempotentRequestPredicate.resolve(exchange) && proxyClientHandler != null) { proxyClientHandler.failed(exchange); //this will attempt a retry if configured to do so } else { exchange.setStatusCode(StatusCodes.SERVICE_UNAVAILABLE); exchange.endExchange(); } } private static final class ResponseCallback implements ClientCallback { private final HttpServerExchange exchange; private final ProxyClientHandler proxyClientHandler; private final Predicate idempotentPredicate; private ResponseCallback(HttpServerExchange exchange, ProxyClientHandler proxyClientHandler, Predicate idempotentPredicate) { this.exchange = exchange; this.proxyClientHandler = proxyClientHandler; this.idempotentPredicate = idempotentPredicate; } @Override public void completed(final ClientExchange result) { final ClientResponse response = result.getResponse(); if(log.isDebugEnabled()) { log.debugf("Received response %s for request %s for exchange %s", response, result.getRequest(), exchange); } final HeaderMap inboundResponseHeaders = response.getResponseHeaders(); final HeaderMap outboundResponseHeaders = exchange.getResponseHeaders(); exchange.setStatusCode(response.getResponseCode()); copyHeaders(outboundResponseHeaders, inboundResponseHeaders); if (exchange.isUpgrade()) { exchange.upgradeChannel(new HttpUpgradeListener() { @Override public void handleUpgrade(StreamConnection streamConnection, HttpServerExchange exchange) { if(log.isDebugEnabled()) { log.debugf("Upgraded request %s to for exchange %s", result.getRequest(), exchange); } StreamConnection clientChannel = null; try { clientChannel = result.getConnection().performUpgrade(); final ClosingExceptionHandler handler = new ClosingExceptionHandler(streamConnection, clientChannel); Transfer.initiateTransfer(clientChannel.getSourceChannel(), streamConnection.getSinkChannel(), ChannelListeners.closingChannelListener(), ChannelListeners.writeShutdownChannelListener(ChannelListeners.flushingChannelListener(ChannelListeners.closingChannelListener(), ChannelListeners.closingChannelExceptionHandler()), ChannelListeners.closingChannelExceptionHandler()), handler, handler, result.getConnection().getBufferPool()); Transfer.initiateTransfer(streamConnection.getSourceChannel(), clientChannel.getSinkChannel(), ChannelListeners.closingChannelListener(), ChannelListeners.writeShutdownChannelListener(ChannelListeners.flushingChannelListener(ChannelListeners.closingChannelListener(), ChannelListeners.closingChannelExceptionHandler()), ChannelListeners.closingChannelExceptionHandler()), handler, handler, result.getConnection().getBufferPool()); } catch (IOException e) { IoUtils.safeClose(streamConnection, clientChannel); } } }); } final IoExceptionHandler handler = new IoExceptionHandler(exchange, result.getConnection()); Transfer.initiateTransfer(result.getResponseChannel(), exchange.getResponseChannel(), ChannelListeners.closingChannelListener(), new HTTPTrailerChannelListener(result, exchange, exchange, proxyClientHandler, idempotentPredicate), handler, handler, exchange.getConnection().getByteBufferPool()); } @Override public void failed(IOException e) { handleFailure(exchange, proxyClientHandler, idempotentPredicate, e); } } private static final class HTTPTrailerChannelListener implements ChannelListener { private final Attachable source; private final Attachable target; private final HttpServerExchange exchange; private final ProxyClientHandler proxyClientHandler; private final Predicate idempotentPredicate; private HTTPTrailerChannelListener(final Attachable source, final Attachable target, HttpServerExchange exchange, ProxyClientHandler proxyClientHandler, Predicate idempotentPredicate) { this.source = source; this.target = target; this.exchange = exchange; this.proxyClientHandler = proxyClientHandler; this.idempotentPredicate = idempotentPredicate; } @Override public void handleEvent(final StreamSinkChannel channel) { HeaderMap trailers = source.getAttachment(HttpAttachments.REQUEST_TRAILERS); if (trailers != null) { target.putAttachment(HttpAttachments.RESPONSE_TRAILERS, trailers); } try { channel.shutdownWrites(); if (!channel.flush()) { channel.getWriteSetter().set(ChannelListeners.flushingChannelListener(new ChannelListener() { @Override public void handleEvent(StreamSinkChannel channel) { channel.suspendWrites(); channel.getWriteSetter().set(null); } }, ChannelListeners.closingChannelExceptionHandler())); channel.resumeWrites(); } else { channel.getWriteSetter().set(null); channel.shutdownWrites(); } } catch (IOException e) { handleFailure(exchange, proxyClientHandler, idempotentPredicate, e); } catch (Exception e) { handleFailure(exchange, proxyClientHandler, idempotentPredicate, new IOException(e)); } } } private static final class IoExceptionHandler implements ChannelExceptionHandler { private final HttpServerExchange exchange; private final ClientConnection clientConnection; private IoExceptionHandler(HttpServerExchange exchange, ClientConnection clientConnection) { this.exchange = exchange; this.clientConnection = clientConnection; } @Override public void handleException(Channel channel, IOException exception) { IoUtils.safeClose(channel); IoUtils.safeClose(clientConnection); if (exchange.isResponseStarted()) { UndertowLogger.REQUEST_IO_LOGGER.debug("Exception reading from target server", exception); if (!exchange.isResponseStarted()) { exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); exchange.endExchange(); } else { IoUtils.safeClose(exchange.getConnection()); } } else { UndertowLogger.REQUEST_IO_LOGGER.ioException(exception); exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); exchange.endExchange(); } } } @Deprecated public ProxyHandler setMaxConnectionRetries(int maxConnectionRetries) { this.maxConnectionRetries = maxConnectionRetries; return this; } public boolean isRewriteHostHeader() { return rewriteHostHeader; } @Deprecated public ProxyHandler setRewriteHostHeader(boolean rewriteHostHeader) { this.rewriteHostHeader = rewriteHostHeader; return this; } public boolean isReuseXForwarded() { return reuseXForwarded; } @Deprecated public ProxyHandler setReuseXForwarded(boolean reuseXForwarded) { this.reuseXForwarded = reuseXForwarded; return this; } public int getMaxConnectionRetries() { return maxConnectionRetries; } public Predicate getIdempotentRequestPredicate() { return idempotentRequestPredicate; } private static final class ClosingExceptionHandler implements ChannelExceptionHandler { private final Closeable[] toClose; private ClosingExceptionHandler(Closeable... toClose) { this.toClose = toClose; } @Override public void handleException(Channel channel, IOException exception) { IoUtils.safeClose(channel); IoUtils.safeClose(toClose); } } public static Builder builder() { return new Builder(); } public static class Builder { private ProxyClient proxyClient; private int maxRequestTime = -1; private final Map requestHeaders = new CopyOnWriteMap<>(); private HttpHandler next = ResponseCodeHandler.HANDLE_404; private boolean rewriteHostHeader; private boolean reuseXForwarded; private int maxConnectionRetries = DEFAULT_MAX_RETRY_ATTEMPTS; private Predicate idempotentRequestPredicate = IdempotentPredicate.INSTANCE; Builder() {} public ProxyClient getProxyClient() { return proxyClient; } public Builder setProxyClient(ProxyClient proxyClient) { if(proxyClient == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("proxyClient"); } this.proxyClient = proxyClient; return this; } public int getMaxRequestTime() { return maxRequestTime; } public Builder setMaxRequestTime(int maxRequestTime) { this.maxRequestTime = maxRequestTime; return this; } public Map getRequestHeaders() { return Collections.unmodifiableMap(requestHeaders); } public Builder addRequestHeader(HttpString header, ExchangeAttribute value) { this.requestHeaders.put(header, value); return this; } public HttpHandler getNext() { return next; } public Builder setNext(HttpHandler next) { this.next = next; return this; } public boolean isRewriteHostHeader() { return rewriteHostHeader; } public Builder setRewriteHostHeader(boolean rewriteHostHeader) { this.rewriteHostHeader = rewriteHostHeader; return this; } public boolean isReuseXForwarded() { return reuseXForwarded; } public Builder setReuseXForwarded(boolean reuseXForwarded) { this.reuseXForwarded = reuseXForwarded; return this; } public int getMaxConnectionRetries() { return maxConnectionRetries; } public Builder setMaxConnectionRetries(int maxConnectionRetries) { this.maxConnectionRetries = maxConnectionRetries; return this; } public Predicate getIdempotentRequestPredicate() { return idempotentRequestPredicate; } public Builder setIdempotentRequestPredicate(Predicate idempotentRequestPredicate) { if(idempotentRequestPredicate == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("idempotentRequestPredicate"); } this.idempotentRequestPredicate = idempotentRequestPredicate; return this; } public ProxyHandler build() { return new ProxyHandler(this); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/ProxyHandlerBuilder.java000066400000000000000000000043661420065311100334250ustar00rootroot00000000000000package io.undertow.server.handlers.proxy; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.handlers.builder.HandlerBuilder; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * @author Stuart Douglas */ public class ProxyHandlerBuilder implements HandlerBuilder { @Override public String name() { return "reverse-proxy"; } @Override public Map> parameters() { Map> params = new HashMap<>(); params.put("hosts", String[].class); params.put("rewrite-host-header", Boolean.class); return params; } @Override public Set requiredParameters() { return Collections.singleton("hosts"); } @Override public String defaultParameter() { return "hosts"; } @Override public HandlerWrapper build(Map config) { String[] hosts = (String[]) config.get("hosts"); List uris = new ArrayList<>(); for (String host : hosts) { try { uris.add(new URI(host)); } catch (URISyntaxException e) { throw new RuntimeException(e); } } Boolean rewriteHostHeader = (Boolean) config.get("rewrite-host-header"); return new Wrapper(uris, rewriteHostHeader); } private static class Wrapper implements HandlerWrapper { private final List uris; private final boolean rewriteHostHeader; private Wrapper(List uris, Boolean rewriteHostHeader) { this.uris = uris; this.rewriteHostHeader = rewriteHostHeader != null && rewriteHostHeader; } @Override public HttpHandler wrap(HttpHandler handler) { final LoadBalancingProxyClient loadBalancingProxyClient = new LoadBalancingProxyClient(); for (URI url : uris) { loadBalancingProxyClient.addHost(url); } return new ProxyHandler(loadBalancingProxyClient, -1, handler, rewriteHostHeader, false); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/RouteIteratorFactory.java000066400000000000000000000132471420065311100336350ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy; import java.nio.CharBuffer; import java.util.Iterator; import java.util.NoSuchElementException; /** * Factory for route/affinity iterator parser. This implementation lazily parses routes while supporting strategies in * {@link RouteParsingStrategy} including ranked routing. The iterator never creates new String instances but returns * a CharSequence wrapper from the existing session ID. * * @author Radoslav Husar */ public class RouteIteratorFactory { public enum ParsingCompatibility { MOD_JK, MOD_CLUSTER, } private final RouteParsingStrategy routeParsingStrategy; private final ParsingCompatibility parsingCompatibility; private final String rankedRouteDelimiter; /** * @param routeParsingStrategy route parsing strategy * @param parsingCompatibility route parsing compatibility behavior */ public RouteIteratorFactory(RouteParsingStrategy routeParsingStrategy, ParsingCompatibility parsingCompatibility) { this(routeParsingStrategy, parsingCompatibility, null); } /** * @param routeParsingStrategy route parsing strategy * @param parsingCompatibility route parsing compatibility behavior * @param rankedRouteDelimiter String sequence to split routes at if ranked routing is enabled */ public RouteIteratorFactory(RouteParsingStrategy routeParsingStrategy, ParsingCompatibility parsingCompatibility, String rankedRouteDelimiter) { if (routeParsingStrategy == RouteParsingStrategy.RANKED && rankedRouteDelimiter == null) { throw new IllegalArgumentException(); } this.routeParsingStrategy = routeParsingStrategy; this.parsingCompatibility = parsingCompatibility; this.rankedRouteDelimiter = rankedRouteDelimiter; } /** * Returns an {@link Iterator} of routes. * * @param sessionId String of sessionID from the cookie/parameter possibly including encoded/appended affinity/route information * @return routes iterator; never returns {@code null} */ public Iterator iterator(String sessionId) { return new RouteIterator(sessionId); } private class RouteIterator implements Iterator { private final String sessionId; private boolean nextResolved; private int nextPos; private CharSequence next; RouteIterator(String sessionId) { this.sessionId = sessionId; if (routeParsingStrategy == RouteParsingStrategy.NONE) { this.nextResolved = true; this.next = null; } else { int index = (sessionId == null) ? -1 : sessionId.indexOf('.'); if (index == -1) { // Case where there is no routing information at all. this.nextResolved = true; this.next = null; } else { nextPos = index + 1; } } } @Override public boolean hasNext() { resolveNext(); return next != null; } @Override public CharSequence next() { resolveNext(); if (next != null) { CharSequence result = next; nextResolved = (routeParsingStrategy != RouteParsingStrategy.RANKED); next = null; return result; } throw new NoSuchElementException(); } private void resolveNext() { if (!nextResolved) { if (routeParsingStrategy != RouteParsingStrategy.RANKED) { if (parsingCompatibility == ParsingCompatibility.MOD_JK) { // mod_jk aka LoadBalancingProxyClient uses mod_jk parsing mechanism though never supports domain // it treats route only as the sequence after the first "." but before the second "."; // i.e. does not allow "." in route int last = sessionId.indexOf('.', nextPos); if (last == -1) { last = sessionId.length(); } next = CharBuffer.wrap(sessionId, nextPos, last); } else { // mod_cluster treats everything after first "." as the route; i.e. allows "." in route next = CharBuffer.wrap(sessionId, nextPos, sessionId.length()); } } else if (nextPos >= sessionId.length()) { next = null; } else { int currentPos = sessionId.indexOf(rankedRouteDelimiter, nextPos); next = CharBuffer.wrap(sessionId, nextPos, (currentPos != -1) ? currentPos : sessionId.length()); nextPos += next.length() + rankedRouteDelimiter.length(); } nextResolved = true; } } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/RouteParsingStrategy.java000066400000000000000000000022001420065311100336250ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy; /** * Enumeration of supported route parsing strategies. * * @author Radoslav Husar */ public enum RouteParsingStrategy { /** * Ignores encoded route/affinity information altogether, essentially disabling session stickiness on the load balancer. */ NONE, /** * Default behavior. */ SINGLE, /** * Enables ranked affinity support. */ RANKED, } SimpleProxyClientProvider.java000066400000000000000000000105241420065311100345560ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.UndertowClient; import io.undertow.server.HttpServerExchange; import io.undertow.server.ServerConnection; import io.undertow.util.AttachmentKey; import org.xnio.ChannelListener; import org.xnio.IoUtils; import org.xnio.OptionMap; import java.io.IOException; import java.net.URI; import java.nio.channels.Channel; import java.util.concurrent.TimeUnit; /** * Simple proxy client provider. This provider simply proxies to another server, using a a one to one * connection strategy. * * {@link LoadBalancingProxyClient} should be used instead. This proxy client is too simplistic for * real world use cases, and it not set up to use SSL. * * @author Stuart Douglas */ @Deprecated public class SimpleProxyClientProvider implements ProxyClient { private final URI uri; private final AttachmentKey clientAttachmentKey = AttachmentKey.create(ClientConnection.class); private final UndertowClient client; private static final ProxyTarget TARGET = new ProxyTarget() {}; public SimpleProxyClientProvider(URI uri) { this.uri = uri; client = UndertowClient.getInstance(); } @Override public ProxyTarget findTarget(HttpServerExchange exchange) { return TARGET; } @Override public void getConnection(ProxyTarget target, HttpServerExchange exchange, ProxyCallback callback, long timeout, TimeUnit timeUnit) { ClientConnection existing = exchange.getConnection().getAttachment(clientAttachmentKey); if (existing != null) { if (existing.isOpen()) { //this connection already has a client, re-use it callback.completed(exchange, new ProxyConnection(existing, uri.getPath() == null ? "/" : uri.getPath())); return; } else { exchange.getConnection().removeAttachment(clientAttachmentKey); } } client.connect(new ConnectNotifier(callback, exchange), uri, exchange.getIoThread(), exchange.getConnection().getByteBufferPool(), OptionMap.EMPTY); } private final class ConnectNotifier implements ClientCallback { private final ProxyCallback callback; private final HttpServerExchange exchange; private ConnectNotifier(ProxyCallback callback, HttpServerExchange exchange) { this.callback = callback; this.exchange = exchange; } @Override public void completed(final ClientConnection connection) { final ServerConnection serverConnection = exchange.getConnection(); //we attach to the connection so it can be re-used serverConnection.putAttachment(clientAttachmentKey, connection); serverConnection.addCloseListener(new ServerConnection.CloseListener() { @Override public void closed(ServerConnection serverConnection) { IoUtils.safeClose(connection); } }); connection.getCloseSetter().set(new ChannelListener() { @Override public void handleEvent(Channel channel) { serverConnection.removeAttachment(clientAttachmentKey); } }); callback.completed(exchange, new ProxyConnection(connection, uri.getPath() == null ? "/" : uri.getPath())); } @Override public void failed(IOException e) { callback.failed(exchange); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/000077500000000000000000000000001420065311100311435ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/Balancer.java000066400000000000000000000176551420065311100335330ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import io.undertow.UndertowLogger; import java.util.concurrent.atomic.AtomicInteger; /** * The mod_cluster balancer config. * * @author Stuart Douglas * @author Emanuel Muckenhuber */ public class Balancer { /** * Name of the balancer. max size: 40, Default: "mycluster" */ private final String name; /** * {@code true}: use JVMRoute to stick a request to a node, {@code false}: ignore JVMRoute. Default: {@code true} */ private final boolean stickySession; /** * Name of the cookie containing the session-id. Max size: 30 Default: "JSESSIONID" */ private final String stickySessionCookie; /** * Name of the parameter containing the session-id. Max size: 30. Default: "jsessionid" */ private final String stickySessionPath; /** * {@code true}: remove the session-id (cookie or parameter) when the request can't be * routed to the right node. {@code false}: send it anyway. Default: {@code false} */ private final boolean stickySessionRemove; /** * {@code true}: Return an error if the request can't be routed according to * JVMRoute, {@code false}: Route it to another node. Default: {@code true} */ private final boolean stickySessionForce; /** * value in seconds: time to wait for an available worker. Default: "0" no wait. */ private final int waitWorker; /** * Maximum number of failover attempts to send the request to the backend server. Default: "1" */ private final int maxRetries; private final int id; private static final AtomicInteger idGen = new AtomicInteger(); Balancer(BalancerBuilder b) { this.id = idGen.incrementAndGet(); this.name = b.getName(); this.stickySession = b.isStickySession(); this.stickySessionCookie = b.getStickySessionCookie(); this.stickySessionPath = b.getStickySessionPath(); this.stickySessionRemove = b.isStickySessionRemove(); this.stickySessionForce = b.isStickySessionForce(); this.waitWorker = b.getWaitWorker(); this.maxRetries = b.getMaxRetries(); UndertowLogger.ROOT_LOGGER.balancerCreated(this.id, this.name, this.stickySession, this.stickySessionCookie, this.stickySessionPath, this.stickySessionRemove, this.stickySessionForce, this.waitWorker, this.maxRetries); } public int getId() { return id; } public String getName() { return this.name; } public boolean isStickySession() { return this.stickySession; } public String getStickySessionCookie() { return this.stickySessionCookie; } public String getStickySessionPath() { return this.stickySessionPath; } public boolean isStickySessionRemove() { return this.stickySessionRemove; } public boolean isStickySessionForce() { return this.stickySessionForce; } public int getWaitWorker() { return this.waitWorker; } public int getMaxRetries() { return this.maxRetries; } @Deprecated public int getMaxattempts() { return this.maxRetries; } @Override public String toString() { return new StringBuilder("balancer: Name: ") .append(this.name).append(", Sticky: ").append(this.stickySession ? 1 : 0) .append(" [").append(this.stickySessionCookie).append("]/[") .append(this.stickySessionPath).append("], remove: ") .append(this.stickySessionRemove ? 1 : 0).append(", force: ") .append(this.stickySessionForce ? 1 : 0).append(", Timeout: ") .append(this.waitWorker).append(", Maxtry: ").append(this.maxRetries).toString(); } static BalancerBuilder builder() { return new BalancerBuilder(); } public static final class BalancerBuilder { private String name = "mycluster"; private boolean stickySession = true; private String stickySessionCookie = "JSESSIONID"; private String stickySessionPath = "jsessionid"; private boolean stickySessionRemove = false; private boolean stickySessionForce = true; private int waitWorker = 0; private int maxRetries = 1; public String getName() { return name; } public BalancerBuilder setName(String name) { this.name = name; return this; } public boolean isStickySession() { return stickySession; } public BalancerBuilder setStickySession(boolean stickySession) { this.stickySession = stickySession; return this; } public String getStickySessionCookie() { return stickySessionCookie; } public BalancerBuilder setStickySessionCookie(String stickySessionCookie) { if (stickySessionCookie != null && stickySessionCookie.length() > 30) { this.stickySessionCookie = stickySessionCookie.substring(0, 30); UndertowLogger.ROOT_LOGGER.stickySessionCookieLengthTruncated(stickySessionCookie, this.stickySessionCookie); } else { this.stickySessionCookie = stickySessionCookie; } return this; } public String getStickySessionPath() { return stickySessionPath; } public BalancerBuilder setStickySessionPath(String stickySessionPath) { this.stickySessionPath = stickySessionPath; return this; } public boolean isStickySessionRemove() { return stickySessionRemove; } public BalancerBuilder setStickySessionRemove(boolean stickySessionRemove) { this.stickySessionRemove = stickySessionRemove; return this; } public boolean isStickySessionForce() { return stickySessionForce; } public BalancerBuilder setStickySessionForce(boolean stickySessionForce) { this.stickySessionForce = stickySessionForce; return this; } public int getWaitWorker() { return waitWorker; } public BalancerBuilder setWaitWorker(int waitWorker) { this.waitWorker = waitWorker; return this; } public int getMaxRetries() { return this.maxRetries; } /** * Maximum number of failover attempts to send the request to the backend server. * * @param maxRetries number of failover attempts */ public BalancerBuilder setMaxRetries(int maxRetries) { this.maxRetries = maxRetries; return this; } /** * @deprecated Use {@link BalancerBuilder#getMaxRetries()}. */ @Deprecated public int getMaxattempts() { return maxRetries; } /** * @deprecated Use {@link BalancerBuilder#setMaxRetries(int)}. */ @Deprecated public BalancerBuilder setMaxattempts(int maxattempts) { this.maxRetries = maxattempts; return this; } public Balancer build() { return new Balancer(this); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/Context.java000066400000000000000000000141621420065311100334360ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.allAreSet; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.proxy.ProxyCallback; import io.undertow.server.handlers.proxy.ProxyConnection; /** * * @author Emanuel Muckenhuber */ class Context { private static final AtomicInteger idGen = new AtomicInteger(); enum Status { ENABLED, DISABLED, STOPPED, ; } private final int id; private final Node node; private final String path; private final Node.VHostMapping vhost; private static final int STOPPED = (1 << 31); private static final int DISABLED = (1 << 30); private static final int REQUEST_MASK = ((1 << 30) - 1); private volatile int state = STOPPED; private static final AtomicIntegerFieldUpdater stateUpdater = AtomicIntegerFieldUpdater.newUpdater(Context.class, "state"); Context(final String path, final Node.VHostMapping vHost, final Node node) { id = idGen.incrementAndGet(); this.path = path; this.node = node; this.vhost = vHost; } public int getId() { return id; } public String getJVMRoute() { return node.getJvmRoute(); } public String getPath() { return path; } public List getVirtualHosts() { return vhost.getAliases(); } public int getActiveRequests() { return state & REQUEST_MASK; } public Status getStatus() { final int state = this.state; if ((state & STOPPED) == STOPPED) { return Status.STOPPED; } else if ((state & DISABLED) == DISABLED) { return Status.DISABLED; } return Status.ENABLED; } public boolean isEnabled() { return allAreClear(state, DISABLED | STOPPED); } public boolean isStopped() { return allAreSet(state, STOPPED); } public boolean isDisabled() { return allAreSet(state, DISABLED); } Node getNode() { return node; } Node.VHostMapping getVhost() { return vhost; } boolean checkAvailable(boolean existingSession) { if (node.checkAvailable(existingSession)) { return existingSession ? !isStopped() : isEnabled(); } return false; } void enable() { int oldState, newState; for (;;) { oldState = this.state; newState = oldState & ~(STOPPED | DISABLED); if (stateUpdater.compareAndSet(this, oldState, newState)) { return; } } } void disable() { int oldState, newState; for (;;) { oldState = this.state; newState = oldState | DISABLED; if (stateUpdater.compareAndSet(this, oldState, newState)) { return; } } } void stop() { int oldState, newState; for (;;) { oldState = this.state; newState = oldState | STOPPED; if (stateUpdater.compareAndSet(this, oldState, newState)) { return; } } } /** * Handle a proxy request for this context. * * @param target the proxy target * @param exchange the http server exchange * @param callback the proxy callback * @param timeout the timeout * @param timeUnit the time unit * @param exclusive whether this connection is exclusive */ void handleRequest(final ModClusterProxyTarget target, final HttpServerExchange exchange, final ProxyCallback callback, long timeout, TimeUnit timeUnit, boolean exclusive) { if (addRequest()) { exchange.addExchangeCompleteListener(new ExchangeCompletionListener() { @Override public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { requestDone(); nextListener.proceed(); } }); node.getConnectionPool().connect(target, exchange, callback, timeout, timeUnit, exclusive); } else { callback.failed(exchange); } } boolean addRequest() { int oldState, newState; for (;;) { oldState = this.state; if ((oldState & STOPPED) != 0) { return false; } newState = oldState + 1; if ((newState & REQUEST_MASK) == REQUEST_MASK) { return false; } if (stateUpdater.compareAndSet(this, oldState, newState)) { return true; } } } void requestDone() { int oldState, newState; for (;;) { oldState = this.state; if ((oldState & REQUEST_MASK) == 0) { return; } newState = oldState - 1; if (stateUpdater.compareAndSet(this, oldState, newState)) { return; } } } @Override public String toString() { return "Context{" + ", path='" + path + '\'' + '}'; } } MCMPAdvertiseTask.java000066400000000000000000000207701420065311100351630ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import io.undertow.UndertowLogger; import io.undertow.util.NetworkUtils; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.XnioWorker; import org.xnio.channels.MulticastMessageChannel; import java.io.IOException; import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.concurrent.TimeUnit; /** * @author Emanuel Muckenhuber */ class MCMPAdvertiseTask implements Runnable { public static final String RFC_822_FMT = "EEE, d MMM yyyy HH:mm:ss Z"; private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(RFC_822_FMT, Locale.US); private static final boolean linuxLike; private static final boolean windows; static { String value = System.getProperty("os.name"); linuxLike = (value != null) && (value.toLowerCase().startsWith("linux") || value.toLowerCase().startsWith("mac") || value.toLowerCase().startsWith("hp")); windows = (value != null) && value.toLowerCase().contains("win"); } private volatile int seq = 0; private final String protocol; private final String host; private final int port; private final String path; private final byte[] ssalt; private final MessageDigest md; private final InetSocketAddress address; private final ModClusterContainer container; private final MulticastMessageChannel channel; static void advertise(final ModClusterContainer container, final MCMPConfig.AdvertiseConfig config, final XnioWorker worker) throws IOException { InetSocketAddress bindAddress; final InetAddress address = InetAddress.getByName(config.getAdvertiseAddress()); if (address == null) { bindAddress = new InetSocketAddress(config.getAdvertisePort()); } else { bindAddress = new InetSocketAddress(address, config.getAdvertisePort()); } MulticastMessageChannel channel; try { channel = worker.createUdpServer(bindAddress, OptionMap.EMPTY); } catch (IOException e) { if (address != null && (linuxLike || windows)) { //try again with no address //see UNDERTOW-454 UndertowLogger.ROOT_LOGGER.potentialCrossTalking(address, (address instanceof Inet4Address) ? "IPv4" : "IPv6", e.getLocalizedMessage()); bindAddress = new InetSocketAddress(config.getAdvertisePort()); channel = worker.createUdpServer(bindAddress, OptionMap.EMPTY); } else { throw e; } } // multicast ttl can only be set after the channel has been created channel.setOption(Options.MULTICAST_TTL, config.getAdvertiseTtl()); final MCMPAdvertiseTask task = new MCMPAdvertiseTask(container, config, channel); //execute immediately, so there is no delay before load balancing starts working channel.getIoThread().execute(task); channel.getIoThread().executeAtInterval(task, config.getAdvertiseFrequency(), TimeUnit.MILLISECONDS); } MCMPAdvertiseTask(final ModClusterContainer container, final MCMPConfig.AdvertiseConfig config, final MulticastMessageChannel channel) throws IOException { this.container = container; this.protocol = config.getProtocol(); // MODCLUSTER-483 mod_cluster client does not yet support ipv6 addresses with zone indices so skip it String host = config.getManagementSocketAddress().getHostString(); int zoneIndex = host.indexOf("%"); this.host = (zoneIndex < 0) ? host : host.substring(0, zoneIndex); this.port = config.getManagementSocketAddress().getPort(); this.path = config.getPath(); this.channel = channel; final InetAddress group = InetAddress.getByName(config.getAdvertiseGroup()); address = new InetSocketAddress(group, config.getAdvertisePort()); try { md = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } final String securityKey = config.getSecurityKey(); if (securityKey == null) { // Security key is not configured, so the result hash was zero bytes ssalt = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; } else { md.reset(); digestString(md, securityKey); ssalt = md.digest(); } UndertowLogger.ROOT_LOGGER.proxyAdvertisementsStarted(address.toString(), config.getAdvertiseFrequency()); } private static final String CRLF = "\r\n"; /* * the messages to send are something like: * * HTTP/1.0 200 OK * Date: Thu, 13 Sep 2012 09:24:02 GMT * Sequence: 5 * Digest: ae8e7feb7cd85be346134657de3b0661 * Server: b58743ba-fd84-11e1-bd12-ad866be2b4cc * X-Manager-Address: 127.0.0.1:6666 * X-Manager-Url: /b58743ba-fd84-11e1-bd12-ad866be2b4cc * X-Manager-Protocol: http * X-Manager-Host: 10.33.144.3 * */ @Override public void run() { try { /* * apr_uuid_get(&magd->suuid); * magd->srvid[0] = '/'; * apr_uuid_format(&magd->srvid[1], &magd->suuid); * In fact we use the srvid on the 2 second byte [1] */ final byte[] ssalt = this.ssalt; final String server = container.getServerID(); final String date = DATE_FORMAT.format(new Date(System.currentTimeMillis())); final String seq = "" + this.seq++; final byte[] digest; synchronized (md) { md.reset(); md.update(ssalt); digestString(md, date); digestString(md, seq); digestString(md, server); digest = md.digest(); } final String digestString = bytesToHexString(digest); final StringBuilder builder = new StringBuilder(); builder.append("HTTP/1.0 200 OK").append(CRLF) .append("Date: ").append(date).append(CRLF) .append("Sequence: ").append(seq).append(CRLF) .append("Digest: ").append(digestString).append(CRLF) .append("Server: ").append(server).append(CRLF) .append("X-Manager-Address: ").append(NetworkUtils.formatPossibleIpv6Address(host)).append(":").append(port).append(CRLF) .append("X-Manager-Url: ").append(path).append(CRLF) .append("X-Manager-Protocol: ").append(protocol).append(CRLF) .append("X-Manager-Host: ").append(host).append(CRLF); final String payload = builder.toString(); final ByteBuffer byteBuffer = ByteBuffer.wrap(payload.getBytes(StandardCharsets.US_ASCII)); UndertowLogger.ROOT_LOGGER.proxyAdvertiseMessagePayload(payload); channel.sendTo(address, byteBuffer); } catch (Exception e) { UndertowLogger.ROOT_LOGGER.proxyAdvertiseCannotSendMessage(e, address); } } private void digestString(MessageDigest md, String securityKey) { byte[] buf = securityKey.getBytes(StandardCharsets.UTF_8); md.update(buf); } private static final char[] TABLE = "0123456789abcdef".toCharArray(); static String bytesToHexString(final byte[] bytes) { final StringBuilder builder = new StringBuilder(bytes.length * 2); for (byte b : bytes) { builder.append(TABLE[b >> 4 & 0x0f]).append(TABLE[b & 0x0f]); } return builder.toString(); } } MCMPConfig.java000066400000000000000000000211041420065311100336070ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import io.undertow.UndertowLogger; import io.undertow.server.HttpHandler; import java.net.InetSocketAddress; /** * @author Emanuel Muckenhuber * @author Radoslav Husar * @version March 2016 */ public class MCMPConfig { public static Builder builder() { return new Builder(); } public static WebBuilder webBuilder() { return new WebBuilder(); } private final InetSocketAddress managementSocketAddress; private final AdvertiseConfig advertiseConfig; public MCMPConfig(Builder builder) { this.managementSocketAddress = new InetSocketAddress(builder.managementHost, builder.managementPort); if (managementSocketAddress.isUnresolved()) { throw UndertowLogger.PROXY_REQUEST_LOGGER.unableToResolveModClusterManagementHost(builder.managementHost); } if (managementSocketAddress.getAddress().isAnyLocalAddress()) { throw UndertowLogger.PROXY_REQUEST_LOGGER.cannotUseWildcardAddressAsModClusterManagementHost(builder.managementHost); } if (builder.advertiseBuilder != null) { this.advertiseConfig = new AdvertiseConfig(builder.advertiseBuilder, this); } else { this.advertiseConfig = null; } } @Deprecated public String getManagementHost() { return managementSocketAddress.getHostString(); } @Deprecated public int getManagementPort() { return managementSocketAddress.getPort(); } public InetSocketAddress getManagementSocketAddress() { return managementSocketAddress; } public AdvertiseConfig getAdvertiseConfig() { return advertiseConfig; } public HttpHandler create(final ModCluster modCluster, final HttpHandler next) { return new MCMPHandler(this, modCluster, next); } static class MCMPWebManagerConfig extends MCMPConfig { private final boolean allowCmd; private final boolean checkNonce; private final boolean reduceDisplay; MCMPWebManagerConfig(WebBuilder builder) { super(builder); this.allowCmd = builder.allowCmd; this.checkNonce = builder.checkNonce; this.reduceDisplay = builder.reduceDisplay; } public boolean isAllowCmd() { return allowCmd; } public boolean isCheckNonce() { return checkNonce; } public boolean isReduceDisplay() { return reduceDisplay; } @Override public HttpHandler create(ModCluster modCluster, HttpHandler next) { return new MCMPWebManager(this, modCluster, next); } } static class AdvertiseConfig { private final String advertiseGroup; private final String advertiseAddress; private final int advertisePort; private final int advertiseTtl; private final int advertiseFrequency; private final String securityKey; private final String protocol; private final String path; private final InetSocketAddress managementSocketAddress; AdvertiseConfig(AdvertiseBuilder builder, MCMPConfig config) { this.advertiseGroup = builder.advertiseGroup; this.advertiseAddress = builder.advertiseAddress; this.advertisePort = builder.advertisePort; this.advertiseTtl = builder.advertiseTtl; this.advertiseFrequency = builder.advertiseFrequency; this.securityKey = builder.securityKey; this.protocol = builder.protocol; this.path = builder.path; this.managementSocketAddress = config.getManagementSocketAddress(); } public String getAdvertiseGroup() { return advertiseGroup; } public String getAdvertiseAddress() { return advertiseAddress; } public int getAdvertisePort() { return advertisePort; } public int getAdvertiseTtl() { return advertiseTtl; } public int getAdvertiseFrequency() { return advertiseFrequency; } public String getSecurityKey() { return securityKey; } public String getProtocol() { return protocol; } public String getPath() { return path; } public InetSocketAddress getManagementSocketAddress() { return managementSocketAddress; } } public static class Builder { String managementHost; int managementPort; AdvertiseBuilder advertiseBuilder; public Builder setManagementHost(String managementHost) { this.managementHost = managementHost; return this; } public Builder setManagementPort(int managementPort) { this.managementPort = managementPort; return this; } public AdvertiseBuilder enableAdvertise() { this.advertiseBuilder = new AdvertiseBuilder(this); return advertiseBuilder; } public MCMPConfig build() { return new MCMPConfig(this); } public HttpHandler create(final ModCluster modCluster, final HttpHandler next) { final MCMPConfig config = build(); return config.create(modCluster, next); } } public static class WebBuilder extends Builder { boolean checkNonce = true; boolean reduceDisplay = false; boolean allowCmd = true; public WebBuilder setCheckNonce(boolean checkNonce) { this.checkNonce = checkNonce; return this; } public WebBuilder setReduceDisplay(boolean reduceDisplay) { this.reduceDisplay = reduceDisplay; return this; } public WebBuilder setAllowCmd(boolean allowCmd) { this.allowCmd = allowCmd; return this; } @Override public MCMPConfig build() { return new MCMPWebManagerConfig(this); } } public static class AdvertiseBuilder { String advertiseGroup = "224.0.1.105"; String advertiseAddress = "127.0.0.1"; int advertisePort = 23364; int advertiseTtl = 10; int advertiseFrequency = 10000; String securityKey; String protocol = "http"; String path = "/"; private final Builder parent; public AdvertiseBuilder(Builder parent) { this.parent = parent; } public AdvertiseBuilder setAdvertiseGroup(String advertiseGroup) { this.advertiseGroup = advertiseGroup; return this; } public AdvertiseBuilder setAdvertiseAddress(String advertiseAddress) { this.advertiseAddress = advertiseAddress; return this; } public AdvertiseBuilder setAdvertisePort(int advertisePort) { this.advertisePort = advertisePort; return this; } public AdvertiseBuilder setAdvertiseTtl(int advertiseTtl) { this.advertiseTtl = advertiseTtl; return this; } public AdvertiseBuilder setAdvertiseFrequency(int advertiseFrequency) { this.advertiseFrequency = advertiseFrequency; return this; } public AdvertiseBuilder setSecurityKey(String securityKey) { this.securityKey = securityKey; return this; } public AdvertiseBuilder setProtocol(String protocol) { this.protocol = protocol; return this; } public AdvertiseBuilder setPath(String path) { if (path.startsWith("/")) { this.path = path; } else { this.path = "/" + path; } return this; } public Builder getParent() { return parent; } } } MCMPConstants.java000066400000000000000000000065671420065311100343760ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import io.undertow.util.HttpString; /** * @author Emanuel Muckenhuber */ interface MCMPConstants { String ALIAS_STRING = "Alias"; String BALANCER_STRING = "Balancer"; String CONTEXT_STRING = "Context"; String DOMAIN_STRING = "Domain"; String FLUSH_PACKET_STRING = "flushpackets"; String FLUSH_WAIT_STRING = "flushwait"; String HOST_STRING = "Host"; String JVMROUTE_STRING = "JVMRoute"; String LOAD_STRING = "Load"; String MAXATTEMPTS_STRING = "Maxattempts"; String PING_STRING = "ping"; String PORT_STRING = "Port"; String REVERSED_STRING = "Reversed"; String SCHEME_STRING = "Scheme"; String SMAX_STRING = "smax"; String STICKYSESSION_STRING = "StickySession"; String STICKYSESSIONCOOKIE_STRING = "StickySessionCookie"; String STICKYSESSIONPATH_STRING = "StickySessionPath"; String STICKYSESSIONREMOVE_STRING = "StickySessionRemove"; String STICKYSESSIONFORCE_STRING = "StickySessionForce"; String TIMEOUT_STRING = "Timeout"; String TTL_STRING = "ttl"; String TYPE_STRING = "Type"; String WAITWORKER_STRING = "WaitWorker"; HttpString ALIAS = new HttpString(ALIAS_STRING); HttpString BALANCER = new HttpString(BALANCER_STRING); HttpString CONTEXT = new HttpString(CONTEXT_STRING); HttpString DOMAIN = new HttpString(DOMAIN_STRING); HttpString FLUSH_PACKET = new HttpString(FLUSH_PACKET_STRING); HttpString FLUSH_WAIT = new HttpString(FLUSH_WAIT_STRING); HttpString HOST = new HttpString(HOST_STRING); HttpString JVMROUTE = new HttpString(JVMROUTE_STRING); HttpString LOAD = new HttpString(LOAD_STRING); HttpString MAXATTEMPTS = new HttpString(MAXATTEMPTS_STRING); HttpString PING = new HttpString(PING_STRING); HttpString PORT = new HttpString(PORT_STRING); HttpString REVERSED = new HttpString(REVERSED_STRING); HttpString SCHEME = new HttpString(SCHEME_STRING); HttpString SMAX = new HttpString(SMAX_STRING); HttpString STICKYSESSION = new HttpString(STICKYSESSION_STRING); HttpString STICKYSESSIONCOOKIE = new HttpString(STICKYSESSIONCOOKIE_STRING); HttpString STICKYSESSIONPATH = new HttpString(STICKYSESSIONPATH_STRING); HttpString STICKYSESSIONREMOVE = new HttpString(STICKYSESSIONREMOVE_STRING); HttpString STICKYSESSIONFORCE = new HttpString(STICKYSESSIONFORCE_STRING); HttpString TIMEOUT = new HttpString(TIMEOUT_STRING); HttpString TTL = new HttpString(TTL_STRING); HttpString TYPE = new HttpString(TYPE_STRING); HttpString WAITWORKER = new HttpString(WAITWORKER_STRING); String TYPESYNTAX = "SYNTAX"; String TYPEMEM = "MEM"; } MCMPErrorCode.java000066400000000000000000000053331420065311100342740ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.TYPEMEM; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.TYPESYNTAX; /** * @author Emanuel Muckenhuber */ enum MCMPErrorCode { CANT_READ_NODE(TYPEMEM, "MEM: Can't read node"), CANT_UPDATE_NODE(TYPEMEM, "MEM: Can't update or insert node"), CANT_UPDATE_CONTEXT(TYPEMEM, "MEM: Can't update or insert context"), NODE_STILL_EXISTS(TYPESYNTAX, "MEM: Old node still exist"), ; private final String type; private final String message; MCMPErrorCode(String type, String message) { this.type = type; this.message = message; } public String getType() { return type; } public String getMessage() { return message; } /** the syntax error messages String SMESPAR = "SYNTAX: Can't parse message"; String SBALBIG = "SYNTAX: Balancer field too big"; String SBAFBIG = "SYNTAX: A field is too big"; String SROUBIG = "SYNTAX: JVMRoute field too big"; String SROUBAD = "SYNTAX: JVMRoute can't be empty"; String SDOMBIG = "SYNTAX: LBGroup field too big"; String SHOSBIG = "SYNTAX: Host field too big"; String SPORBIG = "SYNTAX: Port field too big"; String STYPBIG = "SYNTAX: Type field too big"; String SALIBAD = "SYNTAX: Alias without Context"; String SCONBAD = "SYNTAX: Context without Alias"; String SBADFLD = "SYNTAX: Invalid field "; String SBADFLD1 = " in message"; String SMISFLD = "SYNTAX: Mandatory field(s) missing in message"; String SCMDUNS = "SYNTAX: Command is not supported"; String SMULALB = "SYNTAX: Only one Alias in APP command"; String SMULCTB = "SYNTAX: Only one Context in APP command"; String SREADER = "SYNTAX: %s can't read POST data"; String MBALAUI = "MEM: Can't update or insert balancer"; String MHOSTRD = "MEM: Can't read host alias"; String MHOSTUI = "MEM: Can't update or insert host alias"; */ } MCMPHandler.java000066400000000000000000001013101420065311100337550ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.Version; import io.undertow.io.Sender; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.form.FormData; import io.undertow.server.handlers.form.FormDataParser; import io.undertow.server.handlers.form.FormEncodedDataDefinition; import io.undertow.server.handlers.form.FormParserFactory; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.StatusCodes; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.ssl.XnioSsl; import java.io.IOException; import java.net.InetSocketAddress; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.ALIAS; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.BALANCER; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.CONTEXT; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.DOMAIN; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.FLUSH_PACKET; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.FLUSH_WAIT; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.HOST; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.JVMROUTE; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.LOAD; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.MAXATTEMPTS; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.PORT; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.REVERSED; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.SCHEME; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.SMAX; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.STICKYSESSION; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.STICKYSESSIONCOOKIE; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.STICKYSESSIONFORCE; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.STICKYSESSIONPATH; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.STICKYSESSIONREMOVE; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.TIMEOUT; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.TTL; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.TYPE; import static io.undertow.server.handlers.proxy.mod_cluster.MCMPConstants.WAITWORKER; /** * The mod cluster management protocol http handler. * * @author Emanuel Muckenhuber */ class MCMPHandler implements HttpHandler { enum MCMPAction { ENABLE, DISABLE, STOP, REMOVE, ; } public static final HttpString CONFIG = new HttpString("CONFIG"); public static final HttpString ENABLE_APP = new HttpString("ENABLE-APP"); public static final HttpString DISABLE_APP = new HttpString("DISABLE-APP"); public static final HttpString STOP_APP = new HttpString("STOP-APP"); public static final HttpString REMOVE_APP = new HttpString("REMOVE-APP"); public static final HttpString STATUS = new HttpString("STATUS"); public static final HttpString DUMP = new HttpString("DUMP"); public static final HttpString INFO = new HttpString("INFO"); public static final HttpString PING = new HttpString("PING"); private static final Set HANDLED_METHODS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(CONFIG, ENABLE_APP, DISABLE_APP, STOP_APP, REMOVE_APP, STATUS, INFO, DUMP, PING))); protected static final String VERSION_PROTOCOL = "0.2.1"; protected static final String MOD_CLUSTER_EXPOSED_VERSION = "mod_cluster_undertow/" + Version.getVersionString(); private static final String CONTENT_TYPE = "text/plain; charset=ISO-8859-1"; /* the syntax error messages */ private static final String TYPESYNTAX = MCMPConstants.TYPESYNTAX; private static final String SCONBAD = "SYNTAX: Context without Alias"; private static final String SBADFLD = "SYNTAX: Invalid field "; private static final String SBADFLD1 = " in message"; private static final String SMISFLD = "SYNTAX: Mandatory field(s) missing in message"; private final FormParserFactory parserFactory; private final MCMPConfig config; private final HttpHandler next; private final long creationTime = System.currentTimeMillis(); // This should change with each restart private final ModCluster modCluster; protected final ModClusterContainer container; MCMPHandler(MCMPConfig config, ModCluster modCluster, HttpHandler next) { this.config = config; this.next = next; this.modCluster = modCluster; this.container = modCluster.getContainer(); this.parserFactory = FormParserFactory.builder(false).addParser(new FormEncodedDataDefinition().setForceCreation(true)).build(); UndertowLogger.ROOT_LOGGER.mcmpHandlerCreated(); } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { final HttpString method = exchange.getRequestMethod(); if(!handlesMethod(method)) { next.handleRequest(exchange); return; } /* * Proxy the request that needs to be proxied and process others */ // TODO maybe this should be handled outside here? final InetSocketAddress addr = exchange.getConnection().getLocalAddress(InetSocketAddress.class); if (!addr.isUnresolved() && addr.getPort() != config.getManagementSocketAddress().getPort() || !Arrays.equals(addr.getAddress().getAddress(), config.getManagementSocketAddress().getAddress().getAddress())) { next.handleRequest(exchange); return; } if(exchange.isInIoThread()) { //for now just do all the management stuff in a worker, as it uses blocking IO exchange.dispatch(this); return; } try { handleRequest(method, exchange); } catch (Exception e) { UndertowLogger.ROOT_LOGGER.failedToProcessManagementReq(e); exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); exchange.getResponseHeaders().add(Headers.CONTENT_TYPE, CONTENT_TYPE); final Sender sender = exchange.getResponseSender(); sender.send("failed to process management request"); } } protected boolean handlesMethod(HttpString method) { return HANDLED_METHODS.contains(method); } /** * Handle a management+ request. * * @param method the http method * @param exchange the http server exchange * @throws Exception */ protected void handleRequest(final HttpString method, HttpServerExchange exchange) throws Exception { final RequestData requestData = parseFormData(exchange); boolean persistent = exchange.isPersistent(); exchange.setPersistent(false); //UNDERTOW-947 MCMP should not use persistent connections if (CONFIG.equals(method)) { processConfig(exchange, requestData); } else if (ENABLE_APP.equals(method)) { processCommand(exchange, requestData, MCMPAction.ENABLE); } else if (DISABLE_APP.equals(method)) { processCommand(exchange, requestData, MCMPAction.DISABLE); } else if (STOP_APP.equals(method)) { processCommand(exchange, requestData, MCMPAction.STOP); } else if (REMOVE_APP.equals(method)) { processCommand(exchange, requestData, MCMPAction.REMOVE); } else if (STATUS.equals(method)) { processStatus(exchange, requestData); } else if (INFO.equals(method)) { processInfo(exchange); } else if (DUMP.equals(method)) { processDump(exchange); } else if (PING.equals(method)) { processPing(exchange, requestData); } else { exchange.setPersistent(persistent); next.handleRequest(exchange); } } /** * Process the node config. * * @param exchange the http server exchange * @param requestData the request data */ private void processConfig(final HttpServerExchange exchange, final RequestData requestData) { // Get the node builder List hosts = null; List contexts = null; final Balancer.BalancerBuilder balancer = Balancer.builder(); final NodeConfig.NodeBuilder node = NodeConfig.builder(modCluster); final Iterator i = requestData.iterator(); while (i.hasNext()) { final HttpString name = i.next(); final String value = requestData.getFirst(name); UndertowLogger.ROOT_LOGGER.mcmpKeyValue(name, value); if (!checkString(value)) { processError(TYPESYNTAX, SBADFLD + name + SBADFLD1, exchange); return; } if (BALANCER.equals(name)) { node.setBalancer(value); balancer.setName(value); } else if (MAXATTEMPTS.equals(name)) { balancer.setMaxRetries(Integer.parseInt(value)); } else if (STICKYSESSION.equals(name)) { if ("No".equalsIgnoreCase(value)) { balancer.setStickySession(false); } } else if (STICKYSESSIONCOOKIE.equals(name)) { balancer.setStickySessionCookie(value); } else if (STICKYSESSIONPATH.equals(name)) { balancer.setStickySessionPath(value); } else if (STICKYSESSIONREMOVE.equals(name)) { if ("Yes".equalsIgnoreCase(value)) { balancer.setStickySessionRemove(true); } } else if (STICKYSESSIONFORCE.equals(name)) { if ("no".equalsIgnoreCase(value)) { balancer.setStickySessionForce(false); } } else if (JVMROUTE.equals(name)) { node.setJvmRoute(value); } else if (DOMAIN.equals(name)) { node.setDomain(value); } else if (HOST.equals(name)) { node.setHostname(value); } else if (PORT.equals(name)) { node.setPort(Integer.parseInt(value)); } else if (TYPE.equals(name)) { node.setType(value); } else if (REVERSED.equals(name)) { continue; // ignore } else if (FLUSH_PACKET.equals(name)) { if ("on".equalsIgnoreCase(value)) { node.setFlushPackets(true); } else if ("auto".equalsIgnoreCase(value)) { node.setFlushPackets(true); } } else if (FLUSH_WAIT.equals(name)) { node.setFlushwait(Integer.parseInt(value)); } else if (MCMPConstants.PING.equals(name)) { node.setPing(Integer.parseInt(value)); } else if (SMAX.equals(name)) { node.setSmax(Integer.parseInt(value)); } else if (TTL.equals(name)) { node.setTtl(TimeUnit.SECONDS.toMillis(Long.parseLong(value))); } else if (TIMEOUT.equals(name)) { node.setTimeout(Integer.parseInt(value)); } else if (CONTEXT.equals(name)) { final String[] context = value.split(","); contexts = Arrays.asList(context); } else if (ALIAS.equals(name)) { final String[] alias = value.split(","); hosts = Arrays.asList(alias); } else if(WAITWORKER.equals(name)) { node.setWaitWorker(Integer.parseInt(value)); } else { processError(TYPESYNTAX, SBADFLD + name + SBADFLD1, exchange); return; } } final NodeConfig config; try { // Build the config config = node.build(); if (container.addNode(config, balancer, exchange.getIoThread(), exchange.getConnection().getByteBufferPool())) { // Apparently this is hard to do in the C part, so maybe we should just remove this if (contexts != null && hosts != null) { for (final String context : contexts) { container.enableContext(context, config.getJvmRoute(), hosts); } } processOK(exchange); } else { processError(MCMPErrorCode.NODE_STILL_EXISTS, exchange); } } catch (Exception e) { processError(MCMPErrorCode.CANT_UPDATE_NODE, exchange); } } /** * Process a mod_cluster mgmt command. * * @param exchange the http server exchange * @param requestData the request data * @param action the mgmt action * @throws IOException */ void processCommand(final HttpServerExchange exchange, final RequestData requestData, final MCMPAction action) throws IOException { if (exchange.getRequestPath().equals("*") || exchange.getRequestPath().endsWith("/*")) { processNodeCommand(exchange, requestData, action); } else { processAppCommand(exchange, requestData, action); } } /** * Process a mgmt command targeting a node. * * @param exchange the http server exchange * @param requestData the request data * @param action the mgmt action * @throws IOException */ void processNodeCommand(final HttpServerExchange exchange, final RequestData requestData, final MCMPAction action) throws IOException { final String jvmRoute = requestData.getFirst(JVMROUTE); if (jvmRoute == null) { processError(TYPESYNTAX, SMISFLD, exchange); return; } if (processNodeCommand(jvmRoute, action)) { processOK(exchange); } else { processError(MCMPErrorCode.CANT_UPDATE_NODE, exchange); } } boolean processNodeCommand(final String jvmRoute, final MCMPAction action) throws IOException { switch (action) { case ENABLE: return container.enableNode(jvmRoute); case DISABLE: return container.disableNode(jvmRoute); case STOP: return container.stopNode(jvmRoute); case REMOVE: return container.removeNode(jvmRoute) != null; } return false; } /** * Process a command targeting an application. * * @param exchange the http server exchange * @param requestData the request data * @param action the mgmt action * @return * @throws IOException */ void processAppCommand(final HttpServerExchange exchange, final RequestData requestData, final MCMPAction action) throws IOException { final String contextPath = requestData.getFirst(CONTEXT); final String jvmRoute = requestData.getFirst(JVMROUTE); final String aliases = requestData.getFirst(ALIAS); if (contextPath == null || jvmRoute == null || aliases == null) { processError(TYPESYNTAX, SMISFLD, exchange); return; } final List virtualHosts = Arrays.asList(aliases.split(",")); if (virtualHosts == null || virtualHosts.isEmpty()) { processError(TYPESYNTAX, SCONBAD, exchange); return; } String response = null; switch (action) { case ENABLE: if (!container.enableContext(contextPath, jvmRoute, virtualHosts)) { processError(MCMPErrorCode.CANT_UPDATE_CONTEXT, exchange); return; } break; case DISABLE: if (!container.disableContext(contextPath, jvmRoute, virtualHosts)) { processError(MCMPErrorCode.CANT_UPDATE_CONTEXT, exchange); return; } break; case STOP: int i = container.stopContext(contextPath, jvmRoute, virtualHosts); final StringBuilder builder = new StringBuilder(); builder.append("Type=STOP-APP-RSP,JvmRoute=").append(jvmRoute); builder.append("Alias=").append(aliases); builder.append("Context=").append(contextPath); builder.append("Requests=").append(i); response = builder.toString(); break; case REMOVE: if (!container.removeContext(contextPath, jvmRoute, virtualHosts)) { processError(MCMPErrorCode.CANT_UPDATE_CONTEXT, exchange); return; } break; default: { processError(TYPESYNTAX, SMISFLD, exchange); return; } } if (response != null) { sendResponse(exchange, response); } else { processOK(exchange); } } /** * Process the status request. * * @param exchange the http server exchange * @param requestData the request data * @throws IOException */ void processStatus(final HttpServerExchange exchange, final RequestData requestData) throws IOException { final String jvmRoute = requestData.getFirst(JVMROUTE); final String loadValue = requestData.getFirst(LOAD); if (loadValue == null || jvmRoute == null) { processError(TYPESYNTAX, SMISFLD, exchange); return; } UndertowLogger.ROOT_LOGGER.receivedNodeLoad(jvmRoute, loadValue); final int load = Integer.parseInt(loadValue); if (load > 0 || load == -2) { final Node node = container.getNode(jvmRoute); if (node == null) { processError(MCMPErrorCode.CANT_READ_NODE, exchange); return; } final NodePingUtil.PingCallback callback = new NodePingUtil.PingCallback() { @Override public void completed() { final String response = "Type=STATUS-RSP&State=OK&JVMRoute=" + jvmRoute + "&id=" + creationTime; try { if (load > 0) { node.updateLoad(load); } sendResponse(exchange, response); } catch (Exception e) { UndertowLogger.ROOT_LOGGER.failedToSendPingResponse(e); } } @Override public void failed() { final String response = "Type=STATUS-RSP&State=NOTOK&JVMRoute=" + jvmRoute + "&id=" + creationTime; try { node.markInError(); sendResponse(exchange, response); } catch (Exception e) { UndertowLogger.ROOT_LOGGER.failedToSendPingResponseDBG(e, node.getJvmRoute(), jvmRoute); } } }; // Ping the node node.ping(exchange, callback); } else if (load == 0) { final Node node = container.getNode(jvmRoute); if (node != null) { node.hotStandby(); sendResponse(exchange, "Type=STATUS-RSP&State=OK&JVMRoute=" + jvmRoute + "&id=" + creationTime); } else { processError(MCMPErrorCode.CANT_READ_NODE, exchange); } } else if (load == -1) { // Error, disable node final Node node = container.getNode(jvmRoute); if (node != null) { node.markInError(); sendResponse(exchange, "Type=STATUS-RSP&State=NOTOK&JVMRoute=" + jvmRoute + "&id=" + creationTime); } else { processError(MCMPErrorCode.CANT_READ_NODE, exchange); } } else { processError(TYPESYNTAX, SMISFLD, exchange); } } /** * Process the ping request. * * @param exchange the http server exchange * @param requestData the request data * @throws IOException */ void processPing(final HttpServerExchange exchange, final RequestData requestData) throws IOException { final String jvmRoute = requestData.getFirst(JVMROUTE); final String scheme = requestData.getFirst(SCHEME); final String host = requestData.getFirst(HOST); final String port = requestData.getFirst(PORT); final String OK = "Type=PING-RSP&State=OK&id=" + creationTime; final String NOTOK = "Type=PING-RSP&State=NOTOK&id=" + creationTime; if (jvmRoute != null) { // ping the corresponding node. final Node nodeConfig = container.getNode(jvmRoute); if (nodeConfig == null) { sendResponse(exchange, NOTOK); return; } final NodePingUtil.PingCallback callback = new NodePingUtil.PingCallback() { @Override public void completed() { try { sendResponse(exchange, OK); } catch (Exception e) { e.printStackTrace(); } } @Override public void failed() { try { nodeConfig.markInError(); sendResponse(exchange, NOTOK); } catch (Exception e) { e.printStackTrace(); } } }; nodeConfig.ping(exchange, callback); } else { if (scheme == null && host == null && port == null) { sendResponse(exchange, OK); return; } else { if (host == null || port == null) { processError(TYPESYNTAX, SMISFLD, exchange); return; } // Check whether we can reach the host checkHostUp(scheme, host, Integer.parseInt(port), exchange, new NodePingUtil.PingCallback() { @Override public void completed() { sendResponse(exchange, OK); } @Override public void failed() { sendResponse(exchange, NOTOK); } }); return; } } } /* * Something like: * * Node: [1],Name: 368e2e5c-d3f7-3812-9fc6-f96d124dcf79,Balancer: * cluster-prod-01,LBGroup: ,Host: 127.0.0.1,Port: 8443,Type: * https,Flushpackets: Off,Flushwait: 10,Ping: 10,Smax: 21,Ttl: 60,Elected: * 0,Read: 0,Transfered: 0,Connected: 0,Load: 1 Vhost: [1:1:1], Alias: * default-host Vhost: [1:1:2], Alias: localhost Vhost: [1:1:3], Alias: * example.com Context: [1:1:1], Context: /myapp, Status: ENABLED */ /** * Process INFO request * * @throws IOException */ protected void processInfo(HttpServerExchange exchange) throws IOException { final String data = processInfoString(); exchange.getResponseHeaders().add(Headers.SERVER, MOD_CLUSTER_EXPOSED_VERSION); sendResponse(exchange, data); } protected String processInfoString() { final StringBuilder builder = new StringBuilder(); final List vHosts = new ArrayList<>(); final List contexts = new ArrayList<>(); final Collection nodes = container.getNodes(); for (final Node node : nodes) { MCMPInfoUtil.printInfo(node, builder); vHosts.addAll(node.getVHosts()); contexts.addAll(node.getContexts()); } for (final Node.VHostMapping vHost : vHosts) { MCMPInfoUtil.printInfo(vHost, builder); } for (final Context context : contexts) { MCMPInfoUtil.printInfo(context, builder); } return builder.toString(); } /* * something like: * * balancer: [1] Name: cluster-prod-01 Sticky: 1 [JSESSIONID]/[jsessionid] * remove: 0 force: 0 Timeout: 0 maxAttempts: 1 node: [1:1],Balancer: * cluster-prod-01,JVMRoute: 368e2e5c-d3f7-3812-9fc6-f96d124dcf79,LBGroup: * [],Host: 127.0.0.1,Port: 8443,Type: https,flushpackets: 0,flushwait: * 10,ping: 10,smax: 21,ttl: 60,timeout: 0 host: 1 [default-host] vhost: 1 * node: 1 host: 2 [localhost] vhost: 1 node: 1 host: 3 [example.com] vhost: * 1 node: 1 context: 1 [/myapp] vhost: 1 node: 1 status: 1 */ /** * Process DUMP request * * @param exchange * @throws IOException */ protected void processDump(HttpServerExchange exchange) throws IOException { final String data = processDumpString(); exchange.getResponseHeaders().add(Headers.SERVER, MOD_CLUSTER_EXPOSED_VERSION); sendResponse(exchange, data); } protected String processDumpString() { final StringBuilder builder = new StringBuilder(); final Collection balancers = container.getBalancers(); for (final Balancer balancer : balancers) { MCMPInfoUtil.printDump(balancer, builder); } final List vHosts = new ArrayList<>(); final List contexts = new ArrayList<>(); final Collection nodes = container.getNodes(); for (final Node node : nodes) { MCMPInfoUtil.printDump(node, builder); vHosts.addAll(node.getVHosts()); contexts.addAll(node.getContexts()); } for (final Node.VHostMapping vHost : vHosts) { MCMPInfoUtil.printDump(vHost, builder); } for (final Context context : contexts) { MCMPInfoUtil.printDump(context, builder); } return builder.toString(); } /** * Check whether a host is up. * * @param scheme the scheme * @param host the host * @param port the port * @param exchange the http server exchange * @param callback the ping callback */ protected void checkHostUp(final String scheme, final String host, final int port, final HttpServerExchange exchange, final NodePingUtil.PingCallback callback) { final XnioSsl xnioSsl = null; // TODO final OptionMap options = OptionMap.builder() .set(Options.TCP_NODELAY, true) .getMap(); try { // http, ajp and maybe more in future if ("ajp".equalsIgnoreCase(scheme) || "http".equalsIgnoreCase(scheme)) { final URI uri = new URI(scheme, null, host, port, "/", null, null); NodePingUtil.pingHttpClient(uri, callback, exchange, container.getClient(), xnioSsl, options); } else { final InetSocketAddress address = new InetSocketAddress(host, port); NodePingUtil.pingHost(address, exchange, callback, options); } } catch (URISyntaxException e) { callback.failed(); } } /** * Send a simple response string. * * @param exchange the http server exchange * @param response the response string */ static void sendResponse(final HttpServerExchange exchange, final String response) { exchange.setStatusCode(StatusCodes.OK); exchange.getResponseHeaders().add(Headers.CONTENT_TYPE, CONTENT_TYPE); final Sender sender = exchange.getResponseSender(); UndertowLogger.ROOT_LOGGER.mcmpSendingResponse(exchange.getSourceAddress(), exchange.getStatusCode(), exchange.getResponseHeaders(), response); sender.send(response); } /** * If the process is OK, then add 200 HTTP status and its "OK" phrase * * @throws IOException */ static void processOK(HttpServerExchange exchange) throws IOException { exchange.setStatusCode(StatusCodes.OK); exchange.getResponseHeaders().add(Headers.CONTENT_TYPE, CONTENT_TYPE); exchange.endExchange(); } static void processError(MCMPErrorCode errorCode, HttpServerExchange exchange) { processError(errorCode.getType(), errorCode.getMessage(), exchange); } /** * Send an error message. * * @param type the error type * @param errString the error string * @param exchange the http server exchange */ static void processError(String type, String errString, HttpServerExchange exchange) { exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); exchange.getResponseHeaders().add(Headers.CONTENT_TYPE, CONTENT_TYPE); exchange.getResponseHeaders().add(new HttpString("Version"), VERSION_PROTOCOL); exchange.getResponseHeaders().add(new HttpString("Type"), type); exchange.getResponseHeaders().add(new HttpString("Mess"), errString); exchange.endExchange(); UndertowLogger.ROOT_LOGGER.mcmpProcessingError(type, errString); } /** * Transform the form data into an intermediate request data which can me used * by the web manager * * @param exchange the http server exchange * @return * @throws IOException */ RequestData parseFormData(final HttpServerExchange exchange) throws IOException { // Read post parameters final FormDataParser parser = parserFactory.createParser(exchange); final FormData formData = parser.parseBlocking(); final RequestData data = new RequestData(); for (String name : formData) { final HttpString key = new HttpString(name); data.add(key, formData.get(name)); } return data; } private static void checkStringForSuspiciousCharacters(String data) { for(int i = 0; i < data.length(); ++i) { char c = data.charAt(i); if(c == '>' || c == '<' || c == '\\' || c == '\"' || c == '\n' || c == '\r') { throw UndertowMessages.MESSAGES.mcmpMessageRejectedDueToSuspiciousCharacters(data); } } } static class RequestData { private final Map> values = new LinkedHashMap<>(); Iterator iterator() { return values.keySet().iterator(); } void add(final HttpString name, Deque values) { checkStringForSuspiciousCharacters(name.toString()); for (final FormData.FormValue value : values) { add(name, value); } } void addValues(final HttpString name, Deque value) { Deque values = this.values.get(name); for(String i : value) { checkStringForSuspiciousCharacters(i); } if (values == null) { this.values.put(name, value); } else { values.addAll(value); } } void add(final HttpString name, final FormData.FormValue value) { Deque values = this.values.get(name); if (values == null) { this.values.put(name, values = new ArrayDeque<>(1)); } String stringVal = value.getValue(); checkStringForSuspiciousCharacters(stringVal); values.add(stringVal); } String getFirst(HttpString name) { final Deque deque = values.get(name); return deque == null ? null : deque.peekFirst(); } } static boolean checkString(final String value) { return value != null && value.length() > 0; } } MCMPInfoUtil.java000066400000000000000000000160451420065311100341430ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import java.util.concurrent.TimeUnit; /** * @author Emanuel Muckenhuber */ class MCMPInfoUtil { private static final String NEWLINE = "\n"; static void printDump(final Balancer balancer, final StringBuilder builder) { builder.append("balancer: [").append(balancer.getId()).append("]") .append(" Name: ").append(balancer.getName()) .append(" Sticky: ").append(toStringOneZero(balancer.isStickySession())) .append(" [").append(balancer.getStickySessionCookie()).append("]/[").append(balancer.getStickySessionPath()).append("]") .append(" remove: ").append(toStringOneZero(balancer.isStickySessionRemove())) .append(" force: ").append(toStringOneZero(balancer.isStickySessionForce())) .append(" Timeout: ").append(balancer.getWaitWorker()) .append(" maxAttempts: ").append(balancer.getMaxRetries()) .append(NEWLINE); } static void printInfo(final Node.VHostMapping host, final StringBuilder builder) { // node id is not unique? for (final String alias : host.getAliases()) { builder.append("Vhost: [") .append(host.getNode().getId()).append(":") .append(host.getId()).append(":") .append(-1) // id[i] id in the table!? does not exist .append("], Alias: ").append(alias) .append(NEWLINE); } } static void printDump(final Node.VHostMapping host, final StringBuilder builder) { // node id is not unique? for (final String alias : host.getAliases()) { builder.append("host: ").append(host.getId()) .append(" [").append(alias).append("]") .append(" vhost: ").append(host.getId()) .append(" node: ").append(host.getNode().getId()) .append(NEWLINE); } } static void printInfo(final Context context, final StringBuilder builder) { builder.append("Context: [") .append(context.getNode().getId()).append(":") .append(context.getVhost().getId()).append(":") .append(context.getId()) .append("]") .append(", Context: ").append(context.getPath()) .append(", Status: ").append(context.getStatus()) .append(NEWLINE); } static void printDump(final Context context, final StringBuilder builder) { builder.append("context: ").append(context.getId()) .append(" [").append(context.getPath()).append("]") .append(" vhost: ").append(context.getVhost().getId()) .append(" node: ").append(context.getNode().getId()) .append(" status: ").append(formatStatus(context.getStatus())) .append(NEWLINE); } static void printInfo(final Node node, final StringBuilder builder) { builder.append("Node: ") .append("[").append(node.getId()).append("]") .append(",Name: ").append(node.getJvmRoute()) .append(",Balancer: ").append(node.getNodeConfig().getBalancer()) .append(",LBGroup: ").append(formatString(node.getNodeConfig().getDomain())) .append(",Host: ").append(node.getNodeConfig().getConnectionURI().getHost()) .append(",Port: ").append(node.getNodeConfig().getConnectionURI().getPort()) .append(",Type: ").append(node.getNodeConfig().getConnectionURI().getScheme()) .append(",Flushpackets: ").append(toStringOnOff(node.getNodeConfig().isFlushPackets())) .append(",Flushwait: ").append(node.getNodeConfig().getFlushwait()) .append(",Ping: ").append(node.getNodeConfig().getPing()) .append(",Smax: ").append(node.getNodeConfig().getSmax()) .append(",Ttl: ").append(TimeUnit.MILLISECONDS.toSeconds(node.getNodeConfig().getTtl())) .append(",Elected: ").append(node.getElected()) .append(",Read: ").append(node.getConnectionPool().getClientStatistics().getRead()) .append(",Transfered: ").append(node.getConnectionPool().getClientStatistics().getWritten()) .append(",Connected: ").append(node.getConnectionPool().getOpenConnections()) .append(",Load: ").append(node.getLoad()) .append(NEWLINE); } static void printDump(final Node node, final StringBuilder builder) { builder.append("node: [") .append(node.getBalancer().getId()).append(":") .append(node.getId()).append("]") .append(",Balancer: ").append(node.getNodeConfig().getBalancer()) .append(",JVMRoute: ").append(node.getJvmRoute()) .append(",LBGroup: [").append(formatString(node.getNodeConfig().getDomain())).append("]") .append(",Host: ").append(node.getNodeConfig().getConnectionURI().getHost()) .append(",Port: ").append(node.getNodeConfig().getConnectionURI().getPort()) .append(",Type: ").append(node.getNodeConfig().getConnectionURI().getScheme()) .append(",flushpackets: ").append(toStringOneZero(node.getNodeConfig().isFlushPackets())) .append(",flushwait: ").append(node.getNodeConfig().getFlushwait()) .append(",ping: ").append(node.getNodeConfig().getPing()) .append(",smax: ").append(node.getNodeConfig().getSmax()) .append(",ttl: ").append(TimeUnit.MILLISECONDS.toSeconds(node.getNodeConfig().getTtl())) .append(",timeout: ").append(node.getNodeConfig().getTimeout()) .append(NEWLINE); } static String toStringOneZero(boolean bool) { return bool ? "1" : "0"; } static String toStringOnOff(boolean bool) { return bool ? "On" : "Off"; } static String formatString(String str) { return str == null ? "" : str; } /* matches constants defined in mod_cluster-1.3.7.Final/native/include/context.h */ static int formatStatus(Context.Status status) { return status == Context.Status.ENABLED ? 1 : status == Context.Status.DISABLED ? 2 : status == Context.Status.STOPPED ? 3 : -1; } } MCMPWebManager.java000066400000000000000000000373701420065311100344260ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import io.undertow.io.Sender; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.Methods; import io.undertow.util.StatusCodes; import java.io.IOException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Deque; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.TimeUnit; /** * The mod cluster manager web frontend. * * @author Emanuel Muckenhuber */ class MCMPWebManager extends MCMPHandler { private final boolean checkNonce; private final boolean reduceDisplay; private final boolean allowCmd; private final Random r = new SecureRandom(); private String nonce = null; MCMPWebManager(MCMPConfig.MCMPWebManagerConfig config, ModCluster modCluster, HttpHandler next) { super(config, modCluster, next); this.checkNonce = config.isCheckNonce(); this.reduceDisplay = config.isReduceDisplay(); this.allowCmd = config.isAllowCmd(); } String getNonce() { return "nonce=" + getRawNonce(); } synchronized String getRawNonce() { if (this.nonce == null) { byte[] nonce = new byte[16]; r.nextBytes(nonce); this.nonce = ""; for (int i = 0; i < 16; i = i + 2) { this.nonce = this.nonce.concat(Integer.toHexString(0xFF & nonce[i] * 16 + 0xFF & nonce[i + 1])); } } return nonce; } @Override protected void handleRequest(HttpString method, HttpServerExchange exchange) throws Exception { if (!Methods.GET.equals(method)) { super.handleRequest(method, exchange); return; } // Process the request processRequest(exchange); } protected boolean handlesMethod(HttpString method) { if(Methods.GET.equals(method)) { return true; } return super.handlesMethod(method); } private void processRequest(HttpServerExchange exchange) throws IOException { Map> params = exchange.getQueryParameters(); boolean hasNonce = params.containsKey("nonce"); int refreshTime = 0; if (checkNonce) { /* Check the nonce */ if (hasNonce) { String receivedNonce = params.get("nonce").getFirst(); if (receivedNonce.equals(getRawNonce())) { boolean refresh = params.containsKey("refresh"); if (refresh) { String sval = params.get("refresh").getFirst(); refreshTime = Integer.parseInt(sval); if (refreshTime < 10) refreshTime = 10; exchange.getResponseHeaders().add(new HttpString("Refresh"), Integer.toString(refreshTime)); } boolean cmd = params.containsKey("Cmd"); boolean range = params.containsKey("Range"); if (cmd) { String scmd = params.get("Cmd").getFirst(); if (scmd.equals("INFO")) { processInfo(exchange); return; } else if (scmd.equals("DUMP")) { processDump(exchange); return; } else if (scmd.equals("ENABLE-APP") && range) { String srange = params.get("Range").getFirst(); final RequestData data = buildRequestData(exchange, params); if (srange.equals("NODE")) { processNodeCommand(exchange, data, MCMPAction.ENABLE); } if (srange.equals("DOMAIN")) { boolean domain = params.containsKey("Domain"); if (domain) { String sdomain = params.get("Domain").getFirst(); processDomainCmd(exchange, sdomain, MCMPAction.ENABLE); } } if (srange.equals("CONTEXT")) { processAppCommand(exchange, data, MCMPAction.ENABLE); } } else if (scmd.equals("DISABLE-APP") && range) { final String srange = params.get("Range").getFirst(); final RequestData data = buildRequestData(exchange, params); if (srange.equals("NODE")) { processNodeCommand(exchange, data, MCMPAction.DISABLE); } if (srange.equals("DOMAIN")) { boolean domain = params.containsKey("Domain"); if (domain) { String sdomain = params.get("Domain").getFirst(); processDomainCmd(exchange, sdomain, MCMPAction.DISABLE); } } if (srange.equals("CONTEXT")) { processAppCommand(exchange, data, MCMPAction.DISABLE); } } return; } } } } exchange.setStatusCode(StatusCodes.OK); exchange.getResponseHeaders().add(Headers.CONTENT_TYPE, "text/html; charset=ISO-8859-1"); final Sender resp = exchange.getResponseSender(); final StringBuilder buf = new StringBuilder(); buf.append("\nMod_cluster Status\n\n"); buf.append("

" + MOD_CLUSTER_EXPOSED_VERSION + "

"); final String uri = exchange.getRequestPath(); final String nonce = getNonce(); if (refreshTime <= 0) { buf.append("Auto Refresh"); } buf.append(" show DUMP output"); buf.append(" show INFO output"); buf.append("\n"); // Show load balancing groups final Map> nodes = new LinkedHashMap<>(); for (final Node node : container.getNodes()) { final String domain = node.getNodeConfig().getDomain() != null ? node.getNodeConfig().getDomain() : ""; List list = nodes.get(domain); if (list == null) { list = new ArrayList<>(); nodes.put(domain, list); } list.add(node); } for (Map.Entry> entry : nodes.entrySet()) { final String groupName = entry.getKey(); if (reduceDisplay) { buf.append("

LBGroup " + groupName + ": "); } else { buf.append("

LBGroup " + groupName + ": "); } if (allowCmd) { domainCommandString(buf, uri, MCMPAction.ENABLE, groupName); domainCommandString(buf, uri, MCMPAction.DISABLE, groupName); } for (final Node node : entry.getValue()) { final NodeConfig nodeConfig = node.getNodeConfig(); if (reduceDisplay) { buf.append("

Node " + nodeConfig.getJvmRoute()); printProxyStat(buf, node, reduceDisplay); } else { buf.append("

Node " + nodeConfig.getJvmRoute() + " (" + nodeConfig.getConnectionURI() + "):

\n"); } if (allowCmd) { nodeCommandString(buf, uri, MCMPAction.ENABLE, nodeConfig.getJvmRoute()); nodeCommandString(buf, uri, MCMPAction.DISABLE, nodeConfig.getJvmRoute()); } if (!reduceDisplay) { buf.append("
\n"); buf.append("Balancer: " + nodeConfig.getBalancer() + ",LBGroup: " + nodeConfig.getDomain()); String flushpackets = "off"; if (nodeConfig.isFlushPackets()) { flushpackets = "Auto"; } buf.append(",Flushpackets: " + flushpackets + ",Flushwait: " + nodeConfig.getFlushwait() + ",Ping: " + nodeConfig.getPing() + " ,Smax: " + nodeConfig.getPing() + ",Ttl: " + TimeUnit.MILLISECONDS.toSeconds(nodeConfig.getTtl())); printProxyStat(buf, node, reduceDisplay); } else { buf.append("
\n"); } buf.append("\n"); // Process the virtual-host of the node printInfoHost(buf, uri, reduceDisplay, allowCmd, node); } } buf.append("\n"); resp.send(buf.toString()); } void nodeCommandString(StringBuilder buf, String uri, MCMPAction status, String jvmRoute) { switch (status) { case ENABLE: buf.append("Enable Contexts "); break; case DISABLE: buf.append("Disable Contexts "); break; } } static void printProxyStat(StringBuilder buf, Node node, boolean reduceDisplay) { String status = "NOTOK"; if (node.getStatus() == NodeStatus.NODE_UP) status = "OK"; if (reduceDisplay) { buf.append(" " + status + " "); } else { buf.append(",Status: " + status + ",Elected: " + node.getElected() + ",Read: " + node.getConnectionPool().getClientStatistics().getRead() + ",Transferred: " + node.getConnectionPool().getClientStatistics().getWritten() + ",Connected: " + node.getConnectionPool().getOpenConnections() + ",Load: " + node.getLoad()); } } /* based on domain_command_string */ void domainCommandString(StringBuilder buf, String uri, MCMPAction status, String lbgroup) { switch (status) { case ENABLE: buf.append("Enable Nodes "); break; case DISABLE: buf.append("Disable Nodes"); break; } } void processDomainCmd(HttpServerExchange exchange, String domain, MCMPAction action) throws IOException { if (domain != null) { for (final Node node : container.getNodes()) { if (domain.equals(node.getNodeConfig().getDomain())) { processNodeCommand(node.getJvmRoute(), action); } } } processOK(exchange); } /* based on manager_info_hosts */ private void printInfoHost(StringBuilder buf, String uri, boolean reduceDisplay, boolean allowCmd, final Node node) { for (Node.VHostMapping host : node.getVHosts()) { if (!reduceDisplay) { buf.append("

Virtual Host " + host.getId() + ":

"); } printInfoContexts(buf, uri, reduceDisplay, allowCmd, host.getId(), host, node); if (reduceDisplay) { buf.append("Aliases: "); for (String alias : host.getAliases()) { buf.append(alias + " "); } } else { buf.append("

Aliases:

"); buf.append("
");
                for (String alias : host.getAliases()) {
                    buf.append(alias + "\n");
                }
                buf.append("
"); } } } /* based on manager_info_contexts */ private void printInfoContexts(StringBuilder buf, String uri, boolean reduceDisplay, boolean allowCmd, long host, Node.VHostMapping vhost, Node node) { if (!reduceDisplay) buf.append("

Contexts:

"); buf.append("
");
        for (Context context : node.getContexts()) {
            if (context.getVhost() == vhost) {
                String status = "REMOVED";
                switch (context.getStatus()) {
                    case ENABLED:
                        status = "ENABLED";
                        break;
                    case DISABLED:
                        status = "DISABLED";
                        break;
                    case STOPPED:
                        status = "STOPPED";
                        break;
                }
                buf.append(context.getPath() + " , Status: " + status + " Request: " + context.getActiveRequests() + " ");
                if (allowCmd) {
                    contextCommandString(buf, uri, context.getStatus(), context.getPath(), vhost.getAliases(), node.getJvmRoute());
                }
                buf.append("\n");
            }
        }
        buf.append("
"); } /* generate a command URL for the context */ void contextCommandString(StringBuilder buf, String uri, Context.Status status, String path, List alias, String jvmRoute) { switch (status) { case DISABLED: buf.append("Enable "); break; case ENABLED: buf.append("Disable "); break; } } static void contextString(StringBuilder buf, String path, List alias, String jvmRoute) { buf.append("JVMRoute=" + jvmRoute + "&Alias="); boolean first = true; for (String a : alias) { if (first) { first = false; } else { buf.append(","); } buf.append(a); } buf.append("&Context=" + path); } static RequestData buildRequestData(final HttpServerExchange exchange, Map> params) { final RequestData data = new RequestData(); for (final Map.Entry> entry : params.entrySet()) { final HttpString name = new HttpString(entry.getKey()); data.addValues(name, entry.getValue()); } return data; } } ModCluster.java000066400000000000000000000255461420065311100340240ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import java.io.IOException; import java.util.UUID; import java.util.concurrent.TimeUnit; import io.undertow.client.UndertowClient; import io.undertow.server.HttpHandler; import io.undertow.server.handlers.proxy.ProxyHandler; import io.undertow.server.handlers.proxy.RouteParsingStrategy; import org.xnio.OptionMap; import org.xnio.XnioWorker; import org.xnio.ssl.XnioSsl; /** * @author Emanuel Muckenhuber */ public class ModCluster { // Health check intervals private final long healthCheckInterval; private final long removeBrokenNodes; private final NodeHealthChecker healthChecker; // Proxy connection pool defaults private final int maxConnections; private final int cacheConnections; private final int requestQueueSize; private final boolean queueNewRequests; private final int maxRequestTime; private final long ttl; private final boolean useAlias; private final XnioWorker xnioWorker; private final ModClusterContainer container; private final int maxRetries; private final boolean deterministicFailover; private final RouteParsingStrategy routeParsingStrategy; private final String rankedAffinityDelimiter; private final boolean reuseXForwarded; private final String serverID = UUID.randomUUID().toString(); // TODO ModCluster(Builder builder) { this.xnioWorker = builder.xnioWorker; this.maxConnections = builder.maxConnections; this.cacheConnections = builder.cacheConnections; this.requestQueueSize = builder.requestQueueSize; this.queueNewRequests = builder.queueNewRequests; this.healthCheckInterval = builder.healthCheckInterval; this.removeBrokenNodes = builder.removeBrokenNodes; this.deterministicFailover = builder.deterministicFailover; this.routeParsingStrategy = builder.routeParsingStrategy; this.rankedAffinityDelimiter = builder.rankedAffinityDelimiter; this.healthChecker = builder.healthChecker; this.maxRequestTime = builder.maxRequestTime; this.ttl = builder.ttl; this.useAlias = builder.useAlias; this.maxRetries = builder.maxRetries; this.reuseXForwarded = builder.reuseXForwarded; this.container = new ModClusterContainer(this, builder.xnioSsl, builder.client, builder.clientOptions); } protected String getServerID() { return serverID; } protected ModClusterContainer getContainer() { return container; } public ModClusterController getController() { return container; } public int getMaxConnections() { return maxConnections; } public int getCacheConnections() { return cacheConnections; } public int getRequestQueueSize() { return requestQueueSize; } public boolean isQueueNewRequests() { return queueNewRequests; } public long getHealthCheckInterval() { return healthCheckInterval; } public long getRemoveBrokenNodes() { return removeBrokenNodes; } public NodeHealthChecker getHealthChecker() { return healthChecker; } public long getTtl() { return ttl; } public boolean isUseAlias() { return useAlias; } public boolean isDeterministicFailover() { return deterministicFailover; } public RouteParsingStrategy routeParsingStrategy() { return this.routeParsingStrategy; } public String rankedAffinityDelimiter() { return this.rankedAffinityDelimiter; } /** * Get the handler proxying the requests. * * @return the proxy handler */ @Deprecated public HttpHandler getProxyHandler() { return createProxyHandler(); } /** * Get the handler proxying the requests. * * @return the proxy handler */ public HttpHandler createProxyHandler() { return ProxyHandler.builder() .setProxyClient(container.getProxyClient()) .setMaxRequestTime(maxRequestTime) .setMaxConnectionRetries(maxRetries) .setReuseXForwarded(reuseXForwarded) .build(); } /** * Get the handler proxying the requests. * * @return the proxy handler */ public HttpHandler createProxyHandler(HttpHandler next) { return ProxyHandler.builder() .setProxyClient(container.getProxyClient()) .setNext(next) .setMaxRequestTime(maxRequestTime) .setMaxConnectionRetries(maxRetries) .setReuseXForwarded(reuseXForwarded) .build(); } /** * Start */ public synchronized void start() { } /** * Start advertising a mcmp handler. * * @param config the mcmp handler config * @throws IOException */ public synchronized void advertise(MCMPConfig config) throws IOException { final MCMPConfig.AdvertiseConfig advertiseConfig = config.getAdvertiseConfig(); if (advertiseConfig == null) { throw new IllegalArgumentException("advertise not enabled"); } MCMPAdvertiseTask.advertise(container, advertiseConfig, xnioWorker); } /** * Stop */ public synchronized void stop() { } public static Builder builder(final XnioWorker worker) { return builder(worker, UndertowClient.getInstance(), null); } public static Builder builder(final XnioWorker worker, final UndertowClient client) { return builder(worker, client, null); } public static Builder builder(final XnioWorker worker, final UndertowClient client, final XnioSsl xnioSsl) { return new Builder(worker, client, xnioSsl); } public static class Builder { private final XnioSsl xnioSsl; private final UndertowClient client; private final XnioWorker xnioWorker; // Fairly restrictive connection pool defaults private int maxConnections = 16; private int cacheConnections = 1; private int requestQueueSize = 0; private boolean queueNewRequests = false; private int maxRequestTime = -1; private long ttl = TimeUnit.SECONDS.toMillis(60); private boolean useAlias = false; private NodeHealthChecker healthChecker = NodeHealthChecker.NO_CHECK; private long healthCheckInterval = TimeUnit.SECONDS.toMillis(10); private long removeBrokenNodes = TimeUnit.MINUTES.toMillis(1); private OptionMap clientOptions = OptionMap.EMPTY; private int maxRetries; private boolean deterministicFailover = false; private RouteParsingStrategy routeParsingStrategy = RouteParsingStrategy.SINGLE; private String rankedAffinityDelimiter = "."; private boolean reuseXForwarded; private Builder(XnioWorker xnioWorker, UndertowClient client, XnioSsl xnioSsl) { this.xnioSsl = xnioSsl; this.client = client; this.xnioWorker = xnioWorker; } public ModCluster build() { return new ModCluster(this); } public Builder setMaxRequestTime(int maxRequestTime) { this.maxRequestTime = maxRequestTime; return this; } public Builder setHealthCheckInterval(long healthCheckInterval) { this.healthCheckInterval = healthCheckInterval; return this; } public Builder setRemoveBrokenNodes(long removeBrokenNodes) { this.removeBrokenNodes = removeBrokenNodes; return this; } public Builder setMaxConnections(int maxConnections) { this.maxConnections = maxConnections; return this; } public Builder setCacheConnections(int cacheConnections) { this.cacheConnections = cacheConnections; return this; } public Builder setRequestQueueSize(int requestQueueSize) { this.requestQueueSize = requestQueueSize; return this; } public Builder setQueueNewRequests(boolean queueNewRequests) { this.queueNewRequests = queueNewRequests; return this; } public Builder setHealthChecker(NodeHealthChecker healthChecker) { this.healthChecker = healthChecker; return this; } public Builder setUseAlias(boolean useAlias) { this.useAlias = useAlias; return this; } public Builder setMaxRetries(int maxRetries) { this.maxRetries = maxRetries; return this; } public Builder setDeterministicFailover(boolean deterministicFailover) { this.deterministicFailover = deterministicFailover; return this; } /** * Configures route parsing strategy to support none, single or ranked affinity. * * @param routeParsingStrategy strategy to use for parsing routes * @return this builder */ public Builder setRouteParsingStrategy(RouteParsingStrategy routeParsingStrategy) { this.routeParsingStrategy = routeParsingStrategy; return this; } /** * Configures ranked affinity delimiter used for splitting multiple encoded routes when * {@link RouteParsingStrategy#RANKED} is specified. Web requests will have an affinity for the first available node in * the list. * * @param rankedAffinityDelimiter delimiter splitting multiple routes; typically a "." * @return this builder */ public Builder setRankedAffinityDelimiter(String rankedAffinityDelimiter) { this.rankedAffinityDelimiter = rankedAffinityDelimiter; return this; } public Builder setTtl(long ttl) { this.ttl = ttl; return this; } public Builder setClientOptions(OptionMap clientOptions) { this.clientOptions = clientOptions; return this; } public Builder setReuseXForwarded(boolean reuseXForwarded) { this.reuseXForwarded = reuseXForwarded; return this; } } } ModClusterContainer.java000066400000000000000000000765541420065311100356740ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import io.undertow.UndertowLogger; import io.undertow.client.UndertowClient; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.Cookie; import io.undertow.server.handlers.cache.LRUCache; import io.undertow.server.handlers.proxy.RouteIteratorFactory; import io.undertow.server.handlers.proxy.ProxyClient; import io.undertow.util.CopyOnWriteMap; import io.undertow.util.Headers; import io.undertow.util.PathMatcher; import io.undertow.connector.ByteBufferPool; import org.xnio.OptionMap; import org.xnio.XnioExecutor; import org.xnio.XnioIoThread; import org.xnio.ssl.XnioSsl; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; /** * @author Stuart Douglas * @author Emanuel Muckenhuber * @author Radoslav Husar * @author Richard Opalka */ class ModClusterContainer implements ModClusterController { // The configured balancers private final ConcurrentMap balancers = new CopyOnWriteMap<>(); // The available nodes private final ConcurrentMap nodes = new CopyOnWriteMap<>(); // virtual-host > per context balancing table private final ConcurrentMap hosts = new CopyOnWriteMap<>(); // Map of removed jvmRoutes to failover domain private final LRUCache failoverDomains = new LRUCache<>(100, 5 * 60 * 1000); // The health check tasks private final ConcurrentMap healthChecks = new CopyOnWriteMap<>(); private final UpdateLoadTask updateLoadTask = new UpdateLoadTask(); private final XnioSsl xnioSsl; private final UndertowClient client; private final ProxyClient proxyClient; private final ModCluster modCluster; private final NodeHealthChecker healthChecker; private final long removeBrokenNodesThreshold; private RouteIteratorFactory routeIteratorFactory; private final OptionMap clientOptions; ModClusterContainer(final ModCluster modCluster, final XnioSsl xnioSsl, final UndertowClient client, OptionMap clientOptions) { this.xnioSsl = xnioSsl; this.client = client; this.modCluster = modCluster; this.clientOptions = clientOptions; this.healthChecker = modCluster.getHealthChecker(); this.proxyClient = new ModClusterProxyClient(null, this); this.removeBrokenNodesThreshold = removeThreshold(modCluster.getHealthCheckInterval(), modCluster.getRemoveBrokenNodes()); this.routeIteratorFactory = new RouteIteratorFactory(modCluster.routeParsingStrategy(), RouteIteratorFactory.ParsingCompatibility.MOD_CLUSTER, modCluster.rankedAffinityDelimiter()); } String getServerID() { return modCluster.getServerID(); } UndertowClient getClient() { return client; } XnioSsl getXnioSsl() { return xnioSsl; } /** * Get the proxy client. * * @return the proxy client */ public ProxyClient getProxyClient() { return proxyClient; } Collection getBalancers() { return Collections.unmodifiableCollection(balancers.values()); } Collection getNodes() { return Collections.unmodifiableCollection(nodes.values()); } Node getNode(final String jvmRoute) { return nodes.get(jvmRoute); } /** * Get the mod_cluster proxy target. * * @param exchange the http exchange * @return proxy target */ public ModClusterProxyTarget findTarget(final HttpServerExchange exchange) { // There is an option to disable the virtual host check, probably a default virtual host final PathMatcher.PathMatch entry = mapVirtualHost(exchange); if (entry == null) { return null; } for (final Balancer balancer : balancers.values()) { if (balancer.isStickySession()) { for (Cookie cookie : exchange.requestCookies()) { if (balancer.getStickySessionCookie().equals(cookie.getName())) { String sessionId = cookie.getValue(); Iterator routes = parseRoutes(sessionId); if (routes.hasNext()) { return new ModClusterProxyTarget.ExistingSessionTarget(sessionId, routes, entry.getValue(), this, balancer.isStickySessionForce()); } } } if (exchange.getPathParameters().containsKey(balancer.getStickySessionPath())) { String sessionId = exchange.getPathParameters().get(balancer.getStickySessionPath()).getFirst(); Iterator jvmRoute = parseRoutes(sessionId); if (jvmRoute.hasNext()) { return new ModClusterProxyTarget.ExistingSessionTarget(sessionId, jvmRoute, entry.getValue(), this, balancer.isStickySessionForce()); } } } } return new ModClusterProxyTarget.BasicTarget(entry.getValue(), this); } /** * Register a new node. * * @param config the node configuration * @param balancerConfig the balancer configuration * @param ioThread the associated I/O thread * @param bufferPool the buffer pool * @return whether the node could be created or not */ public synchronized boolean addNode(final NodeConfig config, final Balancer.BalancerBuilder balancerConfig, final XnioIoThread ioThread, final ByteBufferPool bufferPool) { final String jvmRoute = config.getJvmRoute(); final Node existing = nodes.get(jvmRoute); if (existing != null) { if (config.getConnectionURI().equals(existing.getNodeConfig().getConnectionURI())) { // TODO better check if they are the same existing.resetState(); return true; } else { existing.markRemoved(); removeNode(existing); if (!existing.isInErrorState()) { return false; // replies with MNODERM error } } } final String balancerRef = config.getBalancer(); Balancer balancer = balancers.get(balancerRef); if (balancer != null) { UndertowLogger.ROOT_LOGGER.debugf("Balancer %s already exists, replacing", balancerRef); } balancer = balancerConfig.build(); balancers.put(balancerRef, balancer); final Node node = new Node(config, balancer, ioThread, bufferPool, this); nodes.put(jvmRoute, node); // Schedule the health check scheduleHealthCheck(node, ioThread); // Reset the load factor periodically if (updateLoadTask.cancelKey == null) { updateLoadTask.cancelKey = ioThread.executeAtInterval(updateLoadTask, modCluster.getHealthCheckInterval(), TimeUnit.MILLISECONDS); } // Remove from the failover groups failoverDomains.remove(node.getJvmRoute()); UndertowLogger.ROOT_LOGGER.registeringNode(jvmRoute, config.getConnectionURI()); return true; } /** * Management command enabling all contexts on the given node. * * @param jvmRoute the jvmRoute * @return whether the given node was enabled */ public synchronized boolean enableNode(final String jvmRoute) { final Node node = nodes.get(jvmRoute); if (node != null) { for (final Context context : node.getContexts()) { context.enable(); } return true; } return false; } /** * Management command disabling all contexts on the given node. * * @param jvmRoute the jvmRoute * @return whether the given node was disabled */ public synchronized boolean disableNode(final String jvmRoute) { final Node node = nodes.get(jvmRoute); if (node != null) { for (final Context context : node.getContexts()) { context.disable(); } return true; } return false; } /** * Management command stopping all contexts on the given node. * * @param jvmRoute the jvmRoute * @return whether the given node was stopped */ public synchronized boolean stopNode(final String jvmRoute) { final Node node = nodes.get(jvmRoute); if (node != null) { for (final Context context : node.getContexts()) { context.stop(); } return true; } return false; } /** * Remove a node. * * @param jvmRoute the jvmRoute * @return the removed node */ public synchronized Node removeNode(final String jvmRoute) { final Node node = nodes.get(jvmRoute); if (node != null) { removeNode(node); } return node; } protected void removeNode(final Node node) { removeNode(node, false); } protected synchronized void removeNode(final Node node, boolean onlyInError) { if (onlyInError && !node.isInErrorState()) { return; } final String jvmRoute = node.getJvmRoute(); node.markRemoved(); if (nodes.remove(jvmRoute, node)) { UndertowLogger.ROOT_LOGGER.removingNode(jvmRoute); node.markRemoved(); // Remove the health check removeHealthCheck(node, node.getIoThread()); // Remove the contexts, if any for (final Context context : node.getContexts()) { removeContext(context.getPath(), node, context.getVirtualHosts()); } final String domain = node.getNodeConfig().getDomain(); if (domain != null) { failoverDomains.add(node.getJvmRoute(), domain); } final String balancerName = node.getBalancer().getName(); for (final Node other : nodes.values()) { if (other.getBalancer().getName().equals(balancerName)) { return; } } balancers.remove(balancerName); } if (nodes.size() == 0) { // In case there are no nodes registered unschedule the task updateLoadTask.cancelKey.remove(); updateLoadTask.cancelKey = null; } } /** * Register a web context. If the web context already exists, just enable it. * * @param contextPath the context path * @param jvmRoute the jvmRoute * @param aliases the virtual host aliases */ public synchronized boolean enableContext(final String contextPath, final String jvmRoute, final List aliases) { final Node node = nodes.get(jvmRoute); if (node != null) { Context context = getOrRegisterContext(contextPath, jvmRoute, aliases, node); context.enable(); return true; } return false; } public synchronized boolean disableContext(final String contextPath, final String jvmRoute, List aliases) { final Node node = nodes.get(jvmRoute); if (node != null) { Context context = getOrRegisterContext(contextPath, jvmRoute, aliases, node); context.disable(); return true; } return false; } synchronized int stopContext(final String contextPath, final String jvmRoute, List aliases) { final Node node = nodes.get(jvmRoute); if (node != null) { Context context = getOrRegisterContext(contextPath, jvmRoute, aliases, node); context.stop(); return context.getActiveRequests(); } return -1; } synchronized boolean removeContext(final String contextPath, final String jvmRoute, List aliases) { final Node node = nodes.get(jvmRoute); if (node != null) { return removeContext(contextPath, node, aliases); } return false; } public synchronized boolean removeContext(final String contextPath, final Node node, List aliases) { if (node == null) { return false; } final String jvmRoute = node.getJvmRoute(); UndertowLogger.ROOT_LOGGER.unregisteringContext(contextPath, jvmRoute); final Context context = node.removeContext(contextPath, aliases); if (context == null) { return false; } context.stop(); for (final String alias : context.getVirtualHosts()) { final VirtualHost virtualHost = hosts.get(alias); if (virtualHost != null) { virtualHost.removeContext(contextPath, jvmRoute, context); if (virtualHost.isEmpty()) { hosts.remove(alias); } } } return true; } private Context getOrRegisterContext(String contextPath, String jvmRoute, List aliases, Node node) { Context context = node.getContext(contextPath, aliases); if (context == null) { context = node.registerContext(contextPath, aliases); UndertowLogger.ROOT_LOGGER.registeringContext(contextPath, jvmRoute); UndertowLogger.ROOT_LOGGER.registeringContext(contextPath, jvmRoute, aliases); for (final String alias : aliases) { VirtualHost virtualHost = hosts.get(alias); if (virtualHost == null) { virtualHost = new VirtualHost(); hosts.put(alias, virtualHost); } virtualHost.registerContext(contextPath, jvmRoute, context); } } return context; } /** * Find a new node handling this request. * * @param entry the resolved virtual host entry * @return the context, {@code null} if not found */ Context findNewNode(final VirtualHost.HostEntry entry) { return electNode(entry.getContexts(), false, null); } /** * Try to find a failover node within the same load balancing group. * * @param entry the resolved virtual host entry * @param domain the load balancing domain, if known * @param session the actual value of JSESSIONID/jsessionid cookie/parameter * @param jvmRoute the original jvmRoute; in case of multiple routes, the first one * @param forceStickySession whether sticky sessions are forced * @return the context, {@code null} if not found */ Context findFailoverNode(final VirtualHost.HostEntry entry, final String domain, final String session, final String jvmRoute, final boolean forceStickySession) { // If configured, deterministically choose the failover target by calculating hash of the session ID modulo number of electable nodes if (modCluster.isDeterministicFailover()) { List candidates = new ArrayList<>(entry.getNodes().size()); for (String route : entry.getNodes()) { Node node = nodes.get(route); if (node != null && !node.isInErrorState() && !node.isHotStandby()) { candidates.add(route); } } // If there are no available regular nodes, all hot standby nodes become candidates if (candidates.isEmpty()) { for (String route : entry.getNodes()) { Node node = nodes.get(route); if (node != null && !node.isInErrorState() && node.isHotStandby()) { candidates.add(route); } } } if (candidates.isEmpty()) { return null; } String sessionId = session.substring(0, session.indexOf('.')); int index = (int) (Math.abs((long) sessionId.hashCode()) % candidates.size()); Collections.sort(candidates); String electedRoute = candidates.get(index); UndertowLogger.ROOT_LOGGER.debugf("Using deterministic failover target: %s", electedRoute); return entry.getContextForNode(electedRoute); } String failOverDomain = null; if (domain == null) { final Node node = nodes.get(jvmRoute); if (node != null) { failOverDomain = node.getNodeConfig().getDomain(); } if (failOverDomain == null) { failOverDomain = failoverDomains.get(jvmRoute); } } else { failOverDomain = domain; } final Collection contexts = entry.getContexts(); if (failOverDomain != null) { final Context context = electNode(contexts, true, failOverDomain); if (context != null) { return context; } } if (forceStickySession) { return null; } else { return electNode(contexts, false, null); } } /** * Map a request to virtual host. * * @param exchange the http exchange */ private PathMatcher.PathMatch mapVirtualHost(final HttpServerExchange exchange) { final String context = exchange.getRelativePath(); if(modCluster.isUseAlias()) { final String hostName = exchange.getRequestHeaders().getFirst(Headers.HOST); if (hostName != null) { // Remove the port from the host int i = hostName.indexOf(":"); VirtualHost host; if (i > 0) { host = hosts.get(hostName.substring(0, i)); if (host == null) { host = hosts.get(hostName); } } else { host = hosts.get(hostName); } if (host == null) { return null; } PathMatcher.PathMatch result = host.match(context); if (result.getValue() == null) { return null; } return result; } } else { for(Map.Entry host : hosts.entrySet()) { PathMatcher.PathMatch result = host.getValue().match(context); if (result.getValue() != null) { return result; } } } return null; } OptionMap getClientOptions() { return clientOptions; } private Iterator parseRoutes(String sessionId) { return routeIteratorFactory.iterator(sessionId); } static Context electNode(final Iterable contexts, final boolean existingSession, final String domain) { Context elected = null; Node candidate = null; boolean candidateHotStandby = false; for (Context context : contexts) { // Skip disabled contexts if (context.checkAvailable(existingSession)) { final Node node = context.getNode(); final boolean hotStandby = node.isHotStandby(); // Check that we only failover in the domain if (domain != null && !domain.equals(node.getNodeConfig().getDomain())) { continue; } if (candidate != null) { // Check if the nodes are in hot-standby if (candidateHotStandby) { if (hotStandby) { if (candidate.getElectedDiff() > node.getElectedDiff()) { candidate = node; elected = context; } } else { candidate = node; elected = context; candidateHotStandby = hotStandby; } } else if (hotStandby) { continue; } else { // Normal election process final int lbStatus1 = candidate.getLoadStatus(); final int lbStatus2 = node.getLoadStatus(); if (lbStatus1 > lbStatus2) { candidate = node; elected = context; candidateHotStandby = false; } } } else { candidate = node; elected = context; candidateHotStandby = hotStandby; } } } if (candidate != null) { candidate.elected(); // We have a winner! } return elected; } void scheduleHealthCheck(final Node node, XnioIoThread ioThread) { assert Thread.holdsLock(this); HealthCheckTask task = healthChecks.get(ioThread); if (task == null) { task = new HealthCheckTask(removeBrokenNodesThreshold, healthChecker); healthChecks.put(ioThread, task); task.cancelKey = ioThread.executeAtInterval(task, modCluster.getHealthCheckInterval(), TimeUnit.MILLISECONDS); } task.nodes.add(node); } void removeHealthCheck(final Node node, XnioIoThread ioThread) { assert Thread.holdsLock(this); final HealthCheckTask task = healthChecks.get(ioThread); if (task == null) { return; } task.nodes.remove(node); if (task.nodes.size() == 0) { healthChecks.remove(ioThread); task.cancelKey.remove(); } } static long removeThreshold(final long healthChecks, final long removeBrokenNodes) { if (healthChecks > 0 && removeBrokenNodes > 0) { final long threshold = removeBrokenNodes / healthChecks; if (threshold > 1000) { return 1000; } else if (threshold < 1) { return 1; } else { return threshold; } } else { return -1; } } static class HealthCheckTask implements Runnable { private final long threshold; private final NodeHealthChecker healthChecker; private final ArrayList nodes = new ArrayList<>(); private volatile XnioExecutor.Key cancelKey; HealthCheckTask(long threshold, NodeHealthChecker healthChecker) { this.threshold = threshold; this.healthChecker = healthChecker; } @Override public void run() { for (final Node node : nodes) { node.checkHealth(threshold, healthChecker); } } } class UpdateLoadTask implements Runnable { private volatile XnioExecutor.Key cancelKey; @Override public void run() { for (final Node node : nodes.values()) { node.resetLbStatus(); } } } @Override public ModClusterStatus getStatus() { List balancers = new ArrayList<>(); for(Map.Entry bentry : this.balancers.entrySet()) { List nodes = new ArrayList<>(); for(Node node : this.getNodes()) { if(node.getBalancer().getName().equals(bentry.getKey())) { List contexts = new ArrayList<>(); for(Context i : node.getContexts()) { contexts.add(new ContextImpl(i)); } nodes.add(new NodeImpl(node, contexts)); } } balancers.add(new BalancerImpl(bentry.getValue(), nodes)); } return new ModClusterStatusImpl(balancers); } private static class ModClusterStatusImpl implements ModClusterStatus { private final List balancers; private ModClusterStatusImpl(List balancers) { this.balancers = balancers; } @Override public List getLoadBalancers() { return balancers; } @Override public LoadBalancer getLoadBalancer(String name) { for (LoadBalancer b : balancers) { if (b.getName().equals(name)) { return b; } } return null; } } private static class BalancerImpl implements ModClusterStatus.LoadBalancer { private final Balancer balancer; private final List nodes; private BalancerImpl(Balancer balancer, List nodes) { this.balancer = balancer; this.nodes = nodes; } @Override public String getName() { return balancer.getName(); } @Override public List getNodes() { return nodes; } @Override public ModClusterStatus.Node getNode(String name) { for (ModClusterStatus.Node i : nodes) { if(i.getName().equals(name)) { return i; } } return null; } @Override public boolean isStickySession() { return balancer.isStickySession(); } @Override public String getStickySessionCookie() { return balancer.getStickySessionCookie(); } @Override public String getStickySessionPath() { return null; } @Override public boolean isStickySessionRemove() { return balancer.isStickySessionRemove(); } @Override public boolean isStickySessionForce() { return balancer.isStickySessionForce(); } @Override public int getWaitWorker() { return balancer.getWaitWorker(); } @Override public int getMaxRetries() { return balancer.getMaxRetries(); } @Override @Deprecated public int getMaxAttempts() { return balancer.getMaxRetries(); } } private static class NodeImpl implements ModClusterStatus.Node { private final Node node; private final List contexts; private NodeImpl(Node node, List contexts) { this.node = node; this.contexts = contexts; } @Override public String getName() { return node.getJvmRoute(); } @Override public URI getUri() { return node.getConnectionPool().getUri(); } @Override public List getContexts() { return Collections.unmodifiableList(contexts); } @Override public ModClusterStatus.Context getContext(String name) { for (ModClusterStatus.Context i : contexts) { if(i.getName().equals(name)) { return i; } } return null; } @Override public int getLoad() { return node.getLoad(); } @Override public NodeStatus getStatus() { return node.getStatus(); } @Override public int getOpenConnections() { return node.getConnectionPool().getOpenConnections(); } @Override public long getTransferred() { return node.getConnectionPool().getClientStatistics().getWritten(); } @Override public long getRead() { return node.getConnectionPool().getClientStatistics().getRead(); } @Override public int getElected() { return node.getElected(); } @Override public int getCacheConnections() { return node.getNodeConfig().getCacheConnections(); } @Override public String getJvmRoute() { return node.getNodeConfig().getJvmRoute(); } @Override public String getDomain() { return node.getNodeConfig().getDomain(); } @Override public int getFlushWait() { return node.getNodeConfig().getFlushwait(); } @Override public int getMaxConnections() { return node.getNodeConfig().getMaxConnections(); } @Override public int getPing() { return node.getNodeConfig().getPing(); } @Override public int getRequestQueueSize() { return node.getNodeConfig().getRequestQueueSize(); } @Override public int getTimeout() { return node.getNodeConfig().getTimeout(); } @Override public long getTtl() { return node.getNodeConfig().getTtl(); } @Override public boolean isFlushPackets() { return node.getNodeConfig().isFlushPackets(); } @Override public boolean isQueueNewRequests() { return node.getNodeConfig().isQueueNewRequests(); } @Override public List getAliases() { List ret = new ArrayList<>(); for(Node.VHostMapping host : node.getVHosts()) { ret.addAll(host.getAliases()); } return ret; } @Override public void resetStatistics() { node.getConnectionPool().getClientStatistics().reset(); } } private static class ContextImpl implements ModClusterStatus.Context { private final Context context; private ContextImpl(Context context) { this.context = context; } @Override public String getName() { return context.getPath(); } @Override public boolean isEnabled() { return context.isEnabled(); } @Override public boolean isStopped() { return context.isStopped(); } @Override public int getRequests() { return context.getActiveRequests(); } @Override public void enable() { context.enable(); } @Override public void disable() { context.disable(); } @Override public void stop() { context.stop(); } } } ModClusterController.java000066400000000000000000000015611420065311100360570ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; /** * @author Stuart Douglas */ public interface ModClusterController { ModClusterStatus getStatus(); } ModClusterProxyClient.java000066400000000000000000000133021420065311100362100ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import static org.xnio.IoUtils.safeClose; import java.util.concurrent.TimeUnit; import io.undertow.client.ClientConnection; import io.undertow.server.HttpServerExchange; import io.undertow.server.ServerConnection; import io.undertow.server.handlers.proxy.ExclusivityChecker; import io.undertow.server.handlers.proxy.ProxyCallback; import io.undertow.server.handlers.proxy.ProxyClient; import io.undertow.server.handlers.proxy.ProxyConnection; import io.undertow.util.AttachmentKey; class ModClusterProxyClient implements ProxyClient { /** * The attachment key that is used to attach the proxy connection to the exchange. *

* This cannot be static as otherwise a connection from a different client could be re-used. */ private final AttachmentKey exclusiveConnectionKey = AttachmentKey .create(ExclusiveConnectionHolder.class); private final ExclusivityChecker exclusivityChecker; private final ModClusterContainer container; protected ModClusterProxyClient(ExclusivityChecker exclusivityChecker, ModClusterContainer container) { this.exclusivityChecker = exclusivityChecker; this.container = container; } @Override public ProxyTarget findTarget(HttpServerExchange exchange) { return container.findTarget(exchange); } public void getConnection(final ProxyTarget target, final HttpServerExchange exchange, final ProxyCallback callback, final long timeout, final TimeUnit timeUnit) { final ExclusiveConnectionHolder holder = exchange.getConnection().getAttachment(exclusiveConnectionKey); if (holder != null && holder.connection.getConnection().isOpen()) { // Something has already caused an exclusive connection to be // allocated so keep using it. callback.completed(exchange, holder.connection); return; } if (! (target instanceof ModClusterProxyTarget)) { callback.couldNotResolveBackend(exchange); return; } // Resolve the node final ModClusterProxyTarget proxyTarget = (ModClusterProxyTarget) target; final Context context = proxyTarget.resolveContext(exchange); if (context == null) { callback.couldNotResolveBackend(exchange); } else { if (holder != null || (exclusivityChecker != null && exclusivityChecker.isExclusivityRequired(exchange))) { // If we have a holder, even if the connection was closed we now // exclusivity was already requested so our client // may be assuming it still exists. final ProxyCallback wrappedCallback = new ProxyCallback() { @Override public void completed(HttpServerExchange exchange, ProxyConnection result) { if (holder != null) { holder.connection = result; } else { final ExclusiveConnectionHolder newHolder = new ExclusiveConnectionHolder(); newHolder.connection = result; ServerConnection connection = exchange.getConnection(); connection.putAttachment(exclusiveConnectionKey, newHolder); connection.addCloseListener(new ServerConnection.CloseListener() { @Override public void closed(ServerConnection connection) { ClientConnection clientConnection = newHolder.connection.getConnection(); if (clientConnection.isOpen()) { safeClose(clientConnection); } } }); } callback.completed(exchange, result); } @Override public void queuedRequestFailed(HttpServerExchange exchange) { callback.queuedRequestFailed(exchange); } @Override public void failed(HttpServerExchange exchange) { callback.failed(exchange); } @Override public void couldNotResolveBackend(HttpServerExchange exchange) { callback.couldNotResolveBackend(exchange); } }; context.handleRequest(proxyTarget, exchange, wrappedCallback, timeout, timeUnit, true); } else { context.handleRequest(proxyTarget, exchange, callback, timeout, timeUnit, false); } } } private static class ExclusiveConnectionHolder { private ProxyConnection connection; } } ModClusterProxyTarget.java000066400000000000000000000113661420065311100362300ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import java.util.Iterator; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.proxy.ProxyClient; /** * @author Emanuel Muckenhuber * @author Radoslav Husar */ public interface ModClusterProxyTarget extends ProxyClient.ProxyTarget, ProxyClient.MaxRetriesProxyTarget { /** * Resolve the responsible context handling this request. * * @param exchange the http server exchange * @return the context */ Context resolveContext(HttpServerExchange exchange); class ExistingSessionTarget implements ModClusterProxyTarget { private final String session; private final Iterator routes; private final VirtualHost.HostEntry entry; private final boolean forceStickySession; private final ModClusterContainer container; private boolean resolved; private Context resolvedContext; public ExistingSessionTarget(String session, Iterator routes, VirtualHost.HostEntry entry, ModClusterContainer container, boolean forceStickySession) { this.session = session; this.routes = routes; this.entry = entry; this.container = container; this.forceStickySession = forceStickySession; } @Override public Context resolveContext(HttpServerExchange exchange) { resolveContextIfUnresolved(); return resolvedContext; } void resolveContextIfUnresolved() { if (resolved) return; resolved = true; boolean firstResolved = false; String firstRoute = null; String firstRouteDomain = null; while (routes.hasNext()) { final String jvmRoute = routes.next().toString(); final Context context = entry.getContextForNode(jvmRoute); if (context != null && context.checkAvailable(true)) { final Node node = context.getNode(); node.elected(); // Maybe move this to context#handleRequest this.resolvedContext = context; return; } if (!firstResolved) { firstResolved = true; firstRoute = jvmRoute; firstRouteDomain = context != null ? context.getNode().getNodeConfig().getDomain() : null; } } this.resolvedContext = container.findFailoverNode(entry, firstRouteDomain, session, firstRoute, forceStickySession); } @Override public int getMaxRetries() { resolveContextIfUnresolved(); if (resolvedContext == null) { return 0; } Balancer balancer = resolvedContext.getNode().getBalancer(); if(balancer == null) { return 0; } return balancer.getMaxRetries(); } } class BasicTarget implements ModClusterProxyTarget { private final VirtualHost.HostEntry entry; private final ModClusterContainer container; private Context resolved; public BasicTarget(VirtualHost.HostEntry entry, ModClusterContainer container) { this.entry = entry; this.container = container; } @Override public int getMaxRetries() { if(resolved == null) { resolveNode(); } if(resolved == null) { return 0; } Balancer balancer = resolved.getNode().getBalancer(); if(balancer == null) { return 0; } return balancer.getMaxRetries(); } @Override public Context resolveContext(HttpServerExchange exchange) { if(resolved == null) { resolveNode(); } return resolved; } private void resolveNode() { this.resolved = container.findNewNode(entry); } } } ModClusterStatus.java000066400000000000000000000053101420065311100352130ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import java.net.URI; import java.util.List; /** * An interface that allows the current status of the mod_cluster container to be queried and modified * * @author Stuart Douglas */ public interface ModClusterStatus { List getLoadBalancers(); LoadBalancer getLoadBalancer(String name); interface LoadBalancer { String getName(); List getNodes(); Node getNode(String name); boolean isStickySession(); String getStickySessionCookie(); String getStickySessionPath(); boolean isStickySessionRemove(); boolean isStickySessionForce(); int getWaitWorker(); /** * Returns maximum number of failover attempts to send the request to the backend server. * * @return number of failover attempts */ int getMaxRetries(); /** * @deprecated Use {@link LoadBalancer#getMaxRetries()}. */ @Deprecated int getMaxAttempts(); } interface Node { String getName(); URI getUri(); List getContexts(); Context getContext(String name); int getLoad(); NodeStatus getStatus(); int getOpenConnections(); long getTransferred(); long getRead(); int getElected(); int getCacheConnections(); String getJvmRoute(); String getDomain(); int getFlushWait(); int getMaxConnections(); int getPing(); int getRequestQueueSize(); int getTimeout(); long getTtl(); boolean isFlushPackets(); boolean isQueueNewRequests(); List getAliases(); void resetStatistics(); } interface Context { String getName(); boolean isEnabled(); boolean isStopped(); int getRequests(); void enable(); void disable(); void stop(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/Node.java000066400000000000000000000351651420065311100327050ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.anyAreSet; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import io.undertow.UndertowLogger; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.proxy.ConnectionPoolManager; import io.undertow.server.handlers.proxy.ProxyConnectionPool; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import org.xnio.XnioIoThread; /** * @author Stuart Douglas * @author Emanuel Muckenhuber */ class Node { private final int id; private final String jvmRoute; private final ConnectionPoolManager connectionPoolManager; private final NodeConfig nodeConfig; private final Balancer balancerConfig; private final ProxyConnectionPool connectionPool; private final NodeLbStatus lbStatus = new NodeLbStatus(); private final ModClusterContainer container; private final List vHosts = new CopyOnWriteArrayList<>(); private final List contexts = new CopyOnWriteArrayList<>(); private final XnioIoThread ioThread; private final ByteBufferPool bufferPool; private volatile int state = ERROR; // This gets cleared with the first status report private static final int ERROR = 1 << 31; private static final int REMOVED = 1 << 30; private static final int HOT_STANDBY = 1 << 29; private static final int ACTIVE_PING = 1 << 28; private static final int ERROR_MASK = (1 << 10) - 1; private static final AtomicInteger idGen = new AtomicInteger(); private static final AtomicIntegerFieldUpdater stateUpdater = AtomicIntegerFieldUpdater.newUpdater(Node.class, "state"); protected Node(NodeConfig nodeConfig, Balancer balancerConfig, XnioIoThread ioThread, ByteBufferPool bufferPool, ModClusterContainer container) { this.id = idGen.incrementAndGet(); this.jvmRoute = nodeConfig.getJvmRoute(); this.nodeConfig = nodeConfig; this.ioThread = ioThread; this.bufferPool = bufferPool; this.balancerConfig = balancerConfig; this.container = container; this.connectionPoolManager = new NodeConnectionPoolManager(); this.connectionPool = new ProxyConnectionPool(connectionPoolManager, nodeConfig.getConnectionURI(), container.getXnioSsl(), container.getClient(), container.getClientOptions()); } public int getId() { return id; } /** * Get the JVM route. * * @return the jvmRoute */ public String getJvmRoute() { return jvmRoute; } public Balancer getBalancer() { return balancerConfig; } public NodeConfig getNodeConfig() { return nodeConfig; } /** * Get or create the connection pool for this node. * * @return the connection pool */ public ProxyConnectionPool getConnectionPool() { return connectionPool; } XnioIoThread getIoThread() { return ioThread; } public NodeStatus getStatus() { final int status = this.state; if (anyAreSet(status, ERROR)) { return NodeStatus.NODE_DOWN; } else if (anyAreSet(status, HOT_STANDBY)) { return NodeStatus.NODE_HOT_STANDBY; } else { return NodeStatus.NODE_UP; } } public int getElected() { return lbStatus.getElected(); } int getElectedDiff() { return lbStatus.getElectedDiff(); } /** * Get the load information. Add the error information for clients. * * @return the node load */ public int getLoad() { final int status = this.state; if (anyAreSet(status, ERROR)) { return -1; } else if (anyAreSet(status, HOT_STANDBY)) { return 0; } else { return lbStatus.getLbFactor(); } } /** * Get the current load status, based on the number of elections and the current load; * * @return the load status */ public int getLoadStatus() { return lbStatus.getLbStatus(); } /** * This node got elected to serve a request! */ void elected() { lbStatus.elected(); } List getVHosts() { return Collections.unmodifiableList(vHosts); } Collection getContexts() { return Collections.unmodifiableCollection(contexts); } void resetLbStatus() { if (allAreClear(state, ERROR)) { if (lbStatus.update()) { return; } } } /** * Check the health of the node and try to ping it if necessary. * * @param threshold the threshold after which the node should be removed * @param healthChecker the node health checker */ protected void checkHealth(long threshold, NodeHealthChecker healthChecker) { final int state = this.state; if (anyAreSet(state, REMOVED | ACTIVE_PING)) { return; } healthCheckPing(threshold, healthChecker); } void healthCheckPing(final long threshold, NodeHealthChecker healthChecker) { int oldState, newState; for (;;) { oldState = this.state; if ((oldState & ACTIVE_PING) != 0) { // There is already a ping active return; } newState = oldState | ACTIVE_PING; if (stateUpdater.compareAndSet(this, oldState, newState)) { break; } } NodePingUtil.internalPingNode(this, new NodePingUtil.PingCallback() { @Override public void completed() { clearActivePing(); } @Override public void failed() { if (healthCheckFailed() == threshold) { // Remove using the executor task pool ioThread.getWorker().execute(new Runnable() { @Override public void run() { container.removeNode(Node.this, true); clearActivePing(); } }); } else { clearActivePing(); } } }, healthChecker, ioThread, bufferPool, container.getClient(), container.getXnioSsl(), OptionMap.EMPTY); } /** * Async ping from the user * * @param exchange the http server exchange * @param callback the ping callback */ void ping(final HttpServerExchange exchange, final NodePingUtil.PingCallback callback) { NodePingUtil.pingNode(this, exchange, callback); } /** * Register a context. * * @param path the context path * @return the created context */ Context registerContext(final String path, final List virtualHosts) { VHostMapping host = null; for (final VHostMapping vhost : vHosts) { if (virtualHosts.equals(vhost.getAliases())) { host = vhost; break; } } if (host == null) { host = new VHostMapping(this, virtualHosts); vHosts.add(host); } final Context context = new Context(path, host, this); contexts.add(context); return context; } /** * Get a context. * * @param path the context path * @param aliases the aliases * @return the context, {@code null} if there is no matching context */ Context getContext(final String path, List aliases) { VHostMapping host = null; for (final VHostMapping vhost : vHosts) { if (aliases.equals(vhost.getAliases())) { host = vhost; break; } } if (host == null) { return null; } for (final Context context : contexts) { if (context.getPath().equals(path) && context.getVhost() == host) { return context; } } return null; } Context removeContext(final String path, final List aliases) { final Context context = getContext(path, aliases); if (context != null) { context.stop(); contexts.remove(context); return context; } return null; } protected void updateLoad(final int i) { int oldState, newState; for (;;) { oldState = this.state; newState = oldState & ~(ERROR | HOT_STANDBY | ERROR_MASK); if (stateUpdater.compareAndSet(this, oldState, newState)) { lbStatus.updateLoad(i); return; } } } protected void hotStandby() { int oldState, newState; for (;;) { oldState = this.state; newState = oldState & ~(ERROR | ERROR_MASK) | HOT_STANDBY; if (stateUpdater.compareAndSet(this, oldState, newState)) { lbStatus.updateLoad(0); return; } } } protected void markRemoved() { int oldState, newState; for (;;) { oldState = this.state; newState = oldState | REMOVED; if (stateUpdater.compareAndSet(this, oldState, newState)) { connectionPool.close(); return; } } } protected void markInError() { int oldState, newState; for (;;) { oldState = this.state; newState = oldState | ERROR; if (stateUpdater.compareAndSet(this, oldState, newState)) { UndertowLogger.ROOT_LOGGER.nodeIsInError(jvmRoute); return; } } } private void clearActivePing() { int oldState, newState; for (;;) { oldState = this.state; newState = oldState & ~ACTIVE_PING; if (stateUpdater.compareAndSet(this, oldState, newState)) { return; } } } /** * Mark a node in error. Mod_cluster has a threshold after which broken nodes get removed. * * @return */ private int healthCheckFailed() { int oldState, newState; for (;;) { oldState = this.state; if ((oldState & ERROR) != ERROR) { newState = oldState | ERROR; UndertowLogger.ROOT_LOGGER.nodeIsInError(jvmRoute); } else if ((oldState & ERROR_MASK) == ERROR_MASK) { return ERROR_MASK; } else { newState = oldState +1; } if (stateUpdater.compareAndSet(this, oldState, newState)) { return newState & ERROR_MASK; } } } protected void resetState() { state = ERROR; lbStatus.updateLoad(0); } protected boolean isInErrorState() { return (state & ERROR) == ERROR; } boolean isHotStandby() { return anyAreSet(state, HOT_STANDBY); } protected boolean checkAvailable(final boolean existingSession) { if (allAreClear(state, ERROR | REMOVED)) { // Check the state of the queue on the connection pool final ProxyConnectionPool.AvailabilityType availability = connectionPool.available(); if (availability == ProxyConnectionPool.AvailabilityType.AVAILABLE) { return true; } else if (availability == ProxyConnectionPool.AvailabilityType.FULL) { if (existingSession) { return true; } else if (nodeConfig.isQueueNewRequests()) { return true; } } } return false; } private class NodeConnectionPoolManager implements ConnectionPoolManager { @Override public boolean isAvailable() { return allAreClear(state, ERROR | REMOVED); } @Override public boolean handleError() { markInError(); return false; } @Override public boolean clearError() { // This needs to be cleared through the status update return isAvailable(); } @Override public int getMaxConnections() { return nodeConfig.getMaxConnections(); } @Override public int getMaxCachedConnections() { return nodeConfig.getMaxConnections(); } @Override public int getSMaxConnections() { return nodeConfig.getSmax(); } @Override public long getTtl() { return nodeConfig.getTtl(); } @Override public int getMaxQueueSize() { return nodeConfig.getRequestQueueSize(); } @Override public int getProblemServerRetry() { return -1; // Disable ping from the pool, this is handled through the health-check } } // Simple host mapping for the mod cluster management protocol static final AtomicInteger vHostIdGen = new AtomicInteger(); static class VHostMapping { private final int id; private final List aliases; private final Node node; VHostMapping(Node node, List aliases) { this.id = vHostIdGen.incrementAndGet(); this.aliases = aliases; this.node = node; } public int getId() { return id; } public List getAliases() { return aliases; } Node getNode() { return node; } } @Override public String toString() { return "Node{" + "jvmRoute='" + jvmRoute + '\'' + ", contexts=" + contexts + '}'; } } NodeConfig.java000066400000000000000000000216161420065311100337500ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import io.undertow.UndertowLogger; import java.net.URI; import java.net.URISyntaxException; /** * The node configuration. * * @author Nabil Benothman * @author Emanuel Muckenhuber */ public class NodeConfig { /** * The JVM Route. */ private final String jvmRoute; /** * The connection URI. */ private final URI connectionURI; /** * The balancer configuration to use. */ private final String balancer; /** * The failover domain. */ private final String domain; /** * Tell how to flush the packets. On: Send immediately, Auto wait for flushwait time before sending, Off don't flush. * Default: "Off" */ private boolean flushPackets; /** * Time to wait before flushing. Value in milliseconds. Default: 10 */ private final int flushwait; /** * Time to wait for a pong answer to a ping. 0 means we don't try to ping before sending. Value in seconds Default: 10 * (10_000 in milliseconds) */ private final int ping; /** * max time in milliseconds to life for connection above smax. Default 60 seconds (60,000 in milliseconds). */ private final long ttl; /** * Max time the proxy will wait for the backend connection. Default 0 no timeout value in seconds. */ private final int timeout; // Proxy connection pool defaults private final int maxConnections; private final int cacheConnections; private final int requestQueueSize; private final boolean queueNewRequests; private final int waitWorker; NodeConfig(NodeBuilder b, final URI connectionURI) { this.connectionURI = connectionURI; balancer = b.balancer; domain = b.domain; jvmRoute = b.jvmRoute; flushPackets = b.flushPackets; flushwait = b.flushwait; ping = b.ping; ttl = b.ttl; timeout = b.timeout; maxConnections = b.maxConnections; cacheConnections = b.cacheConnections; requestQueueSize = b.requestQueueSize; queueNewRequests = b.queueNewRequests; waitWorker = b.waitWorker; UndertowLogger.ROOT_LOGGER.nodeConfigCreated(this.connectionURI, balancer, domain, jvmRoute, flushPackets, flushwait, ping, ttl, timeout, maxConnections, cacheConnections, requestQueueSize, queueNewRequests); } /** * Get the connection URI. * * @return the connection URI */ public URI getConnectionURI() { return connectionURI; } /** * Getter for domain * * @return the domain */ public String getDomain() { return this.domain; } /** * Getter for flushwait * * @return the flushwait */ public int getFlushwait() { return this.flushwait; } /** * Getter for ping * * @return the ping */ public int getPing() { return this.ping; } /** * Getter for smax * * @return the smax */ public int getSmax() { return this.cacheConnections; } /** * Getter for ttl * * @return the ttl */ public long getTtl() { return this.ttl; } /** * Getter for timeout * * @return the timeout */ public int getTimeout() { return this.timeout; } /** * Getter for balancer * * @return the balancer */ public String getBalancer() { return this.balancer; } public boolean isFlushPackets() { return flushPackets; } public void setFlushPackets(boolean flushPackets) { this.flushPackets = flushPackets; } public String getJvmRoute() { return jvmRoute; } /** * Get the maximum connection limit for a nodes thread-pool. * * @return the max connections limit */ public int getMaxConnections() { return maxConnections; } /** * Get the amount of connections which should be kept alive in the connection pool. * * @return the number of cached connections */ public int getCacheConnections() { return cacheConnections; } /** * Get the max queue size for requests. * * @return the queue size for requests */ public int getRequestQueueSize() { return requestQueueSize; } /** * Flag indicating whether requests without a session can be queued. * * @return true if requests without a session id can be queued */ public boolean isQueueNewRequests() { return queueNewRequests; } public static NodeBuilder builder(ModCluster modCluster) { return new NodeBuilder(modCluster); } public static class NodeBuilder { private String jvmRoute; private String balancer = "mycluster"; private String domain = null; private String type = "http"; private String hostname; private int port; private boolean flushPackets = false; private int flushwait = 10; private int ping = 10000; private int maxConnections; private int cacheConnections; private int requestQueueSize; private boolean queueNewRequests; private long ttl; private int timeout = 0; private int waitWorker = -1; NodeBuilder(final ModCluster modCluster) { this.maxConnections = modCluster.getMaxConnections(); this.cacheConnections = modCluster.getCacheConnections(); this.requestQueueSize = modCluster.getRequestQueueSize(); this.queueNewRequests = modCluster.isQueueNewRequests(); this.ttl = modCluster.getTtl(); } public NodeBuilder setHostname(String hostname) { this.hostname = hostname; return this; } public NodeBuilder setPort(int port) { this.port = port; return this; } public NodeBuilder setType(String type) { this.type = type; return this; } public NodeBuilder setBalancer(String balancer) { this.balancer = balancer; return this; } public NodeBuilder setDomain(String domain) { this.domain = domain; return this; } public NodeBuilder setJvmRoute(String jvmRoute) { this.jvmRoute = jvmRoute; return this; } public NodeBuilder setFlushPackets(boolean flushPackets) { this.flushPackets = flushPackets; return this; } public NodeBuilder setFlushwait(int flushwait) { this.flushwait = flushwait; return this; } public NodeBuilder setPing(int ping) { this.ping = ping; return this; } public NodeBuilder setSmax(int smax) { this.cacheConnections = smax; return this; } public NodeBuilder setMaxConnections(int maxConnections) { this.maxConnections = maxConnections; return this; } public NodeBuilder setCacheConnections(int cacheConnections) { this.cacheConnections = cacheConnections; return this; } public NodeBuilder setRequestQueueSize(int requestQueueSize) { this.requestQueueSize = requestQueueSize; return this; } public NodeBuilder setQueueNewRequests(boolean queueNewRequests) { this.queueNewRequests = queueNewRequests; return this; } public NodeBuilder setTtl(long ttl) { this.ttl = ttl; return this; } public NodeBuilder setTimeout(int timeout) { this.timeout = timeout; return this; } public NodeConfig build() throws URISyntaxException { final URI uri = new URI(type, null, hostname, port, "/", "", ""); return new NodeConfig(this, uri); } public void setWaitWorker(int waitWorker) { this.waitWorker = waitWorker; } public int getWaitWorker() { return waitWorker; } } } NodeHealthChecker.java000066400000000000000000000032301420065311100352250ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import io.undertow.client.ClientResponse; /** * @author Emanuel Muckenhuber */ public interface NodeHealthChecker { /** * Check the response of a health check. * * @param response the client response * @return true if the response from the node is healthy */ boolean checkResponse(final ClientResponse response); /** * Receiving a response is a success. */ NodeHealthChecker NO_CHECK = new NodeHealthChecker() { @Override public boolean checkResponse(ClientResponse response) { return true; } }; /** * Check that the response code is 2xx to 3xx. */ NodeHealthChecker OK = new NodeHealthChecker() { @Override public boolean checkResponse(final ClientResponse response) { final int code = response.getResponseCode(); return code >= 200 && code < 400; } }; } NodeLbStatus.java000066400000000000000000000043531420065311100343030ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; /** * The load-balancing information of a node. * * @author Emanuel Muckenhuber */ // move this back to Node class NodeLbStatus { private volatile int oldelected; private volatile int lbfactor; private volatile int lbstatus; private volatile int elected; public int getLbFactor() { return lbfactor; } public int getElected() { return elected; } synchronized int getElectedDiff() { return elected - oldelected; } /** * Update the load balancing status. * * @return */ synchronized boolean update() { int elected = this.elected; int oldelected = this.oldelected; int lbfactor = this.lbfactor; if (lbfactor > 0) { this.lbstatus = ((elected - oldelected) * 1000) / lbfactor; } this.oldelected = elected; return elected != oldelected; // ping if they are equal } synchronized void elected() { if (elected == Integer.MAX_VALUE) { oldelected = (elected - oldelected); elected = 1; } else { elected++; } } void updateLoad(int load) { lbfactor = load; } /** * Get the load balancing status. * * @return */ synchronized int getLbStatus() { int lbfactor = this.lbfactor; if (lbfactor > 0) { return (((elected - oldelected) * 1000) / lbfactor) + lbstatus; } else { return -1; } } } NodePingUtil.java000066400000000000000000000463061420065311100343010ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import java.io.IOException; import java.net.InetSocketAddress; import java.net.URI; import java.util.concurrent.TimeUnit; import io.undertow.UndertowLogger; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientExchange; import io.undertow.client.ClientRequest; import io.undertow.client.UndertowClient; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.proxy.ProxyCallback; import io.undertow.server.handlers.proxy.ProxyConnection; import io.undertow.util.Headers; import io.undertow.util.Methods; import io.undertow.util.SameThreadExecutor; import org.xnio.ChannelExceptionHandler; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoFuture; import org.xnio.IoUtils; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import io.undertow.util.WorkerUtils; import org.xnio.StreamConnection; import org.xnio.XnioExecutor; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; import org.xnio.ssl.XnioSsl; /** * Utilities to ping a remote node. * * @author Emanuel Muckenhuber */ class NodePingUtil { interface PingCallback { /** * Ping completed. */ void completed(); /** * Ping failed. */ void failed(); } /** * Try to open a socket connection to given address. * * @param address the socket address * @param exchange the http servers exchange * @param callback the ping callback * @param options the options */ static void pingHost(InetSocketAddress address, HttpServerExchange exchange, PingCallback callback, OptionMap options) { final XnioIoThread thread = exchange.getIoThread(); final XnioWorker worker = thread.getWorker(); final HostPingTask r = new HostPingTask(address, worker, callback, options); // Schedule timeout task scheduleCancelTask(exchange.getIoThread(), r, 5, TimeUnit.SECONDS); exchange.dispatch(exchange.isInIoThread() ? SameThreadExecutor.INSTANCE : thread, r); } /** * Try to ping a server using the undertow client. * * @param connection the connection URI * @param callback the ping callback * @param exchange the http servers exchange * @param client the undertow client * @param xnioSsl the ssl setup * @param options the options */ static void pingHttpClient(URI connection, PingCallback callback, HttpServerExchange exchange, UndertowClient client, XnioSsl xnioSsl, OptionMap options) { final XnioIoThread thread = exchange.getIoThread(); final RequestExchangeListener exchangeListener = new RequestExchangeListener(callback, NodeHealthChecker.NO_CHECK, true); final Runnable r = new HttpClientPingTask(connection, exchangeListener, thread, client, xnioSsl, exchange.getConnection().getByteBufferPool(), options); exchange.dispatch(exchange.isInIoThread() ? SameThreadExecutor.INSTANCE : thread, r); // Schedule timeout task scheduleCancelTask(exchange.getIoThread(), exchangeListener, 5, TimeUnit.SECONDS); } /** * Try to ping a node using it's connection pool. * * @param node the node * @param exchange the http servers exchange * @param callback the ping callback */ static void pingNode(final Node node, final HttpServerExchange exchange, final PingCallback callback) { if (node == null) { callback.failed(); return; } final int timeout = node.getNodeConfig().getPing(); exchange.dispatch(exchange.isInIoThread() ? SameThreadExecutor.INSTANCE : exchange.getIoThread(), new Runnable() { @Override public void run() { node.getConnectionPool().connect(null, exchange, new ProxyCallback() { @Override public void completed(final HttpServerExchange exchange, ProxyConnection result) { final RequestExchangeListener exchangeListener = new RequestExchangeListener(callback, NodeHealthChecker.NO_CHECK, false); exchange.dispatch(SameThreadExecutor.INSTANCE, new ConnectionPoolPingTask(result, exchangeListener, node.getNodeConfig().getConnectionURI())); // Schedule timeout task scheduleCancelTask(exchange.getIoThread(), exchangeListener, timeout, TimeUnit.SECONDS); } @Override public void failed(HttpServerExchange exchange) { callback.failed(); } @Override public void queuedRequestFailed(HttpServerExchange exchange) { callback.failed(); } @Override public void couldNotResolveBackend(HttpServerExchange exchange) { callback.failed(); } }, timeout, TimeUnit.SECONDS, false); } }); } /** * Internally ping a node. This should probably use the connections from the nodes pool, if there are any available. * * @param node the node * @param callback the ping callback * @param ioThread the xnio i/o thread * @param bufferPool the xnio buffer pool * @param client the undertow client * @param xnioSsl the ssl setup * @param options the options */ static void internalPingNode(Node node, PingCallback callback, NodeHealthChecker healthChecker, XnioIoThread ioThread, ByteBufferPool bufferPool, UndertowClient client, XnioSsl xnioSsl, OptionMap options) { final URI uri = node.getNodeConfig().getConnectionURI(); final long timeout = node.getNodeConfig().getPing(); final RequestExchangeListener exchangeListener = new RequestExchangeListener(callback, healthChecker, true); final HttpClientPingTask r = new HttpClientPingTask(uri, exchangeListener, ioThread, client, xnioSsl, bufferPool, options); // Schedule timeout task scheduleCancelTask(ioThread, exchangeListener, timeout, TimeUnit.SECONDS); ioThread.execute(r); } static class ConnectionPoolPingTask implements Runnable { private final RequestExchangeListener exchangeListener; private final ProxyConnection proxyConnection; private final URI uri; ConnectionPoolPingTask(ProxyConnection proxyConnection, RequestExchangeListener exchangeListener, URI uri) { this.proxyConnection = proxyConnection; this.exchangeListener = exchangeListener; this.uri = uri; } @Override public void run() { // TODO AJP has a special ping thing final ClientRequest request = new ClientRequest(); request.setMethod(Methods.OPTIONS); request.setPath("*"); request.getRequestHeaders() .add(Headers.USER_AGENT, "mod_cluster ping") .add(Headers.HOST, uri.getHost()); proxyConnection.getConnection().sendRequest(request, new ClientCallback() { @Override public void completed(final ClientExchange result) { if (exchangeListener.isDone()) { IoUtils.safeClose(proxyConnection.getConnection()); return; } exchangeListener.exchange = result; result.setResponseListener(exchangeListener); try { result.getRequestChannel().shutdownWrites(); if (!result.getRequestChannel().flush()) { result.getRequestChannel().getWriteSetter().set(ChannelListeners.flushingChannelListener(null, new ChannelExceptionHandler() { @Override public void handleException(StreamSinkChannel channel, IOException exception) { IoUtils.safeClose(proxyConnection.getConnection()); exchangeListener.taskFailed(); } })); result.getRequestChannel().resumeWrites(); } } catch (IOException e) { IoUtils.safeClose(proxyConnection.getConnection()); exchangeListener.taskFailed(); } } @Override public void failed(IOException e) { exchangeListener.taskFailed(); } }); } } static class HostPingTask extends CancellableTask implements Runnable { private final InetSocketAddress address; private final XnioWorker worker; private final OptionMap options; HostPingTask(InetSocketAddress address, XnioWorker worker, PingCallback callback, OptionMap options) { super(callback); this.address = address; this.worker = worker; this.options = options; } @Override public void run() { try { final IoFuture future = worker.openStreamConnection(address, new ChannelListener() { @Override public void handleEvent(StreamConnection channel) { IoUtils.safeClose(channel); // Close the channel right away } }, options); future.addNotifier(new IoFuture.HandlingNotifier() { @Override public void handleCancelled(Void attachment) { cancel(); } @Override public void handleFailed(IOException exception, Void attachment) { taskFailed(); } @Override public void handleDone(StreamConnection data, Void attachment) { taskCompleted(); } }, null); } catch (Exception e) { taskFailed(); } } } static class HttpClientPingTask implements Runnable { private final URI connection; private final XnioIoThread thread; private final UndertowClient client; private final XnioSsl xnioSsl; private final ByteBufferPool bufferPool; private final OptionMap options; private final RequestExchangeListener exchangeListener; HttpClientPingTask(URI connection, RequestExchangeListener exchangeListener, XnioIoThread thread, UndertowClient client, XnioSsl xnioSsl, ByteBufferPool bufferPool, OptionMap options) { this.connection = connection; this.thread = thread; this.client = client; this.xnioSsl = xnioSsl; this.bufferPool = bufferPool; this.options = options; this.exchangeListener = exchangeListener; } @Override public void run() { UndertowLogger.ROOT_LOGGER.httpClientPingTask(connection); // TODO AJP has a special ping thing client.connect(new ClientCallback() { @Override public void completed(final ClientConnection clientConnection) { if (exchangeListener.isDone()) { IoUtils.safeClose(clientConnection); return; } final ClientRequest request = new ClientRequest(); request.setMethod(Methods.OPTIONS); request.setPath("*"); request.getRequestHeaders() .add(Headers.USER_AGENT, "mod_cluster ping") .add(Headers.HOST, connection.getHost()); clientConnection.sendRequest(request, new ClientCallback() { @Override public void completed(ClientExchange result) { exchangeListener.exchange = result; if (exchangeListener.isDone()) { return; } result.setResponseListener(exchangeListener); try { result.getRequestChannel().shutdownWrites(); if (!result.getRequestChannel().flush()) { result.getRequestChannel().getWriteSetter().set(ChannelListeners.flushingChannelListener(null, new ChannelExceptionHandler() { @Override public void handleException(StreamSinkChannel channel, IOException exception) { IoUtils.safeClose(clientConnection); exchangeListener.taskFailed(); } })); result.getRequestChannel().resumeWrites(); } } catch (IOException e) { IoUtils.safeClose(clientConnection); exchangeListener.taskFailed(); } } @Override public void failed(IOException e) { exchangeListener.taskFailed(); IoUtils.safeClose(clientConnection); } }); } @Override public void failed(IOException e) { exchangeListener.taskFailed(); } }, connection, thread, xnioSsl, bufferPool, options); } } static class RequestExchangeListener extends CancellableTask implements ClientCallback { private ClientExchange exchange; private final boolean closeConnection; private final NodeHealthChecker healthChecker; RequestExchangeListener(PingCallback callback, NodeHealthChecker healthChecker, boolean closeConnection) { super(callback); assert healthChecker != null; this.closeConnection = closeConnection; this.healthChecker = healthChecker; } @Override public void completed(final ClientExchange result) { if (isDone()) { IoUtils.safeClose(result.getConnection()); return; } final ChannelListener listener = ChannelListeners.drainListener(Long.MAX_VALUE, new ChannelListener() { @Override public void handleEvent(StreamSourceChannel channel) { try { if (healthChecker.checkResponse(result.getResponse())) { taskCompleted(); } else { taskFailed(); } } finally { if (closeConnection) { if (exchange != null) { IoUtils.safeClose(exchange.getConnection()); } } } } }, new ChannelExceptionHandler() { @Override public void handleException(StreamSourceChannel channel, IOException exception) { taskFailed(); if (exception != null) { IoUtils.safeClose(exchange.getConnection()); } } }); StreamSourceChannel responseChannel = result.getResponseChannel(); responseChannel.getReadSetter().set(listener); responseChannel.resumeReads(); listener.handleEvent(responseChannel); } @Override public void failed(IOException e) { taskFailed(); if (exchange != null) { IoUtils.safeClose(exchange.getConnection()); } } } enum State { WAITING, DONE, CANCELLED; } static class CancellableTask { private final PingCallback delegate; private volatile State state = State.WAITING; private volatile XnioExecutor.Key cancelKey; CancellableTask(PingCallback callback) { this.delegate = callback; } boolean isDone() { return state != State.WAITING; } void setCancelKey(XnioExecutor.Key cancelKey) { if (state == State.WAITING) { this.cancelKey = cancelKey; } else { cancelKey.remove(); } } void taskCompleted() { if (state == State.WAITING) { state = State.DONE; if (cancelKey != null) { cancelKey.remove(); } delegate.completed(); } } void taskFailed() { if (state == State.WAITING) { state = State.DONE; if (cancelKey != null) { cancelKey.remove(); } delegate.failed(); } } void cancel() { if (state == State.WAITING) { state = State.CANCELLED; if (cancelKey != null) { cancelKey.remove(); } delegate.failed(); } } } static void scheduleCancelTask(final XnioIoThread ioThread, final CancellableTask cancellable, final long timeout, final TimeUnit timeUnit ) { final XnioExecutor.Key key = WorkerUtils.executeAfter(ioThread, new Runnable() { @Override public void run() { cancellable.cancel(); } }, timeout, timeUnit); cancellable.setCancelKey(key); } } NodeStatus.java000066400000000000000000000017501420065311100340230ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; /** * @author Stuart Douglas */ public enum NodeStatus { /** * The node is up */ NODE_UP, /** * The node is down */ NODE_DOWN, /** * The node is paused */ NODE_HOT_STANDBY; } VirtualHost.java000066400000000000000000000141671420065311100342240ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.ConcurrentMap; import io.undertow.UndertowMessages; import io.undertow.util.CopyOnWriteMap; import io.undertow.util.PathMatcher; import io.undertow.util.URLUtils; /** * The virtual host handler. * * @author Emanuel Muckenhuber */ public class VirtualHost { private static final String STRING_PATH_SEPARATOR = "/"; private final HostEntry defaultHandler = new HostEntry(STRING_PATH_SEPARATOR); private final ConcurrentMap contexts = new CopyOnWriteMap<>(); /** * lengths of all registered contexts */ private volatile int[] lengths = {}; protected VirtualHost() { // } /** * Matches a path against the registered handlers. * * @param path The relative path to match * @return The match match. This will never be null, however if none matched its value field will be */ PathMatcher.PathMatch match(String path){ int length = path.length(); final int[] lengths = this.lengths; for (int i = 0; i < lengths.length; ++i) { int pathLength = lengths[i]; if (pathLength == length) { HostEntry next = contexts.get(path); if (next != null) { return new PathMatcher.PathMatch<>(path, "", next); } } else if (pathLength < length) { char c = path.charAt(pathLength); if (c == '/') { String part = path.substring(0, pathLength); HostEntry next = contexts.get(part); if (next != null) { return new PathMatcher.PathMatch<>(part, path.substring(pathLength), next); } } } } if(defaultHandler.contexts.isEmpty()) { return new PathMatcher.PathMatch<>("", path, null); } return new PathMatcher.PathMatch<>("", path, defaultHandler); } public synchronized void registerContext(final String path, final String jvmRoute, final Context context) { if (path.isEmpty()) { throw UndertowMessages.MESSAGES.pathMustBeSpecified(); } final String normalizedPath = URLUtils.normalizeSlashes(path); if (STRING_PATH_SEPARATOR.equals(normalizedPath)) { defaultHandler.contexts.put(jvmRoute, context); return; } boolean rebuild = false; HostEntry hostEntry = contexts.get(normalizedPath); if (hostEntry == null) { rebuild = true; hostEntry = new HostEntry(normalizedPath); contexts.put(normalizedPath, hostEntry); } assert !hostEntry.contexts.containsKey(jvmRoute); hostEntry.contexts.put(jvmRoute, context); if (rebuild) { buildLengths(); } } public synchronized void removeContext(final String path, final String jvmRoute, final Context context) { if (path == null || path.isEmpty()) { throw UndertowMessages.MESSAGES.pathMustBeSpecified(); } final String normalizedPath = URLUtils.normalizeSlashes(path); if (STRING_PATH_SEPARATOR.equals(normalizedPath)) { defaultHandler.contexts.remove(jvmRoute, context); } final HostEntry hostEntry = contexts.get(normalizedPath); if (hostEntry != null) { if (hostEntry.contexts.remove(jvmRoute, context)) { if (hostEntry.contexts.isEmpty()) { contexts.remove(normalizedPath); buildLengths(); } } } } boolean isEmpty() { return contexts.isEmpty() && defaultHandler.contexts.isEmpty(); } private void buildLengths() { final Set lengths = new TreeSet<>(new Comparator() { @Override public int compare(Integer o1, Integer o2) { return -o1.compareTo(o2); } }); for (String p : contexts.keySet()) { lengths.add(p.length()); } int[] lengthArray = new int[lengths.size()]; int pos = 0; for (int i : lengths) { lengthArray[pos++] = i; } this.lengths = lengthArray; } static class HostEntry { // node > context private final ConcurrentMap contexts = new CopyOnWriteMap<>(); private final String contextPath; HostEntry(String contextPath) { this.contextPath = contextPath; } protected String getContextPath() { return contextPath; } /** * Get a context for a jvmRoute. * * @param jvmRoute the jvm route */ protected Context getContextForNode(final String jvmRoute) { return contexts.get(jvmRoute); } /** * Get list of nodes as jvmRoutes. */ protected Collection getNodes() { return Collections.unmodifiableCollection(contexts.keySet()); } /** * Get all registered contexts. */ protected Collection getContexts() { return Collections.unmodifiableCollection(contexts.values()); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/resource/000077500000000000000000000000001420065311100272715ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/resource/CachedResource.java000066400000000000000000000315651420065311100330250ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.resource; import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.ByteBuffer; import java.nio.file.Path; import java.util.Date; import java.util.List; import io.undertow.UndertowLogger; import io.undertow.io.IoCallback; import io.undertow.io.Sender; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.cache.DirectBufferCache; import io.undertow.server.handlers.cache.LimitedBufferSlicePool; import io.undertow.server.handlers.cache.ResponseCachingSender; import io.undertow.util.DateUtils; import io.undertow.util.ETag; import io.undertow.util.MimeMappings; /** * @author Stuart Douglas */ public class CachedResource implements Resource, RangeAwareResource { private final CacheKey cacheKey; private final CachingResourceManager cachingResourceManager; private final Resource underlyingResource; private final boolean directory; private final Date lastModifiedDate; private final String lastModifiedDateString; private final ETag eTag; private final String name; private volatile long nextMaxAgeCheck; public CachedResource(final CachingResourceManager cachingResourceManager, final Resource underlyingResource, final String path) { this.cachingResourceManager = cachingResourceManager; this.underlyingResource = underlyingResource; this.directory = underlyingResource.isDirectory(); this.lastModifiedDate = underlyingResource.getLastModified(); if (lastModifiedDate != null) { this.lastModifiedDateString = DateUtils.toDateString(lastModifiedDate); } else { this.lastModifiedDateString = null; } this.eTag = underlyingResource.getETag(); this.name = underlyingResource.getName(); this.cacheKey = new CacheKey(cachingResourceManager, underlyingResource.getCacheKey()); if (cachingResourceManager.getMaxAge() > 0) { nextMaxAgeCheck = System.currentTimeMillis() + cachingResourceManager.getMaxAge(); } else { nextMaxAgeCheck = -1; } } @Override public String getPath() { return underlyingResource.getPath(); } @Override public Date getLastModified() { return lastModifiedDate; } @Override public String getLastModifiedString() { return lastModifiedDateString; } @Override public ETag getETag() { return eTag; } @Override public String getName() { return name; } @Override public boolean isDirectory() { return directory; } @Override public List list() { return underlyingResource.list(); } @Override public String getContentType(final MimeMappings mimeMappings) { return underlyingResource.getContentType(mimeMappings); } public void invalidate() { final DirectBufferCache dataCache = cachingResourceManager.getDataCache(); if(dataCache != null) { dataCache.remove(cacheKey); } } public boolean checkStillValid() { if (nextMaxAgeCheck > 0) { long time = System.currentTimeMillis(); if (time > nextMaxAgeCheck) { nextMaxAgeCheck = time + cachingResourceManager.getMaxAge(); if (!underlyingResource.getLastModified().equals(lastModifiedDate)) { return false; } } } return true; } @Override public void serve(final Sender sender, final HttpServerExchange exchange, final IoCallback completionCallback) { final DirectBufferCache dataCache = cachingResourceManager.getDataCache(); if(dataCache == null) { underlyingResource.serve(sender, exchange, completionCallback); return; } final DirectBufferCache.CacheEntry existing = dataCache.get(cacheKey); final Long length = getContentLength(); //if it is not eligible to be served from the cache if (length == null || length > cachingResourceManager.getMaxFileSize()) { underlyingResource.serve(sender, exchange, completionCallback); return; } //it is not cached yet, install a wrapper to grab the data if (existing == null || !existing.enabled() || !existing.reference()) { Sender newSender = sender; final DirectBufferCache.CacheEntry entry; if (existing == null) { entry = dataCache.add(cacheKey, length.intValue(), cachingResourceManager.getMaxAge()); } else { entry = existing; } if (entry != null && entry.buffers().length != 0 && entry.claimEnable()) { if (entry.reference()) { newSender = new ResponseCachingSender(sender, entry, length); } else { entry.disable(); } } underlyingResource.serve(newSender, exchange, completionCallback); } else { UndertowLogger.REQUEST_LOGGER.tracef("Serving resource %s from the buffer cache to %s", name, exchange); //serve straight from the cache ByteBuffer[] buffers; boolean ok = false; try { LimitedBufferSlicePool.PooledByteBuffer[] pooled = existing.buffers(); buffers = new ByteBuffer[pooled.length]; for (int i = 0; i < buffers.length; i++) { // Keep position from mutating buffers[i] = pooled[i].getBuffer().duplicate(); } ok = true; } finally { if (!ok) { existing.dereference(); } } sender.send(buffers, new DereferenceCallback(existing, completionCallback)); } } @Override public Long getContentLength() { //we always use the underlying size unless the data is cached in the buffer cache //to prevent a mis-match between size on disk and cached size final DirectBufferCache dataCache = cachingResourceManager.getDataCache(); if(dataCache == null) { return underlyingResource.getContentLength(); } final DirectBufferCache.CacheEntry existing = dataCache.get(cacheKey); if(existing == null || !existing.enabled()) { return underlyingResource.getContentLength(); } //we only return the return (long)existing.size(); } @Override public String getCacheKey() { return cacheKey.cacheKey; } @Override public File getFile() { return underlyingResource.getFile(); } @Override public Path getFilePath() { return underlyingResource.getFilePath(); } @Override public File getResourceManagerRoot() { return underlyingResource.getResourceManagerRoot(); } @Override public Path getResourceManagerRootPath() { return underlyingResource.getResourceManagerRootPath(); } @Override public URL getUrl() { return underlyingResource.getUrl(); } @Override public void serveRange(Sender sender, HttpServerExchange exchange, long start, long end, IoCallback completionCallback) { final DirectBufferCache dataCache = cachingResourceManager.getDataCache(); if(dataCache == null) { ((RangeAwareResource)underlyingResource).serveRange(sender, exchange, start, end, completionCallback); return; } final DirectBufferCache.CacheEntry existing = dataCache.get(cacheKey); final Long length = getContentLength(); //if it is not eligible to be served from the cache if (length == null || length > cachingResourceManager.getMaxFileSize()) { ((RangeAwareResource)underlyingResource).serveRange(sender, exchange, start, end, completionCallback); return; } //it is not cached yet, just serve it directly if (existing == null || !existing.enabled() || !existing.reference()) { //it is not cached yet, we can't use a range request to establish the cached item //so we just serve it ((RangeAwareResource)underlyingResource).serveRange(sender, exchange, start, end, completionCallback); } else { //serve straight from the cache ByteBuffer[] buffers; boolean ok = false; try { LimitedBufferSlicePool.PooledByteBuffer[] pooled = existing.buffers(); buffers = new ByteBuffer[pooled.length]; for (int i = 0; i < buffers.length; i++) { // Keep position from mutating buffers[i] = pooled[i].getBuffer().duplicate(); } ok = true; } finally { if (!ok) { existing.dereference(); } } long endTarget = end + 1; //as it is inclusive long startDec = start; long endCount = 0; //handle the start of the range for (ByteBuffer b : buffers) { if (endCount == endTarget) { b.limit(b.position()); continue; } else if (endCount + b.remaining() < endTarget) { endCount += b.remaining(); } else { b.limit((int) (b.position() + (endTarget - endCount))); endCount = endTarget; } if (b.remaining() >= startDec) { b.position((int) (b.position() + startDec)); startDec = 0; } else { startDec -= b.remaining(); b.position(b.limit()); } } sender.send(buffers, new DereferenceCallback(existing, completionCallback)); } } @Override public boolean isRangeSupported() { //we can only handle range requests if the underlying resource supports it //even if we have the resource in the cache it may disappear before we try and serve it return underlyingResource instanceof RangeAwareResource && ((RangeAwareResource) underlyingResource).isRangeSupported(); } private static class DereferenceCallback implements IoCallback { private final DirectBufferCache.CacheEntry entry; private final IoCallback callback; DereferenceCallback(DirectBufferCache.CacheEntry entry, final IoCallback callback) { this.entry = entry; this.callback = callback; } @Override public void onComplete(final HttpServerExchange exchange, final Sender sender) { try { entry.dereference(); } finally { callback.onComplete(exchange, sender); } } @Override public void onException(final HttpServerExchange exchange, final Sender sender, final IOException exception) { UndertowLogger.REQUEST_IO_LOGGER.ioException(exception); try { entry.dereference(); } finally { callback.onException(exchange, sender, exception); } } } static final class CacheKey { final CachingResourceManager manager; final String cacheKey; CacheKey(CachingResourceManager manager, String cacheKey) { this.manager = manager; this.cacheKey = cacheKey; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CacheKey cacheKey1 = (CacheKey) o; if (cacheKey != null ? !cacheKey.equals(cacheKey1.cacheKey) : cacheKey1.cacheKey != null) return false; if (manager != null ? !manager.equals(cacheKey1.manager) : cacheKey1.manager != null) return false; return true; } @Override public int hashCode() { int result = manager != null ? manager.hashCode() : 0; result = 31 * result + (cacheKey != null ? cacheKey.hashCode() : 0); return result; } } } CachingResourceManager.java000066400000000000000000000143751420065311100344260ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/resource/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.resource; import java.io.IOException; import java.util.Collection; import java.util.Set; import io.undertow.UndertowLogger; import io.undertow.server.handlers.cache.DirectBufferCache; import io.undertow.server.handlers.cache.LRUCache; /** * @author Stuart Douglas */ public class CachingResourceManager implements ResourceManager { /** * The biggest file size we cache */ private final long maxFileSize; /** * The underlying resource manager */ private final ResourceManager underlyingResourceManager; /** * A cache of byte buffers */ private final DirectBufferCache dataCache; /** * A cache of file metadata, such as if a file exists or not */ private final LRUCache cache; private final int maxAge; public CachingResourceManager(final int metadataCacheSize, final long maxFileSize, final DirectBufferCache dataCache, final ResourceManager underlyingResourceManager, final int maxAge) { this.maxFileSize = maxFileSize; this.underlyingResourceManager = underlyingResourceManager; this.dataCache = dataCache; this.cache = new LRUCache<>(metadataCacheSize, maxAge); this.maxAge = maxAge; if(underlyingResourceManager.isResourceChangeListenerSupported()) { try { underlyingResourceManager.registerResourceChangeListener(new ResourceChangeListener() { @Override public void handleChanges(Collection changes) { for(ResourceChangeEvent change : changes) { invalidate(change.getResource()); } } }); } catch (Exception e) { UndertowLogger.ROOT_LOGGER.couldNotRegisterChangeListener(e); } } } @Override public CachedResource getResource(final String p) throws IOException { if( p == null ) { return null; } final String path; //base always ends with a / if (p.startsWith("/")) { path = p.substring(1); } else { path = p; } Object res = cache.get(path); if (res instanceof NoResourceMarker) { NoResourceMarker marker = (NoResourceMarker) res; long nextCheck = marker.getNextCheckTime(); if(nextCheck > 0) { long time = System.currentTimeMillis(); if(time > nextCheck) { marker.setNextCheckTime(time + maxAge); if(underlyingResourceManager.getResource(path) != null) { cache.remove(path); } else { return null; } } else { return null; } } else { return null; } } else if (res != null) { CachedResource resource = (CachedResource) res; if (resource.checkStillValid()) { return resource; } else { invalidate(path); } } final Resource underlying = underlyingResourceManager.getResource(path); if (underlying == null) { cache.add(path, new NoResourceMarker(maxAge > 0 ? System.currentTimeMillis() + maxAge : -1)); return null; } final CachedResource resource = new CachedResource(this, underlying, path); cache.add(path, resource); return resource; } @Override public boolean isResourceChangeListenerSupported() { return underlyingResourceManager.isResourceChangeListenerSupported(); } @Override public void registerResourceChangeListener(ResourceChangeListener listener) { underlyingResourceManager.registerResourceChangeListener(listener); } @Override public void removeResourceChangeListener(ResourceChangeListener listener) { underlyingResourceManager.removeResourceChangeListener(listener); } public void invalidate(String path) { if(path.startsWith("/")) { path = path.substring(1); } Object entry = cache.remove(path); if (entry instanceof CachedResource) { ((CachedResource) entry).invalidate(); } } DirectBufferCache getDataCache() { return dataCache; } public long getMaxFileSize() { return maxFileSize; } public int getMaxAge() { return maxAge; } @Override public void close() throws IOException { try { //clear all cached data on close if(dataCache != null) { Set keys = dataCache.getAllKeys(); for(final Object key : keys) { if(key instanceof CachedResource.CacheKey) { if(((CachedResource.CacheKey) key).manager == this) { dataCache.remove(key); } } } } } finally { underlyingResourceManager.close(); } } private static final class NoResourceMarker { volatile long nextCheckTime; private NoResourceMarker(long nextCheckTime) { this.nextCheckTime = nextCheckTime; } public long getNextCheckTime() { return nextCheckTime; } public void setNextCheckTime(long nextCheckTime) { this.nextCheckTime = nextCheckTime; } } } ClassPathResourceManager.java000066400000000000000000000054171420065311100347510ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/resource/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.resource; import io.undertow.UndertowMessages; import java.io.IOException; import java.net.URL; /** * @author Stuart Douglas */ public class ClassPathResourceManager implements ResourceManager { /** * The class loader that is used to load resources */ private final ClassLoader classLoader; /** * The prefix that is appended to resources that are to be loaded. */ private final String prefix; public ClassPathResourceManager(final ClassLoader loader, final Package p) { this(loader, p.getName().replace(".", "/")); } public ClassPathResourceManager(final ClassLoader classLoader, final String prefix) { this.classLoader = classLoader; if (prefix.isEmpty()) { this.prefix = ""; } else if (prefix.endsWith("/")) { this.prefix = prefix; } else { this.prefix = prefix + "/"; } } public ClassPathResourceManager(final ClassLoader classLoader) { this(classLoader, ""); } @Override public Resource getResource(final String path) throws IOException { if( path == null ) { return null; } String modPath = path; if(modPath.startsWith("/")) { modPath = path.substring(1); } final String realPath = prefix + modPath; final URL resource = classLoader.getResource(realPath); if(resource == null) { return null; } else { return new URLResource(resource, path); } } @Override public boolean isResourceChangeListenerSupported() { return false; } @Override public void registerResourceChangeListener(ResourceChangeListener listener) { throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported(); } @Override public void removeResourceChangeListener(ResourceChangeListener listener) { throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported(); } @Override public void close() throws IOException { } } DefaultResourceSupplier.java000066400000000000000000000025161420065311100347010ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/resource/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.resource; import java.io.IOException; import io.undertow.server.HttpServerExchange; /** * A resource supplier that just delegates directly to a resource manager * * @author Stuart Douglas */ public class DefaultResourceSupplier implements ResourceSupplier { private final ResourceManager resourceManager; public DefaultResourceSupplier(ResourceManager resourceManager) { this.resourceManager = resourceManager; } @Override public Resource getResource(HttpServerExchange exchange, String path) throws IOException { return resourceManager.getResource(path); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/resource/DirectoryUtils.java000066400000000000000000000431161420065311100331260ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.resource; import io.undertow.UndertowLogger; import io.undertow.server.HttpServerExchange; import io.undertow.util.DateUtils; import io.undertow.util.ETag; import io.undertow.util.ETagUtils; import io.undertow.util.FlexBase64; import io.undertow.util.Headers; import io.undertow.util.Methods; import io.undertow.util.RedirectBuilder; import io.undertow.util.StatusCodes; import org.xnio.channels.Channels; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; /** * @author Stuart Douglas */ public class DirectoryUtils { /** * Serve static resource for the directory listing * * @param exchange The exchange * @return true if resources were served */ public static boolean sendRequestedBlobs(HttpServerExchange exchange) { ByteBuffer buffer = null; String type = null; String etag = null; String quotedEtag = null; if ("css".equals(exchange.getQueryString())) { buffer = Blobs.FILE_CSS_BUFFER.duplicate(); type = "text/css"; etag = Blobs.FILE_CSS_ETAG; quotedEtag = Blobs.FILE_CSS_ETAG_QUOTED; } else if ("js".equals(exchange.getQueryString())) { buffer = Blobs.FILE_JS_BUFFER.duplicate(); type = "application/javascript"; etag = Blobs.FILE_JS_ETAG; quotedEtag = Blobs.FILE_JS_ETAG_QUOTED; } if (buffer != null) { if(!ETagUtils.handleIfNoneMatch(exchange, new ETag(false, etag), false)) { exchange.setStatusCode(StatusCodes.NOT_MODIFIED); return true; } exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, String.valueOf(buffer.limit())); exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, type); exchange.getResponseHeaders().put(Headers.ETAG, quotedEtag); if (Methods.HEAD.equals(exchange.getRequestMethod())) { exchange.endExchange(); return true; } exchange.getResponseSender().send(buffer); return true; } return false; } public static StringBuilder renderDirectoryListing(String path, Resource resource) { if (!path.endsWith("/")){ path += "/"; } StringBuilder builder = new StringBuilder(); builder.append("\n\n\n") .append("\n\n"); builder.append("\n\n\n"); builder.append("\n") .append("\n\n") .append("\n\n\n\n"); int state = 0; String parent = null; if(path.length() > 1) { for (int i = path.length() - 1; i >= 0; i--) { if (state == 1) { if (path.charAt(i) == '/') { state = 2; } } else if (path.charAt(i) != '/') { if (state == 2) { parent = path.substring(0, i + 1); break; } state = 1; } } if(parent == null) { parent = "/"; } } SimpleDateFormat format = new SimpleDateFormat("MMM dd, yyyy HH:mm:ss", Locale.US); int i = 0; if (parent != null) { i++; builder.append("\n"); } for (Resource entry : resource.list()) { builder.append("\n"); } builder.append("\n
Directory Listing - ").append(path).append("
NameLast ModifiedSize
Powered by Undertow
[..]"); builder.append(format.format((resource.getLastModified() == null ? new Date(0L) : resource.getLastModified()))) .append("--
").append(entry.getName()).append(""); builder.append(format.format((entry.getLastModified() == null) ? new Date(0L) : entry.getLastModified())) .append(""); if (entry.isDirectory()) { builder.append("--"); } else { formatSize(builder, entry.getContentLength()); } builder.append("
\n\n"); return builder; } public static void renderDirectoryListing(HttpServerExchange exchange, Resource resource) { String requestPath = exchange.getRequestPath(); if (! requestPath.endsWith("/")) { exchange.setStatusCode(StatusCodes.FOUND); exchange.getResponseHeaders().put(Headers.LOCATION, RedirectBuilder.redirect(exchange, exchange.getRelativePath() + "/", true)); exchange.endExchange(); return; } StringBuilder builder = renderDirectoryListing(requestPath, resource); try { ByteBuffer output = ByteBuffer.wrap(builder.toString().getBytes(StandardCharsets.UTF_8)); exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/html; charset=UTF-8"); exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, String.valueOf(output.limit())); exchange.getResponseHeaders().put(Headers.LAST_MODIFIED, DateUtils.toDateString(new Date())); exchange.getResponseHeaders().put(Headers.CACHE_CONTROL, "must-revalidate"); Channels.writeBlocking(exchange.getResponseChannel(), output); } catch (UnsupportedEncodingException e) { throw new IllegalStateException(e); } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); } exchange.endExchange(); } private static StringBuilder formatSize(StringBuilder builder, Long size) { if(size == null) { builder.append("???"); return builder; } int n = 1024 * 1024 * 1024; int type = 0; while (size < n && n >= 1024) { n /= 1024; type++; } long top = (size * 100) / n; long bottom = top % 100; top /= 100; builder.append(top); if (bottom > 0) { builder.append(".").append(bottom / 10); bottom %= 10; if (bottom > 0) { builder.append(bottom); } } switch (type) { case 0: builder.append(" GB"); break; case 1: builder.append(" MB"); break; case 2: builder.append(" KB"); break; } return builder; } private DirectoryUtils() { } /** * Constant Content * * @author Jason T. Greene */ public static class Blobs { public static final String FILE_JS="function growit() {\n" + " var table = document.getElementById(\"thetable\");\n" + "\n" + " var i = table.rows.length - 1;\n" + " while (i-- > 0) {\n" + " if (table.rows[i].id == \"eraseme\") {\n" + " table.deleteRow(i);\n" + " } else {\n" + " break;\n" + " }\n" + " }\n" + " table.style.height=\"\";\n" + " var i = 0;\n" + " while (table.offsetHeight < window.innerHeight - 24) {\n" + " i++;\n" + " var tbody = table.tBodies[0];\n" + " var row = tbody.insertRow(tbody.rows.length);\n" + " row.id=\"eraseme\";\n" + " var cell = row.insertCell(0);\n" + " if (table.rows.length % 2 != 0) {\n" + " row.className=\"even eveninvis\";\n" + " } else {\n" + " row.className=\"odd oddinvis\";\n" + " }\n" + "\n" + " cell.colSpan=3;\n" + " cell.appendChild(document.createTextNode(\"i\"));\n" + " }\n" + " table.style.height=\"100%\";\n" + " if (i > 0) {\n" + " document.documentElement.style.overflowY=\"hidden\";\n" + " } else {\n" + " document.documentElement.style.overflowY=\"auto\";\n" + " }\n" + "}"; public static final String FILE_JS_ETAG = md5(FILE_JS.getBytes(StandardCharsets.US_ASCII)); public static final String FILE_JS_ETAG_QUOTED = '"' + FILE_JS_ETAG + '"'; public static final String FILE_CSS = "body {\n" + " font-family: \"Lucida Grande\", \"Lucida Sans Unicode\", \"Trebuchet MS\", Helvetica, Arial, Verdana, sans-serif;\n" + " margin: 5px;\n" + "}\n" + "\n" + "th.loc {\n" + " background-image: linear-gradient(bottom, rgb(153,151,153) 8%, rgb(199,199,199) 54%);\n" + " background-image: -o-linear-gradient(bottom, rgb(153,151,153) 8%, rgb(199,199,199) 54%);\n" + " background-image: -moz-linear-gradient(bottom, rgb(153,151,153) 8%, rgb(199,199,199) 54%);\n" + " background-image: -webkit-linear-gradient(bottom, rgb(153,151,153) 8%, rgb(199,199,199) 54%);\n" + " background-image: -ms-linear-gradient(bottom, rgb(153,151,153) 8%, rgb(199,199,199) 54%);\n" + " \n" + " background-image: -webkit-gradient(\n" + " linear,\n" + " left bottom,\n" + " left top,\n" + " color-stop(0.08, rgb(153,151,153)),\n" + " color-stop(0.54, rgb(199,199,199))\n" + " );\n" + " color: black;\n" + " padding: 2px;\n" + " font-weight: normal;\n" + " border: solid 1px;\n" + " font-size: 150%;\n" + " text-align: left;\n" + "}\n" + "\n" + "th.label {\n" + " border: solid 1px;\n" + " text-align: left;\n" + " padding: 4px;\n" + " padding-left: 8px;\n" + " font-weight: normal;\n" + " font-size: small;\n" + " background-color: #e8e8e8;\n" + "}\n" + "\n" + "th.offset {\n" + " padding-left: 32px;\n" + "}\n" + "\n" + "th.footer {\n" + " font-size: 75%;\n" + " text-align: right;\n" + "}\n" + "\n" + "a.icon {\n" + " padding-left: 24px;\n" + " text-decoration: none;\n" + " color: black;\n" + "}\n" + "\n" + "a.icon:hover {\n" + " text-decoration: underline;\n" + "}\n" + "\n" + "table {\n" + " border: 1px solid;\n" + " border-spacing: 0px;\n" + " width: 100%;\n" + " border-collapse: collapse;\n" + "}\n" + "\n" + "tr.odd {\n" + " background-color: #f3f6fa;\n" + "}\n" + "\n" + "tr.odd td {\n" + " padding: 2px;\n" + " padding-left: 8px;\n" + " font-size: smaller;\n" + "}\n" + "\n" + "tr.even {\n" + " background-color: #ffffff;\n" + "}\n" + "\n" + "tr.even td {\n" + " padding: 2px;\n" + " padding-left: 8px;\n" + " font-size: smaller;\n" + "}\n" + "\n" + "tr.eveninvis td {\n" + " color: #ffffff;\n" + "}\n" + "\n" + "tr.oddinvis td {\n" + " color: #f3f6fa\n" + "}\n" + "\n" + "a.up {\n" + " background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABI0lEQVQ4y2P4//8/Ay7sM4nhPwjjUwMm0ua//Y+M0+e//QrSGDAfgvEZAjdgydHXcAzTXLjWDoxhhqBbhGLA1N0vwBhdM7ohMHVwA8yrzn4zLj/936j8FE7N6IaA1IL0gPQy2DVc+rnp3FeCmtENAekB6WXw7Lz1tWD5x/+wEIdhdI3o8iA9IL0MYZMfvq9a9+V/w+avcIzLAGQ1ID0gvQxJc56/aNn29X/vnm9wjMsAZDWtQD0gvQwFy94+6N37/f/Moz/gGJcByGpAekB6GarXf7427ciP/0vP/YRjdP/CMLIakB6QXobKDd9PN+769b91P2kYpAekl2HJhb8r11/583/9ZRIxUM+8U783MQCBGBDXAHEbibgGrBdfTiMGU2wAAPz+nxp+TnhDAAAAAElFTkSuQmCC') left center no-repeat; background-size: 16px 16px;\n" + "}\n" + "\n" + "a.dir {\n" + " background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXZwQWcAAAAQAAAAEABcxq3DAAAA+UlEQVQ4jWP4//8/AyUYTKTNf/sfGafPf/s1be47G5IMWHL0NRxP2f3mbcaCtz/RDUbHKAZM3f2CJAw3wLzq7Dfj8tP/jcpPkYRBekB6GewaLv3cdO7r/y0XSMMgPSC9DJ6dt74WLP/4v3TVZ5IwSA9IL0PY5Ifvq9Z9+d+w+StJGKQHpJchac7zFy3bvv7v3fONJNwK1APSy5C/7O2D3r3f/888+oMkDNID0stQvf7ztWlHfvxfeu4nSRikB6SXoXLD99ONu379b91PGgbpAellWHLh38r1V/78X3+ZRAzUM/fUr00MQCAGxDVA3EYirgHrpUpupAQDAPs+7c1tGDnPAAAAAElFTkSuQmCC') left center no-repeat; background-size: 16px 16px;\n" + "}\n" + "\n" + "a.file {\n" + " background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXZwQWcAAAAQAAAAEABcxq3DAAABM0lEQVQ4y5WSTW6DMBCF3xvzc4wuOEIO0kVAuUB7vJ4g3KBdoHSRROomEpusUaoAcaYLfmKoqVRLIxnJ7/M3YwJVBcknACv8b+1U9SvoP1bXa/3WNDVIAQmQBLsNOEsGQYAwDNcARgDqusbl+wIRA2NkBEyqP0s+kCOAQhhjICJdkaDIJDwEvQAhH+G+SHagWTsi4jHoAWYIOxYDZDjnb8Fn4Akvz6AHcAbx3Tp5ETwI3RwckyVtv4Fr4VEe9qq6bDB5tlnYWou2bWGtRRRF6jdwAm5Za1FVFc7nM0QERVG8A9hPDRaGpapomgZlWSJJEuR5ftpsNq8ADr9amC+SuN/vuN1uIIntdnvKsuwZwKf2wxgBxpjpX+dA4jjW4/H4kabpixt2AbvAmDX+XnsAB509ww+A8mAar+XXgQAAAABJRU5ErkJggg==') left center no-repeat;\n" + "}"; public static final String FILE_CSS_ETAG = md5(FILE_CSS.getBytes(StandardCharsets.US_ASCII)); public static final String FILE_CSS_ETAG_QUOTED = '"' + FILE_CSS_ETAG + '"'; public static final ByteBuffer FILE_CSS_BUFFER; public static final ByteBuffer FILE_JS_BUFFER; static { try { byte[] bytes = FILE_CSS.getBytes(StandardCharsets.US_ASCII); FILE_CSS_BUFFER = ByteBuffer.allocateDirect(bytes.length); FILE_CSS_BUFFER.put(bytes); FILE_CSS_BUFFER.flip(); bytes = FILE_JS.getBytes(StandardCharsets.US_ASCII); FILE_JS_BUFFER = ByteBuffer.allocateDirect(bytes.length); FILE_JS_BUFFER.put(bytes); FILE_JS_BUFFER.flip(); } catch (Exception e) { throw new IllegalStateException(e); } } } /** * Generate the MD5 hash out of the given {@link ByteBuffer} */ private static String md5(byte[] buffer) { try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(buffer); byte[] digest = md.digest(); return new String(FlexBase64.encodeBytes(digest, 0, digest.length, false), StandardCharsets.US_ASCII); } catch (NoSuchAlgorithmException e) { // Should never happen throw new InternalError("MD5 not supported on this platform"); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/resource/FileResource.java000066400000000000000000000020101420065311100325140ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.resource; import java.io.File; /** * A file resource * * @author Stuart Douglas */ public class FileResource extends PathResource { public FileResource(final File file, final FileResourceManager manager, String path) { super(file.toPath(), manager, path); } } FileResourceManager.java000066400000000000000000000045431420065311100337450ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/resource/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.resource; import io.undertow.UndertowMessages; import java.io.File; /** * Serves files from the file system. */ public class FileResourceManager extends PathResourceManager { public FileResourceManager(final File base) { this(base, 1024, true, false, null); } public FileResourceManager(final File base, long transferMinSize) { this(base, transferMinSize, true, false, null); } public FileResourceManager(final File base, long transferMinSize, boolean caseSensitive) { this(base, transferMinSize, caseSensitive, false, null); } public FileResourceManager(final File base, long transferMinSize, boolean followLinks, final String... safePaths) { this(base, transferMinSize, true, followLinks, safePaths); } protected FileResourceManager(long transferMinSize, boolean caseSensitive, boolean followLinks, final String... safePaths) { super(transferMinSize, caseSensitive, followLinks, safePaths); } public FileResourceManager(final File base, long transferMinSize, boolean caseSensitive, boolean followLinks, final String... safePaths) { super(base.toPath(), transferMinSize, caseSensitive, followLinks, safePaths); } public File getBase() { return new File(base); } public FileResourceManager setBase(final File base) { if (base == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("base"); } String basePath = base.getAbsolutePath(); if (!basePath.endsWith("/")) { basePath = basePath + '/'; } this.base = basePath; return this; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/resource/PathResource.java000066400000000000000000000234311420065311100325430ustar00rootroot00000000000000package io.undertow.server.handlers.resource; import io.undertow.UndertowLogger; import io.undertow.io.IoCallback; import io.undertow.io.Sender; import io.undertow.server.HttpServerExchange; import io.undertow.util.DateUtils; import io.undertow.util.ETag; import io.undertow.util.MimeMappings; import io.undertow.util.StatusCodes; import org.xnio.IoUtils; import io.undertow.connector.PooledByteBuffer; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * A path resource * * @author Stuart Douglas */ public class PathResource implements RangeAwareResource { private final Path file; private final String path; private final ETag eTag; private final PathResourceManager manager; public PathResource(final Path file, final PathResourceManager manager, String path, ETag eTag) { this.file = file; this.path = path; this.manager = manager; this.eTag = eTag; } public PathResource(final Path file, final PathResourceManager manager, String path) { this(file, manager, path, null); } @Override public String getPath() { return path; } @Override public Date getLastModified() { try { if (Files.isSymbolicLink(file) && Files.notExists(file)) { return null; } return new Date(Files.getLastModifiedTime(file).toMillis()); } catch (IOException e) { throw new RuntimeException(e); } } @Override public String getLastModifiedString() { return DateUtils.toDateString(getLastModified()); } @Override public ETag getETag() { return eTag; } @Override public String getName() { if( file.getFileName() != null ) { return file.getFileName().toString(); } return file.toString(); } @Override public boolean isDirectory() { return Files.isDirectory(file); } @Override public List list() { final List resources = new ArrayList<>(); try (DirectoryStream stream = Files.newDirectoryStream(file)) { for (Path child : stream) { resources.add(new PathResource(child, manager, path + file.getFileSystem().getSeparator() + child.getFileName().toString())); } } catch (IOException e) { throw new RuntimeException(e); } return resources; } @Override public String getContentType(final MimeMappings mimeMappings) { final String fileName = file.getFileName().toString(); int index = fileName.lastIndexOf('.'); if (index != -1 && index != fileName.length() - 1) { return mimeMappings.getMimeType(fileName.substring(index + 1)); } return null; } @Override public void serve(final Sender sender, final HttpServerExchange exchange, final IoCallback callback) { serveImpl(sender, exchange, -1, -1, callback, false); } @Override public void serveRange(final Sender sender, final HttpServerExchange exchange, final long start, final long end, final IoCallback callback) { serveImpl(sender, exchange, start, end, callback, true); } private void serveImpl(final Sender sender, final HttpServerExchange exchange, final long start, final long end, final IoCallback callback, final boolean range) { abstract class BaseFileTask implements Runnable { protected volatile FileChannel fileChannel; protected boolean openFile() { try { fileChannel = FileChannel.open(file, StandardOpenOption.READ); if(range) { fileChannel.position(start); } } catch (NoSuchFileException e) { exchange.setStatusCode(StatusCodes.NOT_FOUND); callback.onException(exchange, sender, e); return false; } catch (IOException e) { exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); callback.onException(exchange, sender, e); return false; } return true; } } class ServerTask extends BaseFileTask implements IoCallback { private PooledByteBuffer pooled; long remaining = end - start + 1; @Override public void run() { if(range && remaining == 0) { //we are done if (pooled != null) { pooled.close(); pooled = null; } IoUtils.safeClose(fileChannel); callback.onComplete(exchange, sender); return; } if (fileChannel == null) { if (!openFile()) { return; } pooled = exchange.getConnection().getByteBufferPool().allocate(); } if (pooled != null) { ByteBuffer buffer = pooled.getBuffer(); try { buffer.clear(); int res = fileChannel.read(buffer); if (res == -1) { //we are done pooled.close(); IoUtils.safeClose(fileChannel); callback.onComplete(exchange, sender); return; } buffer.flip(); if(range) { if(buffer.remaining() > remaining) { buffer.limit((int) (buffer.position() + remaining)); } remaining -= buffer.remaining(); } sender.send(buffer, this); } catch (IOException e) { onException(exchange, sender, e); } } } @Override public void onComplete(final HttpServerExchange exchange, final Sender sender) { if (exchange.isInIoThread()) { exchange.dispatch(this); } else { run(); } } @Override public void onException(final HttpServerExchange exchange, final Sender sender, final IOException exception) { UndertowLogger.REQUEST_IO_LOGGER.ioException(exception); if (pooled != null) { pooled.close(); pooled = null; } IoUtils.safeClose(fileChannel); if (!exchange.isResponseStarted()) { exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); } callback.onException(exchange, sender, exception); } } class TransferTask extends BaseFileTask { @Override public void run() { if (!openFile()) { return; } sender.transferFrom(fileChannel, new IoCallback() { @Override public void onComplete(HttpServerExchange exchange, Sender sender) { try { IoUtils.safeClose(fileChannel); } finally { callback.onComplete(exchange, sender); } } @Override public void onException(HttpServerExchange exchange, Sender sender, IOException exception) { try { IoUtils.safeClose(fileChannel); } finally { callback.onException(exchange, sender, exception); } } }); } } BaseFileTask task; try { task = manager.getTransferMinSize() > Files.size(file) || range ? new ServerTask() : new TransferTask(); } catch (IOException e) { throw new RuntimeException(e); } if (exchange.isInIoThread()) { exchange.dispatch(task); } else { task.run(); } } @Override public Long getContentLength() { try { if (Files.isSymbolicLink(file) && Files.notExists(file)) { return null; } return Files.size(file); } catch (IOException e) { throw new RuntimeException(e); } } @Override public String getCacheKey() { return file.toString(); } @Override public File getFile() { return file.toFile(); } @Override public Path getFilePath() { return file; } @Override public File getResourceManagerRoot() { return manager.getBasePath().toFile(); } @Override public Path getResourceManagerRootPath() { return manager.getBasePath(); } @Override public URL getUrl() { try { return file.toUri().toURL(); } catch (MalformedURLException e) { throw new RuntimeException(e); } } @Override public boolean isRangeSupported() { return true; } } PathResourceManager.java000066400000000000000000000503421420065311100337600ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/resourcepackage io.undertow.server.handlers.resource; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.util.ETag; import org.jboss.logging.Logger; import org.xnio.FileChangeCallback; import org.xnio.FileChangeEvent; import org.xnio.FileSystemWatcher; import org.xnio.OptionMap; import org.xnio.Xnio; import java.io.File; import java.io.IOException; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.TreeSet; /** * Serves files from the file system. */ public class PathResourceManager implements ResourceManager { private static final Logger log = Logger.getLogger(PathResourceManager.class.getName()); private static final boolean DEFAULT_CHANGE_LISTENERS_ALLOWED = !Boolean.getBoolean("io.undertow.disable-file-system-watcher"); private static final long DEFAULT_TRANSFER_MIN_SIZE = 1024; private static final ETagFunction NULL_ETAG_FUNCTION = new ETagFunction() { @Override public ETag generate(Path path) { return null; } }; private final List listeners = new ArrayList<>(); private FileSystemWatcher fileSystemWatcher; protected volatile String base; protected volatile FileSystem fileSystem; /** * Size to use direct FS to network transfer (if supported by OS/JDK) instead of read/write */ private final long transferMinSize; /** * Check to validate caseSensitive issues for specific case-insensitive FS. * @see io.undertow.server.handlers.resource.PathResourceManager#isFileSameCase(java.nio.file.Path, String) */ private final boolean caseSensitive; /** * Check to allow follow symbolic links */ private final boolean followLinks; /** * Used if followLinks == true. Set of paths valid to follow symbolic links. If this is empty and followLinks * it true then all links will be followed */ private final TreeSet safePaths = new TreeSet<>(); private final ETagFunction eTagFunction; private final boolean allowResourceChangeListeners; public PathResourceManager(final Path base) { this(base, DEFAULT_TRANSFER_MIN_SIZE, true, false, null); } public PathResourceManager(final Path base, long transferMinSize) { this(base, transferMinSize, true, false, null); } public PathResourceManager(final Path base, long transferMinSize, boolean caseSensitive) { this(base, transferMinSize, caseSensitive, false, null); } public PathResourceManager(final Path base, long transferMinSize, boolean followLinks, final String... safePaths) { this(base, transferMinSize, true, followLinks, safePaths); } protected PathResourceManager(long transferMinSize, boolean caseSensitive, boolean followLinks, final String... safePaths) { this(transferMinSize, caseSensitive, followLinks, DEFAULT_CHANGE_LISTENERS_ALLOWED, safePaths); } protected PathResourceManager(long transferMinSize, boolean caseSensitive, boolean followLinks, boolean allowResourceChangeListeners, final String... safePaths) { this.fileSystem = FileSystems.getDefault(); this.caseSensitive = caseSensitive; this.followLinks = followLinks; this.transferMinSize = transferMinSize; this.allowResourceChangeListeners = allowResourceChangeListeners; if (this.followLinks) { if (safePaths == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("safePaths"); } for (final String safePath : safePaths) { if (safePath == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("safePaths"); } } this.safePaths.addAll(Arrays.asList(safePaths)); } this.eTagFunction = NULL_ETAG_FUNCTION; } public PathResourceManager(final Path base, long transferMinSize, boolean caseSensitive, boolean followLinks, final String... safePaths) { this(base, transferMinSize, caseSensitive, followLinks, DEFAULT_CHANGE_LISTENERS_ALLOWED, safePaths); } public PathResourceManager(final Path base, long transferMinSize, boolean caseSensitive, boolean followLinks, boolean allowResourceChangeListeners, final String... safePaths) { this(builder() .setBase(base) .setTransferMinSize(transferMinSize) .setCaseSensitive(caseSensitive) .setFollowLinks(followLinks) .setAllowResourceChangeListeners(allowResourceChangeListeners) .setSafePaths(safePaths)); } private PathResourceManager(Builder builder) { this.allowResourceChangeListeners = builder.allowResourceChangeListeners; if (builder.base == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("base"); } this.fileSystem = builder.base.getFileSystem(); String basePath = null; try { if(builder.followLinks) { basePath = builder.base.normalize().toRealPath().toString(); } else { basePath = builder.base.normalize().toRealPath(LinkOption.NOFOLLOW_LINKS).toString(); } } catch (IOException e) { throw UndertowMessages.MESSAGES.failedToInitializePathManager(builder.base.toString(), e); } if (!basePath.endsWith(fileSystem.getSeparator())) { basePath = basePath + fileSystem.getSeparator(); } this.base = basePath; this.transferMinSize = builder.transferMinSize; this.caseSensitive = builder.caseSensitive; this.followLinks = builder.followLinks; if (this.followLinks) { if (builder.safePaths == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("safePaths"); } for (final String safePath : builder.safePaths) { if (safePath == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("safePaths"); } } this.safePaths.addAll(Arrays.asList(builder.safePaths)); } this.eTagFunction = builder.eTagFunction; } public Path getBasePath() { return fileSystem.getPath(base); } public PathResourceManager setBase(final Path base) { if (base == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("base"); } this.fileSystem = base.getFileSystem(); String basePath = base.toAbsolutePath().toString(); if (!basePath.endsWith(fileSystem.getSeparator())) { basePath = basePath + fileSystem.getSeparator(); } this.base = basePath; return this; } public PathResourceManager setBase(final File base) { if (base == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("base"); } this.fileSystem = FileSystems.getDefault(); String basePath = base.getAbsolutePath(); if (!basePath.endsWith(fileSystem.getSeparator())) { basePath = basePath + fileSystem.getSeparator(); } this.base = basePath; return this; } public Resource getResource(final String p) { if( p == null ) { return null; } String path; //base always ends with a / if (p.startsWith("/")) { path = p.substring(1); } else { path = p; } try { Path file = fileSystem.getPath(base, path); String normalizedFile = file.normalize().toString(); if(!normalizedFile.startsWith(base)) { if(normalizedFile.length() == base.length() - 1) { //special case for the root path, which may not have a trailing slash if(!base.startsWith(normalizedFile)) { log.tracef("Failed to get path resource %s from path resource manager with base %s, as file was outside the base directory", p, base); return null; } } else { log.tracef("Failed to get path resource %s from path resource manager with base %s, as file was outside the base directory", p, base); return null; } } if (Files.exists(file)) { if(path.endsWith("/") && ! Files.isDirectory(file)) { //UNDERTOW-432 don't return non directories if the path ends with a / log.tracef("Failed to get path resource %s from path resource manager with base %s, as path ended with a / but was not a directory", p, base); return null; } boolean followAll = this.followLinks && safePaths.isEmpty(); SymlinkResult symlinkBase = getSymlinkBase(base, file); if (!followAll && symlinkBase != null && symlinkBase.requiresCheck) { if (this.followLinks && isSymlinkSafe(file)) { return getFileResource(file, path, symlinkBase.path, normalizedFile); } else { log.tracef("Failed to get path resource %s from path resource manager with base %s, as it was not a safe symlink path", p, base); return null; } } else { return getFileResource(file, path, symlinkBase == null ? null : symlinkBase.path, normalizedFile); } } else { log.tracef("Failed to get path resource %s from path resource manager with base %s, as the path did not exist", p, base); return null; } } catch (IOException e) { UndertowLogger.REQUEST_LOGGER.debugf(e, "Invalid path %s", p); return null; } catch (SecurityException e) { UndertowLogger.REQUEST_LOGGER.errorf(e, "Missing JSM permissions for path %s", p); throw e; } catch (Exception e) { UndertowLogger.REQUEST_LOGGER.debugf(e, "Other issue for path %s", p); return null; } } @Override public boolean isResourceChangeListenerSupported() { return allowResourceChangeListeners; } @Override public synchronized void registerResourceChangeListener(ResourceChangeListener listener) { if(!allowResourceChangeListeners) { //by rights we should throw an exception here, but this works around a bug in Wildfly where it just assumes //PathResourceManager supports this. This will be fixed in a later version return; } if (!fileSystem.equals(FileSystems.getDefault())) { throw new IllegalStateException("Resource change listeners not supported when using a non-default file system"); } listeners.add(listener); if (fileSystemWatcher == null) { fileSystemWatcher = Xnio.getInstance().createFileSystemWatcher("Watcher for " + base, OptionMap.EMPTY); fileSystemWatcher.watchPath(new File(base), new FileChangeCallback() { @Override public void handleChanges(Collection changes) { synchronized (PathResourceManager.this) { final List events = new ArrayList<>(); for (FileChangeEvent change : changes) { if (change.getFile().getAbsolutePath().startsWith(base)) { String path = change.getFile().getAbsolutePath().substring(base.length()); if (File.separatorChar == '\\' && path.contains(File.separator)) { path = path.replace(File.separatorChar, '/'); } events.add(new ResourceChangeEvent(path, ResourceChangeEvent.Type.valueOf(change.getType().name()))); } } for (ResourceChangeListener listener : listeners) { listener.handleChanges(events); } } } }); } } @Override public synchronized void removeResourceChangeListener(ResourceChangeListener listener) { if(!allowResourceChangeListeners) { return; } listeners.remove(listener); } public long getTransferMinSize() { return transferMinSize; } @Override public synchronized void close() throws IOException { if (fileSystemWatcher != null) { fileSystemWatcher.close(); } } /** * Returns true is some element of path inside base path is a symlink. */ private SymlinkResult getSymlinkBase(final String base, final Path file) { int nameCount = file.getNameCount(); Path root = fileSystem.getPath(base); int rootCount = root.getNameCount(); Path f = file; for (int i = nameCount - 1; i>=0; i--) { if (SecurityActions.isSymbolicLink(f)) { return new SymlinkResult(i+1 > rootCount, f); } f = f.getParent(); } return null; } /** * Security check for case insensitive file systems. * We make sure the case of the filename matches the case of the request. * This is only a check for case sensitivity, not for non canonical . and ../ which are allowed. * * For example: * file.getName() == "page.jsp" && file.getCanonicalFile().getName() == "page.jsp" should return true * file.getName() == "page.jsp" && file.getCanonicalFile().getName() == "page.JSP" should return false * file.getName() == "./page.jsp" && file.getCanonicalFile().getName() == "page.jsp" should return true */ private boolean isFileSameCase(final Path file, String normalizeFile) throws IOException { String canonicalName = file.toRealPath().toString(); return canonicalName.equals(normalizeFile); } /** * Security check for followSymlinks feature. * Only follows those symbolink links defined in safePaths. */ private boolean isSymlinkSafe(final Path file) throws IOException { String canonicalPath = file.toRealPath().toString(); for (String safePath : this.safePaths) { if (safePath.length() > 0) { if (safePath.startsWith(fileSystem.getSeparator())) { /* * Absolute path */ if (safePath.length() > 0 && canonicalPath.length() >= safePath.length() && canonicalPath.startsWith(safePath)) { return true; } } else { /* * In relative path we build the path appending to base */ String absSafePath = base + fileSystem.getSeparator() + safePath; Path absSafePathFile = fileSystem.getPath(absSafePath); String canonicalSafePath = absSafePathFile.toRealPath().toString(); if (canonicalSafePath.length() > 0 && canonicalPath.length() >= canonicalSafePath.length() && canonicalPath.startsWith(canonicalSafePath)) { return true; } } } } return false; } /** * Apply security check for case insensitive file systems. */ protected PathResource getFileResource(final Path file, final String path, final Path symlinkBase, String normalizedFile) throws IOException { if (this.caseSensitive) { if (symlinkBase != null) { String relative = symlinkBase.relativize(file.normalize()).toString(); String fileResolved = file.toRealPath().toString(); String symlinkBaseResolved = symlinkBase.toRealPath().toString(); if (!fileResolved.startsWith(symlinkBaseResolved)) { log.tracef("Rejected path resource %s from path resource manager with base %s, as the case did not match actual case of %s", path, base, normalizedFile); return null; } String compare = fileResolved.substring(symlinkBaseResolved.length()); if(compare.startsWith(fileSystem.getSeparator())) { compare = compare.substring(fileSystem.getSeparator().length()); } if(relative.startsWith(fileSystem.getSeparator())) { relative = relative.substring(fileSystem.getSeparator().length()); } if (relative.equals(compare)) { log.tracef("Found path resource %s from path resource manager with base %s", path, base); return new PathResource(file, this, path, eTagFunction.generate(file)); } log.tracef("Rejected path resource %s from path resource manager with base %s, as the case did not match actual case of %s", path, base, normalizedFile); return null; } else if (isFileSameCase(file, normalizedFile)) { log.tracef("Found path resource %s from path resource manager with base %s", path, base); return new PathResource(file, this, path, eTagFunction.generate(file)); } else { log.tracef("Rejected path resource %s from path resource manager with base %s, as the case did not match actual case of %s", path, base, normalizedFile); return null; } } else { log.tracef("Found path resource %s from path resource manager with base %s", path, base); return new PathResource(file, this, path, eTagFunction.generate(file)); } } private static class SymlinkResult { public final boolean requiresCheck; public final Path path; private SymlinkResult(boolean requiresCheck, Path path) { this.requiresCheck = requiresCheck; this.path = path; } } public interface ETagFunction { /** * Generates an {@link ETag} for the provided {@link Path}. * * @param path Path for which to generate an ETag * @return ETag representing the provided path, or null */ ETag generate(Path path); } public static Builder builder() { return new Builder(); } public static final class Builder { private Path base; private long transferMinSize = DEFAULT_TRANSFER_MIN_SIZE; private boolean caseSensitive = true; private boolean followLinks = false; private boolean allowResourceChangeListeners = DEFAULT_CHANGE_LISTENERS_ALLOWED; private ETagFunction eTagFunction = NULL_ETAG_FUNCTION; private String[] safePaths; private Builder() { } public Builder setBase(Path base) { this.base = base; return this; } public Builder setTransferMinSize(long transferMinSize) { this.transferMinSize = transferMinSize; return this; } public Builder setCaseSensitive(boolean caseSensitive) { this.caseSensitive = caseSensitive; return this; } public Builder setFollowLinks(boolean followLinks) { this.followLinks = followLinks; return this; } public Builder setAllowResourceChangeListeners(boolean allowResourceChangeListeners) { this.allowResourceChangeListeners = allowResourceChangeListeners; return this; } public Builder setETagFunction(ETagFunction eTagFunction) { this.eTagFunction = eTagFunction; return this; } public Builder setSafePaths(String[] safePaths) { this.safePaths = safePaths; return this; } public ResourceManager build() { return new PathResourceManager(this); } } } PreCompressedResourceSupplier.java000066400000000000000000000164221420065311100360710ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/resource/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.resource; import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.file.Path; import java.util.Date; import java.util.List; import java.util.Map; import io.undertow.io.IoCallback; import io.undertow.io.Sender; import io.undertow.server.HttpServerExchange; import io.undertow.util.CopyOnWriteMap; import io.undertow.util.ETag; import io.undertow.util.Headers; import io.undertow.util.MimeMappings; import io.undertow.util.QValueParser; /** * A resource supplier that allows pre-compressed resources to be served if the client accepts the request. *

* This is done by checking for the existence of a pre-compressed file, and if it exists and the * client supports the encoding then the resource is returned for the pre compressed file * * @author Stuart Douglas */ public class PreCompressedResourceSupplier implements ResourceSupplier { private final ResourceManager resourceManager; private final Map encodingMap = new CopyOnWriteMap<>(); public PreCompressedResourceSupplier(ResourceManager resourceManager) { this.resourceManager = resourceManager; } @Override public Resource getResource(HttpServerExchange exchange, String path) throws IOException { if(exchange.getRequestHeaders().contains(Headers.RANGE)) { //we don't use serve pre compressed resources for range requests return resourceManager.getResource(path); } Resource resource = getEncodedResource(exchange, path); if(resource == null) { return resourceManager.getResource(path); } return resource; } private Resource getEncodedResource(final HttpServerExchange exchange, String path) throws IOException { final List res = exchange.getRequestHeaders().get(Headers.ACCEPT_ENCODING); if (res == null || res.isEmpty()) { return null; } final List> found = QValueParser.parse(res); for (List result : found) { for (final QValueParser.QValueResult value : result) { String extension = encodingMap.get(value.getValue()); if(extension != null) { String newPath = path + extension; Resource resource = resourceManager.getResource(newPath); if(resource != null && !resource.isDirectory()) { return new Resource() { @Override public String getPath() { return resource.getPath(); } @Override public Date getLastModified() { return resource.getLastModified(); } @Override public String getLastModifiedString() { return resource.getLastModifiedString(); } @Override public ETag getETag() { return resource.getETag(); } @Override public String getName() { return resource.getName(); } @Override public boolean isDirectory() { return false; } @Override public List list() { return resource.list(); } @Override public String getContentType(MimeMappings mimeMappings) { String fileName = resource.getName(); String originalFileName = fileName.substring(0, fileName.length() - extension.length()); int index = originalFileName.lastIndexOf('.'); if (index != -1 && index != originalFileName.length() - 1) { return mimeMappings.getMimeType(originalFileName.substring(index + 1)); } return null; } @Override public void serve(Sender sender, HttpServerExchange exchange, IoCallback completionCallback) { exchange.getResponseHeaders().put(Headers.CONTENT_ENCODING, value.getValue()); resource.serve(sender, exchange, completionCallback); } @Override public Long getContentLength() { return resource.getContentLength(); } @Override public String getCacheKey() { return resource.getCacheKey(); } @Override public File getFile() { return resource.getFile(); } @Override public Path getFilePath() { return resource.getFilePath(); } @Override public File getResourceManagerRoot() { return resource.getResourceManagerRoot(); } @Override public Path getResourceManagerRootPath() { return resource.getResourceManagerRootPath(); } @Override public URL getUrl() { return resource.getUrl(); } }; } } } } return null; } public PreCompressedResourceSupplier addEncoding(String encoding, String extension) { encodingMap.put(encoding, extension); return this; } public PreCompressedResourceSupplier removeEncoding(String encoding) { encodingMap.remove(encoding); return this; } } RangeAwareResource.java000066400000000000000000000030221420065311100335760ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/resource/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.resource; import io.undertow.io.IoCallback; import io.undertow.io.Sender; import io.undertow.server.HttpServerExchange; /** * A resource implementation that * * @author Stuart Douglas */ public interface RangeAwareResource extends Resource { /** * Serve the resource, and call the provided callback when complete. * * @param sender The sender to use. * @param exchange The exchange */ void serveRange(final Sender sender, final HttpServerExchange exchange, long start, long end, final IoCallback completionCallback); /** * It is possible that some resources managers may only support range requests on a subset of their resources, * * @return true if this resource supports range requests */ boolean isRangeSupported(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/resource/Resource.java000066400000000000000000000073061420065311100317310ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.resource; import java.io.File; import java.net.URL; import java.nio.file.Path; import java.util.Date; import java.util.List; import io.undertow.io.IoCallback; import io.undertow.io.Sender; import io.undertow.server.HttpServerExchange; import io.undertow.util.ETag; import io.undertow.util.MimeMappings; /** * Representation of a static resource. * * @author Stuart Douglas */ public interface Resource { /** * * @return The path from the resource manager root */ String getPath(); /** * @return The last modified date of this resource, or null if this cannot be determined */ Date getLastModified(); /** * @return A string representation of the last modified date, or null if this cannot be determined */ String getLastModifiedString(); /** * @return The resources etags */ ETag getETag(); /** * @return The name of the resource */ String getName(); /** * @return true if this resource represents a directory */ boolean isDirectory(); /** * @return a list of resources in this directory */ List list(); /** * Return the resources content type. In most cases this will simply use the provided * mime mappings, however in some cases the resource may have additional information as * to the actual content type. */ String getContentType(final MimeMappings mimeMappings); /** * Serve the resource, and call the provided callback when complete. * * @param sender The sender to use. * @param exchange The exchange */ void serve(final Sender sender, final HttpServerExchange exchange, final IoCallback completionCallback); /** * @return The content length, or null if it is unknown */ Long getContentLength(); /** * @return A string that uniquely identifies this resource */ String getCacheKey(); /** * @return The underlying file that matches the resource. This may return null if the resource does not map to a file */ File getFile(); /** * @return The underlying file that matches the resource. This may return null if the resource does not map to a file */ Path getFilePath(); /** * Returns the resource manager root. If the resource manager has multiple roots then this returns the one that * is the parent of this resource. * * @return a file representing the resource manager root. This may return null if the resource does not map to a file */ File getResourceManagerRoot(); /** * Returns the resource manager root. If the resource manager has multiple roots then this returns the one that * is the parent of this resource. * * @return a path representing the resource manager root. This may return null if the resource does not map to a file */ Path getResourceManagerRootPath(); /** * @return The URL of the resource */ URL getUrl(); } ResourceChangeEvent.java000066400000000000000000000030511420065311100337530ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/resource/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.resource; /** * An event that is fired when a resource is modified * * @author Stuart Douglas */ public class ResourceChangeEvent { private final String resource; private final Type type; public ResourceChangeEvent(String resource, Type type) { this.resource = resource; this.type = type; } public String getResource() { return resource; } public Type getType() { return type; } /** * Watched file event types. More may be added in the future. */ public enum Type { /** * A file was added in a directory. */ ADDED, /** * A file was removed from a directory. */ REMOVED, /** * A file was modified in a directory. */ MODIFIED, } } ResourceChangeListener.java000066400000000000000000000021321420065311100344560ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/resource/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.resource; import java.util.Collection; /** * Listener that is invoked when a resource changes. * * @author Stuart Douglas */ public interface ResourceChangeListener { /** * callback that is invoked when resources change. * @param changes The collection of changes */ void handleChanges(final Collection changes); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/resource/ResourceHandler.java000066400000000000000000000512071420065311100332260ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.resource; import java.io.File; import java.io.IOException; import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import io.undertow.UndertowLogger; import io.undertow.io.IoCallback; import io.undertow.predicate.Predicate; import io.undertow.predicate.Predicates; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.ResponseCodeHandler; import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.server.handlers.cache.ResponseCache; import io.undertow.server.handlers.encoding.ContentEncodedResource; import io.undertow.server.handlers.encoding.ContentEncodedResourceManager; import io.undertow.util.ByteRange; import io.undertow.util.CanonicalPathUtils; import io.undertow.util.DateUtils; import io.undertow.util.ETag; import io.undertow.util.ETagUtils; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.Methods; import io.undertow.util.MimeMappings; import io.undertow.util.RedirectBuilder; import io.undertow.util.StatusCodes; /** * @author Stuart Douglas */ public class ResourceHandler implements HttpHandler { /** * Set of methods prescribed by HTTP 1.1. If request method is not one of those, handler will * return NOT_IMPLEMENTED. */ private static final Set KNOWN_METHODS = new HashSet<>(); static { KNOWN_METHODS.add(Methods.OPTIONS); KNOWN_METHODS.add(Methods.GET); KNOWN_METHODS.add(Methods.HEAD); KNOWN_METHODS.add(Methods.POST); KNOWN_METHODS.add(Methods.PUT); KNOWN_METHODS.add(Methods.DELETE); KNOWN_METHODS.add(Methods.TRACE); KNOWN_METHODS.add(Methods.CONNECT); } private final List welcomeFiles = new CopyOnWriteArrayList<>(new String[]{"index.html", "index.htm", "default.html", "default.htm"}); /** * If directory listing is enabled. */ private volatile boolean directoryListingEnabled = false; /** * If the canonical version of paths should be passed into the resource manager. */ private volatile boolean canonicalizePaths = true; /** * The mime mappings that are used to determine the content type. */ private volatile MimeMappings mimeMappings = MimeMappings.DEFAULT; private volatile Predicate cachable = Predicates.truePredicate(); private volatile Predicate allowed = Predicates.truePredicate(); private volatile ResourceSupplier resourceSupplier; private volatile ResourceManager resourceManager; /** * If this is set this will be the maximum time (in seconds) the client will cache the resource. *

* Note: Do not set this for private resources, as it will cause a Cache-Control: public * to be sent. *

* TODO: make this more flexible *

* This will only be used if the {@link #cachable} predicate returns true */ private volatile Integer cacheTime; private volatile ContentEncodedResourceManager contentEncodedResourceManager; /** * Handler that is called if no resource is found */ private final HttpHandler next; public ResourceHandler(ResourceManager resourceSupplier) { this(resourceSupplier, ResponseCodeHandler.HANDLE_404); } public ResourceHandler(ResourceManager resourceManager, HttpHandler next) { this.resourceSupplier = new DefaultResourceSupplier(resourceManager); this.resourceManager = resourceManager; this.next = next; } public ResourceHandler(ResourceSupplier resourceSupplier) { this(resourceSupplier, ResponseCodeHandler.HANDLE_404); } public ResourceHandler(ResourceSupplier resourceManager, HttpHandler next) { this.resourceSupplier = resourceManager; this.next = next; } /** * You should use {@link ResourceHandler(ResourceManager)} instead. */ @Deprecated public ResourceHandler() { this.next = ResponseCodeHandler.HANDLE_404; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { if (exchange.getRequestMethod().equals(Methods.GET) || exchange.getRequestMethod().equals(Methods.POST)) { serveResource(exchange, true); } else if (exchange.getRequestMethod().equals(Methods.HEAD)) { serveResource(exchange, false); } else { if (KNOWN_METHODS.contains(exchange.getRequestMethod())) { exchange.setStatusCode(StatusCodes.METHOD_NOT_ALLOWED); exchange.getResponseHeaders().add(Headers.ALLOW, String.join(", ", Methods.GET_STRING, Methods.HEAD_STRING, Methods.POST_STRING)); } else { exchange.setStatusCode(StatusCodes.NOT_IMPLEMENTED); } exchange.endExchange(); } } private void serveResource(final HttpServerExchange exchange, final boolean sendContent) throws Exception { if (DirectoryUtils.sendRequestedBlobs(exchange)) { return; } if (!allowed.resolve(exchange)) { exchange.setStatusCode(StatusCodes.FORBIDDEN); exchange.endExchange(); return; } ResponseCache cache = exchange.getAttachment(ResponseCache.ATTACHMENT_KEY); final boolean cachable = this.cachable.resolve(exchange); //we set caching headers before we try and serve from the cache if (cachable && cacheTime != null) { exchange.getResponseHeaders().put(Headers.CACHE_CONTROL, "public, max-age=" + cacheTime); long date = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(cacheTime); String dateHeader = DateUtils.toDateString(new Date(date)); exchange.getResponseHeaders().put(Headers.EXPIRES, dateHeader); } if (cache != null && cachable) { if (cache.tryServeResponse()) { return; } } //we now dispatch to a worker thread //as resource manager methods are potentially blocking HttpHandler dispatchTask = new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { Resource resource = null; try { if (File.separatorChar == '/' || !exchange.getRelativePath().contains(File.separator)) { //we don't process resources that contain the sperator character if this is not / //this prevents attacks where people use windows path seperators in file URLS's resource = resourceSupplier.getResource(exchange, canonicalize(exchange.getRelativePath())); } } catch (IOException e) { clearCacheHeaders(exchange); UndertowLogger.REQUEST_IO_LOGGER.ioException(e); exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); exchange.endExchange(); return; } if (resource == null) { clearCacheHeaders(exchange); //usually a 404 handler next.handleRequest(exchange); return; } if (resource.isDirectory()) { Resource indexResource; try { indexResource = getIndexFiles(exchange, resourceSupplier, resource.getPath(), welcomeFiles); } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); exchange.endExchange(); return; } if (indexResource == null) { if (directoryListingEnabled) { DirectoryUtils.renderDirectoryListing(exchange, resource); return; } else { exchange.setStatusCode(StatusCodes.FORBIDDEN); exchange.endExchange(); return; } } else if (!exchange.getRequestPath().endsWith("/")) { exchange.setStatusCode(StatusCodes.FOUND); exchange.getResponseHeaders().put(Headers.LOCATION, RedirectBuilder.redirect(exchange, exchange.getRelativePath() + "/", true)); exchange.endExchange(); return; } resource = indexResource; } else if(exchange.getRelativePath().endsWith("/")) { //UNDERTOW-432 exchange.setStatusCode(StatusCodes.NOT_FOUND); exchange.endExchange(); return; } final ETag etag = resource.getETag(); final Date lastModified = resource.getLastModified(); if (!ETagUtils.handleIfMatch(exchange, etag, false) || !DateUtils.handleIfUnmodifiedSince(exchange, lastModified)) { exchange.setStatusCode(StatusCodes.PRECONDITION_FAILED); exchange.endExchange(); return; } if (!ETagUtils.handleIfNoneMatch(exchange, etag, true) || !DateUtils.handleIfModifiedSince(exchange, lastModified)) { exchange.setStatusCode(StatusCodes.NOT_MODIFIED); exchange.endExchange(); return; } final ContentEncodedResourceManager contentEncodedResourceManager = ResourceHandler.this.contentEncodedResourceManager; Long contentLength = resource.getContentLength(); if (contentLength != null && !exchange.getResponseHeaders().contains(Headers.TRANSFER_ENCODING)) { exchange.setResponseContentLength(contentLength); } ByteRange.RangeResponseResult rangeResponse = null; long start = -1, end = -1; if(resource instanceof RangeAwareResource && ((RangeAwareResource)resource).isRangeSupported() && contentLength != null && contentEncodedResourceManager == null) { exchange.getResponseHeaders().put(Headers.ACCEPT_RANGES, "bytes"); //TODO: figure out what to do with the content encoded resource manager ByteRange range = ByteRange.parse(exchange.getRequestHeaders().getFirst(Headers.RANGE)); if(range != null && range.getRanges() == 1 && resource.getContentLength() != null) { rangeResponse = range.getResponseResult(resource.getContentLength(), exchange.getRequestHeaders().getFirst(Headers.IF_RANGE), resource.getLastModified(), resource.getETag() == null ? null : resource.getETag().getTag()); if(rangeResponse != null){ start = rangeResponse.getStart(); end = rangeResponse.getEnd(); exchange.setStatusCode(rangeResponse.getStatusCode()); exchange.getResponseHeaders().put(Headers.CONTENT_RANGE, rangeResponse.getContentRange()); long length = rangeResponse.getContentLength(); exchange.setResponseContentLength(length); if(rangeResponse.getStatusCode() == StatusCodes.REQUEST_RANGE_NOT_SATISFIABLE) { return; } } } } //we are going to proceed. Set the appropriate headers if (!exchange.getResponseHeaders().contains(Headers.CONTENT_TYPE)) { final String contentType = resource.getContentType(mimeMappings); if (contentType != null) { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, contentType); } else { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/octet-stream"); } } if (lastModified != null) { exchange.getResponseHeaders().put(Headers.LAST_MODIFIED, resource.getLastModifiedString()); } if (etag != null) { exchange.getResponseHeaders().put(Headers.ETAG, etag.toString()); } if (contentEncodedResourceManager != null) { try { ContentEncodedResource encoded = contentEncodedResourceManager.getResource(resource, exchange); if (encoded != null) { exchange.getResponseHeaders().put(Headers.CONTENT_ENCODING, encoded.getContentEncoding()); exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, encoded.getResource().getContentLength()); encoded.getResource().serve(exchange.getResponseSender(), exchange, IoCallback.END_EXCHANGE); return; } } catch (IOException e) { //TODO: should this be fatal UndertowLogger.REQUEST_IO_LOGGER.ioException(e); exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); exchange.endExchange(); return; } } if (!sendContent) { exchange.endExchange(); } else if(rangeResponse != null) { ((RangeAwareResource)resource).serveRange(exchange.getResponseSender(), exchange, start, end, IoCallback.END_EXCHANGE); } else { resource.serve(exchange.getResponseSender(), exchange, IoCallback.END_EXCHANGE); } } }; if(exchange.isInIoThread()) { exchange.dispatch(dispatchTask); } else { dispatchTask.handleRequest(exchange); } } private void clearCacheHeaders(HttpServerExchange exchange) { exchange.getResponseHeaders().remove(Headers.CACHE_CONTROL); exchange.getResponseHeaders().remove(Headers.EXPIRES); } private Resource getIndexFiles(HttpServerExchange exchange, ResourceSupplier resourceManager, final String base, List possible) throws IOException { String realBase; if (base.endsWith("/")) { realBase = base; } else { realBase = base + "/"; } for (String possibility : possible) { Resource index = resourceManager.getResource(exchange, canonicalize(realBase + possibility)); if (index != null) { return index; } } return null; } private String canonicalize(String s) { if(canonicalizePaths) { return CanonicalPathUtils.canonicalize(s); } return s; } public boolean isDirectoryListingEnabled() { return directoryListingEnabled; } public ResourceHandler setDirectoryListingEnabled(final boolean directoryListingEnabled) { this.directoryListingEnabled = directoryListingEnabled; return this; } public ResourceHandler addWelcomeFiles(String... files) { this.welcomeFiles.addAll(Arrays.asList(files)); return this; } public ResourceHandler setWelcomeFiles(String... files) { this.welcomeFiles.clear(); this.welcomeFiles.addAll(Arrays.asList(files)); return this; } public MimeMappings getMimeMappings() { return mimeMappings; } public ResourceHandler setMimeMappings(final MimeMappings mimeMappings) { this.mimeMappings = mimeMappings; return this; } public Predicate getCachable() { return cachable; } public ResourceHandler setCachable(final Predicate cachable) { this.cachable = cachable; return this; } public Predicate getAllowed() { return allowed; } public ResourceHandler setAllowed(final Predicate allowed) { this.allowed = allowed; return this; } public ResourceSupplier getResourceSupplier() { return resourceSupplier; } public ResourceHandler setResourceSupplier(final ResourceSupplier resourceSupplier) { this.resourceSupplier = resourceSupplier; this.resourceManager = null; return this; } public ResourceManager getResourceManager() { return resourceManager; } public ResourceHandler setResourceManager(final ResourceManager resourceManager) { this.resourceManager = resourceManager; this.resourceSupplier = new DefaultResourceSupplier(resourceManager); return this; } public Integer getCacheTime() { return cacheTime; } public ResourceHandler setCacheTime(final Integer cacheTime) { this.cacheTime = cacheTime; return this; } public ContentEncodedResourceManager getContentEncodedResourceManager() { return contentEncodedResourceManager; } public ResourceHandler setContentEncodedResourceManager(ContentEncodedResourceManager contentEncodedResourceManager) { this.contentEncodedResourceManager = contentEncodedResourceManager; return this; } public boolean isCanonicalizePaths() { return canonicalizePaths; } /** * If this handler should use canonicalized paths. * * WARNING: If this is not true and {@link io.undertow.server.handlers.CanonicalPathHandler} is not installed in * the handler chain then is may be possible to perform a directory traversal attack. If you set this to false make * sure you have some kind of check in place to control the path. * @param canonicalizePaths If paths should be canonicalized */ public void setCanonicalizePaths(boolean canonicalizePaths) { this.canonicalizePaths = canonicalizePaths; } public static class Builder implements HandlerBuilder { @Override public String name() { return "resource"; } @Override public Map> parameters() { Map> params = new HashMap<>(); params.put("location", String.class); params.put("allow-listing", boolean.class); return params; } @Override public Set requiredParameters() { return Collections.singleton("location"); } @Override public String defaultParameter() { return "location"; } @Override public HandlerWrapper build(Map config) { return new Wrapper((String)config.get("location"), (Boolean) config.get("allow-listing")); } } private static class Wrapper implements HandlerWrapper { private final String location; private final boolean allowDirectoryListing; private Wrapper(String location, boolean allowDirectoryListing) { this.location = location; this.allowDirectoryListing = allowDirectoryListing; } @Override public HttpHandler wrap(HttpHandler handler) { ResourceManager rm = new PathResourceManager(Paths.get(location), 1024); ResourceHandler resourceHandler = new ResourceHandler(rm); resourceHandler.setDirectoryListingEnabled(allowDirectoryListing); return resourceHandler; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/resource/ResourceManager.java000066400000000000000000000053711420065311100332240ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.resource; import io.undertow.UndertowMessages; import java.io.Closeable; import java.io.IOException; /** * * Representation of a resource manager. A resource manager knows how to obtain * a resource for a given path. * * @author Stuart Douglas */ public interface ResourceManager extends Closeable { /** * Returns a resource for the given path. * * It is the responsibility of the called to make sure that the path in Canonicalised. * * @param path The path * @return The resource representing the path, or null if no resource was found. */ Resource getResource(final String path) throws IOException; /** * * @return true if a resource change listener is supported */ boolean isResourceChangeListenerSupported(); /** * Registers a resource change listener, if the underlying resource manager support it * @param listener The listener to register * @throws IllegalArgumentException If resource change listeners are not supported */ void registerResourceChangeListener(final ResourceChangeListener listener); /** * Removes a resource change listener * @param listener */ void removeResourceChangeListener(final ResourceChangeListener listener); ResourceManager EMPTY_RESOURCE_MANAGER = new ResourceManager() { @Override public Resource getResource(final String path){ return null; } @Override public boolean isResourceChangeListenerSupported() { return false; } @Override public void registerResourceChangeListener(ResourceChangeListener listener) { throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported(); } @Override public void removeResourceChangeListener(ResourceChangeListener listener) { throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported(); } @Override public void close() throws IOException { } }; } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/resource/ResourceSupplier.java000066400000000000000000000024741420065311100334560ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.resource; import java.io.IOException; import io.undertow.server.HttpServerExchange; /** * Interface that allows for more flexibility when resolving a resource than is currently provided * by {@link ResourceManager}. * * @author Stuart Douglas */ public interface ResourceSupplier { /** * * @param exchange The current exchange * @param path The path to resolve * @return A resource to serve * @throws IOException if an error ocured resolving the resource */ Resource getResource(HttpServerExchange exchange, String path) throws IOException; } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/resource/SecurityActions.java000066400000000000000000000024701420065311100332670ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.resource; import java.nio.file.Files; import java.nio.file.Path; import java.security.AccessController; import java.security.PrivilegedAction; class SecurityActions { static Boolean isSymbolicLink(Path file) { if (System.getSecurityManager() == null) { return Files.isSymbolicLink(file); } else { return AccessController.doPrivileged(new PrivilegedAction() { @Override public Boolean run() { return Files.isSymbolicLink(file); } }); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/resource/URLResource.java000066400000000000000000000237141420065311100323150ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.resource; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.JarURLConnection; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.nio.ByteBuffer; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Date; import java.util.LinkedList; import java.util.List; import org.xnio.IoUtils; import io.undertow.UndertowLogger; import io.undertow.io.IoCallback; import io.undertow.io.Sender; import io.undertow.server.HttpServerExchange; import io.undertow.util.DateUtils; import io.undertow.util.ETag; import io.undertow.util.MimeMappings; import io.undertow.util.StatusCodes; /** * @author Stuart Douglas */ public class URLResource implements Resource, RangeAwareResource { private final URL url; private final String path; private boolean connectionOpened = false; private Date lastModified; private Long contentLength; @Deprecated public URLResource(final URL url, URLConnection connection, String path) { this(url, path); } public URLResource(final URL url, String path) { this.url = url; this.path = path; } @Override public String getPath() { return path; } @Override public Date getLastModified() { openConnection(); return lastModified; } private void openConnection() { if (!connectionOpened) { connectionOpened = true; URLConnection connection = null; try { try { connection = url.openConnection(); } catch (IOException e) { lastModified = null; contentLength = null; return; } if (url.getProtocol().equals("jar")) { connection.setUseCaches(false); URL jar = ((JarURLConnection) connection).getJarFileURL(); lastModified = new Date(new File(jar.getFile()).lastModified()); } else { lastModified = new Date(connection.getLastModified()); } contentLength = connection.getContentLengthLong(); } finally { if (connection != null) { try { IoUtils.safeClose(connection.getInputStream()); } catch (IOException e) { //ignore } } } } } @Override public String getLastModifiedString() { return DateUtils.toDateString(getLastModified()); } @Override public ETag getETag() { return null; } @Override public String getName() { String path = url.getPath(); if (path.endsWith("/")) { path = path.substring(0, path.length() - 1); } int sepIndex = path.lastIndexOf("/"); if (sepIndex != -1) { path = path.substring(sepIndex + 1); } return path; } @Override public boolean isDirectory() { Path file = getFilePath(); if (file != null) { return Files.isDirectory(file); } else if (url.getPath().endsWith("/")) { return true; } return false; } @Override public List list() { List result = new LinkedList<>(); Path file = getFilePath(); try { if (file != null) { try (DirectoryStream stream = Files.newDirectoryStream(file)) { for (Path child : stream) { result.add(new URLResource(child.toUri().toURL(), child.toString())); } } } } catch (IOException e) { throw new RuntimeException(e); } return result; } @Override public String getContentType(final MimeMappings mimeMappings) { final String fileName = getName(); int index = fileName.lastIndexOf('.'); if (index != -1 && index != fileName.length() - 1) { return mimeMappings.getMimeType(fileName.substring(index + 1)); } return null; } @Override public void serve(Sender sender, HttpServerExchange exchange, IoCallback completionCallback) { serveImpl(sender, exchange, -1, -1, false, completionCallback); } public void serveImpl(final Sender sender, final HttpServerExchange exchange, final long start, final long end, final boolean range, final IoCallback completionCallback) { class ServerTask implements Runnable, IoCallback { private InputStream inputStream; private byte[] buffer; long toSkip = start; long remaining = end - start + 1; @Override public void run() { if (range && remaining == 0) { //we are done, just return IoUtils.safeClose(inputStream); completionCallback.onComplete(exchange, sender); return; } if (inputStream == null) { try { inputStream = url.openStream(); } catch (IOException e) { exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); return; } buffer = new byte[1024];//TODO: we should be pooling these } try { int res = inputStream.read(buffer); if (res == -1) { //we are done, just return IoUtils.safeClose(inputStream); completionCallback.onComplete(exchange, sender); return; } int bufferStart = 0; int length = res; if (range && toSkip > 0) { //skip to the start of the requested range //not super efficient, but what can you do while (toSkip > res) { toSkip -= res; res = inputStream.read(buffer); if (res == -1) { //we are done, just return IoUtils.safeClose(inputStream); completionCallback.onComplete(exchange, sender); return; } } bufferStart = (int) toSkip; length -= toSkip; toSkip = 0; } if (range && length > remaining) { length = (int) remaining; } sender.send(ByteBuffer.wrap(buffer, bufferStart, length), this); } catch (IOException e) { onException(exchange, sender, e); } } @Override public void onComplete(final HttpServerExchange exchange, final Sender sender) { if (exchange.isInIoThread()) { exchange.dispatch(this); } else { run(); } } @Override public void onException(final HttpServerExchange exchange, final Sender sender, final IOException exception) { UndertowLogger.REQUEST_IO_LOGGER.ioException(exception); IoUtils.safeClose(inputStream); if (!exchange.isResponseStarted()) { exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); } completionCallback.onException(exchange, sender, exception); } } ServerTask serveTask = new ServerTask(); if (exchange.isInIoThread()) { exchange.dispatch(serveTask); } else { serveTask.run(); } } @Override public Long getContentLength() { openConnection(); return contentLength; } @Override public String getCacheKey() { return url.toString(); } @Override public File getFile() { Path path = getFilePath(); return path != null ? path.toFile() : null; } @Override public Path getFilePath() { if (url.getProtocol().equals("file")) { try { return Paths.get(url.toURI()); } catch (URISyntaxException e) { return null; } } return null; } @Override public File getResourceManagerRoot() { return null; } @Override public Path getResourceManagerRootPath() { return null; } @Override public URL getUrl() { return url; } @Override public void serveRange(Sender sender, HttpServerExchange exchange, long start, long end, IoCallback completionCallback) { serveImpl(sender, exchange, start, end, true, completionCallback); } @Override public boolean isRangeSupported() { return true; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/sse/000077500000000000000000000000001420065311100262345ustar00rootroot00000000000000ServerSentEventConnection.java000066400000000000000000000524011420065311100341440ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/sse/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.sse; import io.undertow.UndertowLogger; import io.undertow.connector.PooledByteBuffer; import io.undertow.security.api.SecurityContext; import io.undertow.security.idm.Account; import io.undertow.server.HttpServerExchange; import io.undertow.util.Attachable; import io.undertow.util.AttachmentKey; import io.undertow.util.AttachmentList; import io.undertow.util.HeaderMap; import org.xnio.ChannelExceptionHandler; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.XnioExecutor; import org.xnio.channels.StreamSinkChannel; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Channel; import java.nio.channels.ClosedChannelException; import java.nio.charset.StandardCharsets; import java.security.Principal; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; /** * Represents the server side of a Server Sent Events connection. * * The class implements Attachable, which provides access to the underlying exchanges attachments. * * @author Stuart Douglas */ public class ServerSentEventConnection implements Channel, Attachable { private final HttpServerExchange exchange; private final StreamSinkChannel sink; private final SseWriteListener writeListener = new SseWriteListener(); private PooledByteBuffer pooled; private final Deque queue = new ConcurrentLinkedDeque<>(); private final Queue buffered = new ConcurrentLinkedDeque<>(); /** * Messages that have been written to the channel but flush() has failed */ private final Queue flushingMessages = new ArrayDeque<>(); private final List> closeTasks = new CopyOnWriteArrayList<>(); private Map parameters; private Map properties = new HashMap<>(); private static final AtomicIntegerFieldUpdater openUpdater = AtomicIntegerFieldUpdater.newUpdater(ServerSentEventConnection.class, "open"); private volatile int open = 1; private volatile boolean shutdown = false; private volatile long keepAliveTime = -1; private XnioExecutor.Key timerKey; public ServerSentEventConnection(HttpServerExchange exchange, StreamSinkChannel sink) { this.exchange = exchange; this.sink = sink; this.sink.getCloseSetter().set(new ChannelListener() { @Override public void handleEvent(StreamSinkChannel channel) { if(timerKey != null) { timerKey.remove(); } for (ChannelListener listener : closeTasks) { ChannelListeners.invokeChannelListener(ServerSentEventConnection.this, listener); } IoUtils.safeClose(ServerSentEventConnection.this); } }); this.sink.getWriteSetter().set(writeListener); } /** * Adds a listener that will be invoked when the channel is closed * * @param listener The listener to invoke */ public synchronized void addCloseTask(ChannelListener listener) { this.closeTasks.add(listener); } /** * * @return The principal that was associated with the SSE request */ public Principal getPrincipal() { Account account = getAccount(); if (account != null) { return account.getPrincipal(); } return null; } /** * * @return The account that was associated with the SSE request */ public Account getAccount() { SecurityContext sc = exchange.getSecurityContext(); if (sc != null) { return sc.getAuthenticatedAccount(); } return null; } /** * * @return The request headers from the initial request that opened this connection */ public HeaderMap getRequestHeaders() { return exchange.getRequestHeaders(); } /** * * @return The response headers from the initial request that opened this connection */ public HeaderMap getResponseHeaders() { return exchange.getResponseHeaders(); } /** * * @return The request URI from the initial request that opened this connection */ public String getRequestURI() { return exchange.getRequestURI(); } /** * * @return the query parameters */ public Map> getQueryParameters() { return exchange.getQueryParameters(); } /** * * @return the query string */ public String getQueryString() { return exchange.getQueryString(); } /** * Sends an event to the remote client * * @param data The event data */ public void send(String data) { send(data, null, null, null); } /** * Sends an event to the remote client * * @param data The event data * @param callback A callback that is notified on Success or failure */ public void send(String data, EventCallback callback) { send(data, null, null, callback); } /** * Sends the 'retry' message to the client, instructing it how long to wait before attempting a reconnect. * * @param retry The retry time in milliseconds */ public void sendRetry(long retry) { sendRetry(retry, null); } /** * Sends the 'retry' message to the client, instructing it how long to wait before attempting a reconnect. * * @param retry The retry time in milliseconds * @param callback The callback that is notified on success or failure */ public synchronized void sendRetry(long retry, EventCallback callback) { if (open == 0 || shutdown) { if (callback != null) { callback.failed(this, null, null, null, new ClosedChannelException()); } return; } queue.add(new SSEData(retry, callback)); sink.getIoThread().execute(new Runnable() { @Override public void run() { synchronized (ServerSentEventConnection.this) { if (pooled == null) { fillBuffer(); writeListener.handleEvent(sink); } } } }); } /** * Sends an event to the remote client * * @param data The event data * @param event The event name * @param id The event ID * @param callback A callback that is notified on Success or failure */ public synchronized void send(String data, String event, String id, EventCallback callback) { if (open == 0 || shutdown) { if (callback != null) { callback.failed(this, data, event, id, new ClosedChannelException()); } return; } queue.add(new SSEData(event, data, id, callback)); sink.getIoThread().execute(new Runnable() { @Override public void run() { synchronized (ServerSentEventConnection.this) { if (pooled == null) { fillBuffer(); writeListener.handleEvent(sink); } } } }); } public String getParameter(String name) { if(parameters == null) { return null; } return parameters.get(name); } public void setParameter(String name, String value) { if(parameters == null) { parameters = new HashMap<>(); } parameters.put(name, value); } public Map getProperties() { return properties; } /** * * * @return The keep alive time */ public long getKeepAliveTime() { return keepAliveTime; } /** * Sets the keep alive time in milliseconds. If this is larger than zero a ':' message will be sent this often * (assuming there is no activity) to keep the connection alive. * * The spec recommends a value of 15000 (15 seconds). * * @param keepAliveTime The time in milliseconds between keep alive messaged */ public void setKeepAliveTime(long keepAliveTime) { this.keepAliveTime = keepAliveTime; if(this.timerKey != null) { this.timerKey.remove(); } this.timerKey = sink.getIoThread().executeAtInterval(new Runnable() { @Override public void run() { if(shutdown || open == 0) { if(timerKey != null) { timerKey.remove(); } return; } if(pooled == null) { pooled = exchange.getConnection().getByteBufferPool().allocate(); pooled.getBuffer().put(":\n".getBytes(StandardCharsets.UTF_8)); pooled.getBuffer().flip(); writeListener.handleEvent(sink); } } }, keepAliveTime, TimeUnit.MILLISECONDS); } private void fillBuffer() { if (queue.isEmpty()) { if(pooled != null) { pooled.close(); pooled = null; sink.suspendWrites(); } return; } if (pooled == null) { pooled = exchange.getConnection().getByteBufferPool().allocate(); } else { pooled.getBuffer().clear(); } ByteBuffer buffer = pooled.getBuffer(); while (!queue.isEmpty() && buffer.hasRemaining()) { SSEData data = queue.poll(); buffered.add(data); if (data.leftOverData == null) { StringBuilder message = new StringBuilder(); if(data.retry > 0) { message.append("retry:"); message.append(data.retry); message.append('\n'); } else { if (data.id != null) { message.append("id:"); message.append(data.id); message.append('\n'); } if (data.event != null) { message.append("event:"); message.append(data.event); message.append('\n'); } if (data.data != null) { message.append("data:"); for (int i = 0; i < data.data.length(); ++i) { char c = data.data.charAt(i); if (c == '\n') { message.append("\ndata:"); } else { message.append(c); } } message.append('\n'); } } message.append('\n'); byte[] messageBytes = message.toString().getBytes(StandardCharsets.UTF_8); if (messageBytes.length < buffer.remaining()) { buffer.put(messageBytes); data.endBufferPosition = buffer.position(); } else { queue.addFirst(data); int rem = buffer.remaining(); buffer.put(messageBytes, 0, rem); data.leftOverData = messageBytes; data.leftOverDataOffset = rem; } } else { int remainingData = data.leftOverData.length - data.leftOverDataOffset; if (remainingData > buffer.remaining()) { queue.addFirst(data); int toWrite = buffer.remaining(); buffer.put(data.leftOverData, data.leftOverDataOffset, toWrite); data.leftOverDataOffset += toWrite; } else { buffer.put(data.leftOverData, data.leftOverDataOffset, remainingData); data.endBufferPosition = buffer.position(); data.leftOverData = null; } } } buffer.flip(); sink.resumeWrites(); } /** * execute a graceful shutdown once all data has been sent */ public void shutdown() { if (open == 0 || shutdown) { return; } shutdown = true; sink.getIoThread().execute(new Runnable() { @Override public void run() { synchronized (ServerSentEventConnection.this) { if (queue.isEmpty() && pooled == null) { exchange.endExchange(); } } } }); } @Override public boolean isOpen() { return open != 0; } @Override public void close() throws IOException { close(new ClosedChannelException()); } private synchronized void close(IOException e) throws IOException { if (openUpdater.compareAndSet(this, 1, 0)) { if (pooled != null) { pooled.close(); pooled = null; } List cb = new ArrayList<>(buffered.size() + queue.size() + flushingMessages.size()); cb.addAll(buffered); cb.addAll(queue); cb.addAll(flushingMessages); queue.clear(); buffered.clear(); flushingMessages.clear(); for (SSEData i : cb) { if (i.callback != null) { try { i.callback.failed(this, i.data, i.event, i.id, e); } catch (Exception ex) { UndertowLogger.REQUEST_LOGGER.failedToInvokeFailedCallback(i.callback, ex); } } } sink.shutdownWrites(); if(!sink.flush()) { sink.getWriteSetter().set(ChannelListeners.flushingChannelListener(null, new ChannelExceptionHandler() { @Override public void handleException(StreamSinkChannel channel, IOException exception) { IoUtils.safeClose(sink); } })); sink.resumeWrites(); } } } @Override public T getAttachment(AttachmentKey key) { return exchange.getAttachment(key); } @Override public List getAttachmentList(AttachmentKey> key) { return exchange.getAttachmentList(key); } @Override public T putAttachment(AttachmentKey key, T value) { return exchange.putAttachment(key, value); } @Override public T removeAttachment(AttachmentKey key) { return exchange.removeAttachment(key); } @Override public void addToAttachmentList(AttachmentKey> key, T value) { exchange.addToAttachmentList(key, value); } public interface EventCallback { /** * Notification that is called when a message is sucessfully sent * * @param connection The connection * @param data The message data * @param event The message event * @param id The message id */ void done(ServerSentEventConnection connection, String data, String event, String id); /** * Notification that is called when a message send fails. * * @param connection The connection * @param data The message data * @param event The message event * @param id The message id * @param e The exception */ void failed(ServerSentEventConnection connection, String data, String event, String id, IOException e); } private static class SSEData { final String event; final String data; final String id; final long retry; final EventCallback callback; private int endBufferPosition = -1; private byte[] leftOverData; private int leftOverDataOffset; private SSEData(String event, String data, String id, EventCallback callback) { this.event = event; this.data = data; this.id = id; this.callback = callback; this.retry = -1; } private SSEData(long retry, EventCallback callback) { this.event = null; this.data = null; this.id = null; this.callback = callback; this.retry = retry; } } private class SseWriteListener implements ChannelListener { @Override public void handleEvent(StreamSinkChannel channel) { synchronized (ServerSentEventConnection.this) { try { if (!flushingMessages.isEmpty()) { if (!channel.flush()) { return; } for (SSEData data : flushingMessages) { if (data.callback != null && data.leftOverData == null) { data.callback.done(ServerSentEventConnection.this, data.data, data.event, data.id); } } flushingMessages.clear(); ByteBuffer buffer = pooled.getBuffer(); if (!buffer.hasRemaining()) { fillBuffer(); if (pooled == null) { if (channel.flush()) { channel.suspendWrites(); } return; } } } else if (pooled == null) { if (channel.flush()) { channel.suspendWrites(); } return; } ByteBuffer buffer = pooled.getBuffer(); int res; do { res = channel.write(buffer); boolean flushed = channel.flush(); while (!buffered.isEmpty()) { //figure out which messages are complete SSEData data = buffered.peek(); if (data.endBufferPosition > 0 && buffer.position() >= data.endBufferPosition) { buffered.poll(); if (flushed) { if (data.callback != null && data.leftOverData == null) { data.callback.done(ServerSentEventConnection.this, data.data, data.event, data.id); } } else { //if flush was unsuccessful we defer the callback invocation, till it is actually on the wire flushingMessages.add(data); } } else { if (data.endBufferPosition <= 0) { buffered.poll(); } break; } } if (!flushed && !flushingMessages.isEmpty()) { sink.resumeWrites(); return; } if (!buffer.hasRemaining()) { fillBuffer(); if (pooled == null) { return; } } else if (res == 0) { sink.resumeWrites(); return; } } while (res > 0); } catch (IOException e) { handleException(e); } } } } private void handleException(IOException e) { IoUtils.safeClose(this, sink, exchange.getConnection()); } } ServerSentEventConnectionCallback.java000066400000000000000000000017541420065311100355660ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/sse/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.sse; /** * Callback handler that is invoked when a client connects to a SSE endpoint * * @author Stuart Douglas */ public interface ServerSentEventConnectionCallback { void connected(ServerSentEventConnection connection, String lastEventId); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/handlers/sse/ServerSentEventHandler.java000066400000000000000000000102051420065311100334750ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.sse; import io.undertow.UndertowLogger; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.PathTemplateMatch; import org.xnio.ChannelExceptionHandler; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.channels.StreamSinkChannel; import java.io.IOException; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * @author Stuart Douglas */ public class ServerSentEventHandler implements HttpHandler { private static final HttpString LAST_EVENT_ID = new HttpString("Last-Event-ID"); private final ServerSentEventConnectionCallback callback; private final Set connections = Collections.newSetFromMap(new ConcurrentHashMap()); public ServerSentEventHandler(ServerSentEventConnectionCallback callback) { this.callback = callback; } public ServerSentEventHandler() { this.callback = null; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/event-stream; charset=UTF-8"); exchange.setPersistent(false); final StreamSinkChannel sink = exchange.getResponseChannel(); if(!sink.flush()) { sink.getWriteSetter().set(ChannelListeners.flushingChannelListener(new ChannelListener() { @Override public void handleEvent(StreamSinkChannel channel) { handleConnect(channel, exchange); } }, new ChannelExceptionHandler() { @Override public void handleException(StreamSinkChannel channel, IOException exception) { IoUtils.safeClose(exchange.getConnection()); } })); sink.resumeWrites(); } else { exchange.dispatch(exchange.getIoThread(), new Runnable() { @Override public void run() { handleConnect(sink, exchange); } }); } } private void handleConnect(StreamSinkChannel channel, HttpServerExchange exchange) { UndertowLogger.REQUEST_LOGGER.debugf("Opened SSE connection to %s", exchange); final ServerSentEventConnection connection = new ServerSentEventConnection(exchange, channel); PathTemplateMatch pt = exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY); if(pt != null) { for(Map.Entry p : pt.getParameters().entrySet()) { connection.setParameter(p.getKey(), p.getValue()); } } connections.add(connection); connection.addCloseTask(new ChannelListener() { @Override public void handleEvent(ServerSentEventConnection channel) { connections.remove(connection); } }); if(callback != null) { callback.connected(connection, exchange.getRequestHeaders().getLast(LAST_EVENT_ID)); } } public Set getConnections() { return Collections.unmodifiableSet(connections); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/000077500000000000000000000000001420065311100255035ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/ParseTimeoutUpdater.java000066400000000000000000000133711420065311100323210ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol; import io.undertow.UndertowLogger; import io.undertow.server.ServerConnection; import io.undertow.util.WorkerUtils; import org.xnio.IoUtils; import org.xnio.XnioExecutor; import org.xnio.channels.ConnectedChannel; import java.io.Closeable; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; /** * Wrapper for parse timeout. * * @author Sebastian Laskawiec * @see io.undertow.UndertowOptions#REQUEST_PARSE_TIMEOUT */ public final class ParseTimeoutUpdater implements Runnable, ServerConnection.CloseListener, Closeable { private final ConnectedChannel connection; private final long requestParseTimeout; private final long requestIdleTimeout; private volatile XnioExecutor.Key handle; private volatile long expireTime = -1; private volatile boolean parsing = false; //we add 50ms to the timeout to make sure the underlying channel has actually timed out private static final int FUZZ_FACTOR = 50; private final Runnable closeTask; /** * Creates new instance of ParseTimeoutSourceConduit. * @param channel Channel which will be closed in case of timeout. * @param requestParseTimeout Timeout value. Negative value will indicate that this updated is disabled. * @param requestIdleTimeout */ public ParseTimeoutUpdater(ConnectedChannel channel, long requestParseTimeout, long requestIdleTimeout) { this(channel, requestParseTimeout, requestIdleTimeout, new Runnable() { @Override public void run() { IoUtils.safeClose(channel); } }); } /** * Creates new instance of ParseTimeoutSourceConduit. * @param channel Channel which will be closed in case of timeout. * @param requestParseTimeout Timeout value. Negative value will indicate that this updated is disabled. * @param requestIdleTimeout */ public ParseTimeoutUpdater(ConnectedChannel channel, long requestParseTimeout, long requestIdleTimeout, Runnable closeTask) { this.connection = channel; this.requestParseTimeout = requestParseTimeout; this.requestIdleTimeout = requestIdleTimeout; this.closeTask = closeTask; } /** * Called when the connection goes idle */ public void connectionIdle() { parsing = false; handleSchedule(requestIdleTimeout); } private void handleSchedule(long timeout) { //no current timeout, clear the expire time if(timeout == -1) { this.expireTime = -1; return; } //calculate the new expire time long newExpireTime = System.currentTimeMillis() + timeout; long oldExpireTime = this.expireTime; this.expireTime = newExpireTime; //if the new one is less than the current one we need to schedule a new timer, so cancel the old one if(newExpireTime < oldExpireTime) { if(handle != null) { handle.remove(); handle = null; } } if(handle == null) { try { handle = WorkerUtils.executeAfter(connection.getIoThread(), this, timeout + FUZZ_FACTOR, TimeUnit.MILLISECONDS); } catch (RejectedExecutionException e) { UndertowLogger.REQUEST_LOGGER.debug("Failed to schedule parse timeout, server is probably shutting down", e); } } } /** * Called when a request is received, however it is not parsed in a single read() call. This starts a timer, * and if the request is not parsed within this time then the connection is closed. * */ public void failedParse() { if(!parsing) { parsing = true; handleSchedule(requestParseTimeout); } } /** * Cancels timeout countdown. *

* Should be called after parsing is complete (to avoid closing connection during other activities). *

*/ public void requestStarted() { expireTime = -1; parsing = false; } @Override public void run() { if(!connection.isOpen()) { return; } handle = null; if (expireTime > 0) { //timeout is not active long now = System.currentTimeMillis(); if(expireTime > now) { handle = WorkerUtils.executeAfter(connection.getIoThread(), this, (expireTime - now) + FUZZ_FACTOR, TimeUnit.MILLISECONDS); } else { if(parsing) { UndertowLogger.REQUEST_LOGGER.parseRequestTimedOut(connection.getPeerAddress()); } else { UndertowLogger.REQUEST_LOGGER.debugf("Timing out idle connection from %s", connection.getPeerAddress()); } closeTask.run(); } } } @Override public void closed(ServerConnection connection) { close(); } public void close() { if(handle != null) { handle.remove(); handle = null; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/ajp/000077500000000000000000000000001420065311100262555ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/ajp/AjpOpenListener.java000066400000000000000000000210521420065311100321620ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.ajp; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.UndertowOptions; import io.undertow.conduits.BytesReceivedStreamSourceConduit; import io.undertow.conduits.BytesSentStreamSinkConduit; import io.undertow.conduits.IdleTimeoutConduit; import io.undertow.conduits.ReadTimeoutStreamSourceConduit; import io.undertow.conduits.WriteTimeoutStreamSinkConduit; import io.undertow.server.ConnectorStatistics; import io.undertow.server.ConnectorStatisticsImpl; import io.undertow.server.HttpHandler; import io.undertow.server.OpenListener; import io.undertow.server.ServerConnection; import io.undertow.server.XnioByteBufferPool; import org.xnio.IoUtils; import org.xnio.OptionMap; import org.xnio.Options; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import org.xnio.Pool; import org.xnio.StreamConnection; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import static io.undertow.UndertowOptions.DECODE_URL; import static io.undertow.UndertowOptions.URL_CHARSET; /** * @author Stuart Douglas */ public class AjpOpenListener implements OpenListener { private static final String DEFAULT_AJP_ALLOWED_REQUEST_ATTRIBUTES_PATTERN = SecurityActions.getSystemProperty("io.undertow.ajp.allowedRequestAttributesPattern"); private final Set connections = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final ByteBufferPool bufferPool; private final int bufferSize; private volatile String scheme; private volatile HttpHandler rootHandler; private volatile OptionMap undertowOptions; private volatile AjpRequestParser parser; private volatile boolean statisticsEnabled; private final ConnectorStatisticsImpl connectorStatistics; private final ServerConnection.CloseListener closeListener = new ServerConnection.CloseListener() { @Override public void closed(ServerConnection connection) { connectorStatistics.decrementConnectionCount(); } }; public AjpOpenListener(final Pool pool) { this(pool, OptionMap.EMPTY); } public AjpOpenListener(final Pool pool, final OptionMap undertowOptions) { this(new XnioByteBufferPool(pool), undertowOptions); } public AjpOpenListener(final ByteBufferPool pool) { this(pool, OptionMap.EMPTY); } public AjpOpenListener(final ByteBufferPool pool, final OptionMap undertowOptions) { this.undertowOptions = undertowOptions; this.bufferPool = pool; PooledByteBuffer buf = pool.allocate(); this.bufferSize = buf.getBuffer().remaining(); buf.close(); parser = new AjpRequestParser(undertowOptions.get(URL_CHARSET, StandardCharsets.UTF_8.name()), undertowOptions.get(DECODE_URL, true), undertowOptions.get(UndertowOptions.MAX_PARAMETERS, UndertowOptions.DEFAULT_MAX_PARAMETERS), undertowOptions.get(UndertowOptions.MAX_HEADERS, UndertowOptions.DEFAULT_MAX_HEADERS), undertowOptions.get(UndertowOptions.ALLOW_ENCODED_SLASH, false), undertowOptions.get(UndertowOptions.ALLOW_UNESCAPED_CHARACTERS_IN_URL, false), undertowOptions.get(UndertowOptions.AJP_ALLOWED_REQUEST_ATTRIBUTES_PATTERN, DEFAULT_AJP_ALLOWED_REQUEST_ATTRIBUTES_PATTERN)); connectorStatistics = new ConnectorStatisticsImpl(); statisticsEnabled = undertowOptions.get(UndertowOptions.ENABLE_CONNECTOR_STATISTICS, false); } @Override public void handleEvent(final StreamConnection channel) { if (UndertowLogger.REQUEST_LOGGER.isTraceEnabled()) { UndertowLogger.REQUEST_LOGGER.tracef("Opened connection with %s", channel.getPeerAddress()); } //set read and write timeouts try { Integer readTimeout = channel.getOption(Options.READ_TIMEOUT); Integer idle = undertowOptions.get(UndertowOptions.IDLE_TIMEOUT); if(idle != null) { IdleTimeoutConduit conduit = new IdleTimeoutConduit(channel); channel.getSourceChannel().setConduit(conduit); channel.getSinkChannel().setConduit(conduit); } if (readTimeout != null && readTimeout > 0) { channel.getSourceChannel().setConduit(new ReadTimeoutStreamSourceConduit(channel.getSourceChannel().getConduit(), channel, this)); } Integer writeTimeout = channel.getOption(Options.WRITE_TIMEOUT); if (writeTimeout != null && writeTimeout > 0) { channel.getSinkChannel().setConduit(new WriteTimeoutStreamSinkConduit(channel.getSinkChannel().getConduit(), channel, this)); } } catch (IOException e) { IoUtils.safeClose(channel); UndertowLogger.REQUEST_IO_LOGGER.ioException(e); } if(statisticsEnabled) { channel.getSinkChannel().setConduit(new BytesSentStreamSinkConduit(channel.getSinkChannel().getConduit(), connectorStatistics.sentAccumulator())); channel.getSourceChannel().setConduit(new BytesReceivedStreamSourceConduit(channel.getSourceChannel().getConduit(), connectorStatistics.receivedAccumulator())); connectorStatistics.incrementConnectionCount(); } AjpServerConnection connection = new AjpServerConnection(channel, bufferPool, rootHandler, undertowOptions, bufferSize); AjpReadListener readListener = new AjpReadListener(connection, scheme, parser, statisticsEnabled ? connectorStatistics : null); if(statisticsEnabled) { connection.addCloseListener(closeListener); } connection.setAjpReadListener(readListener); connections.add(connection); connection.addCloseListener(new ServerConnection.CloseListener() { @Override public void closed(ServerConnection c) { connections.remove(connection); } }); readListener.startRequest(); channel.getSourceChannel().setReadListener(readListener); readListener.handleEvent(channel.getSourceChannel()); } @Override public HttpHandler getRootHandler() { return rootHandler; } @Override public void setRootHandler(final HttpHandler rootHandler) { this.rootHandler = rootHandler; } @Override public OptionMap getUndertowOptions() { return undertowOptions; } @Override public void setUndertowOptions(final OptionMap undertowOptions) { if (undertowOptions == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("undertowOptions"); } this.undertowOptions = undertowOptions; statisticsEnabled = undertowOptions.get(UndertowOptions.ENABLE_CONNECTOR_STATISTICS, false); parser = new AjpRequestParser(undertowOptions.get(URL_CHARSET, StandardCharsets.UTF_8.name()), undertowOptions.get(DECODE_URL, true), undertowOptions.get(UndertowOptions.MAX_PARAMETERS, UndertowOptions.DEFAULT_MAX_PARAMETERS), undertowOptions.get(UndertowOptions.MAX_HEADERS, UndertowOptions.DEFAULT_MAX_HEADERS), undertowOptions.get(UndertowOptions.ALLOW_ENCODED_SLASH, false), undertowOptions.get(UndertowOptions.ALLOW_UNESCAPED_CHARACTERS_IN_URL, false)); } @Override public ByteBufferPool getBufferPool() { return bufferPool; } @Override public ConnectorStatistics getConnectorStatistics() { if(statisticsEnabled) { return connectorStatistics; } return null; } @Override public void closeConnections() { for(AjpServerConnection i : connections) { IoUtils.safeClose(i); } } public String getScheme() { return scheme; } public void setScheme(final String scheme) { this.scheme = scheme; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/ajp/AjpReadListener.java000066400000000000000000000421161420065311100321400ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.ajp; import io.undertow.UndertowLogger; import io.undertow.UndertowOptions; import io.undertow.conduits.ConduitListener; import io.undertow.conduits.EmptyStreamSourceConduit; import io.undertow.conduits.ReadDataStreamSourceConduit; import io.undertow.server.AbstractServerConnection; import io.undertow.server.ConnectorStatisticsImpl; import io.undertow.server.Connectors; import io.undertow.server.HttpServerExchange; import io.undertow.server.protocol.ParseTimeoutUpdater; import io.undertow.util.HeaderMap; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.Methods; import org.xnio.ChannelListener; import io.undertow.connector.PooledByteBuffer; import io.undertow.util.StatusCodes; import io.undertow.util.BadRequestException; import org.xnio.StreamConnection; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.ConduitStreamSinkChannel; import org.xnio.conduits.ConduitStreamSourceChannel; import org.xnio.conduits.StreamSourceConduit; import org.xnio.conduits.WriteReadyHandler; import java.io.IOException; import java.nio.ByteBuffer; import static org.xnio.IoUtils.safeClose; /** * @author Stuart Douglas */ final class AjpReadListener implements ChannelListener { private static final byte[] CPONG = {'A', 'B', 0, 1, 9}; private static final byte[] SEND_HEADERS_INTERNAL_SERVER_ERROR_MSG = {'A', 'B', 0, 8, 4, (byte)((500 >> 8) & 0xFF) , (byte)(500 & 0xFF), 0, 0, '\0', 0, 0}; private static final byte[] SEND_HEADERS_BAD_REQUEST_MSG = {'A', 'B', 0, 8, 4, (byte)((400 >> 8) & 0xFF) , (byte)(400 & 0xFF), 0, 0, '\0', 0, 0}; private static final byte[] END_RESPONSE = {'A', 'B', 0, 2, 5, 1}; private final AjpServerConnection connection; private final String scheme; private final boolean recordRequestStartTime; private AjpRequestParseState state = new AjpRequestParseState(); private HttpServerExchange httpServerExchange; private volatile int read = 0; private final int maxRequestSize; private final long maxEntitySize; private final AjpRequestParser parser; private final ConnectorStatisticsImpl connectorStatistics; private WriteReadyHandler.ChannelListenerHandler writeReadyHandler; private ParseTimeoutUpdater parseTimeoutUpdater; AjpReadListener(final AjpServerConnection connection, final String scheme, AjpRequestParser parser, ConnectorStatisticsImpl connectorStatistics) { this.connection = connection; this.scheme = scheme; this.parser = parser; this.connectorStatistics = connectorStatistics; this.maxRequestSize = connection.getUndertowOptions().get(UndertowOptions.MAX_HEADER_SIZE, UndertowOptions.DEFAULT_MAX_HEADER_SIZE); this.maxEntitySize = connection.getUndertowOptions().get(UndertowOptions.MAX_ENTITY_SIZE, UndertowOptions.DEFAULT_MAX_ENTITY_SIZE); this.writeReadyHandler = new WriteReadyHandler.ChannelListenerHandler<>(connection.getChannel().getSinkChannel()); this.recordRequestStartTime = connection.getUndertowOptions().get(UndertowOptions.RECORD_REQUEST_START_TIME, false); int requestParseTimeout = connection.getUndertowOptions().get(UndertowOptions.REQUEST_PARSE_TIMEOUT, -1); int requestIdleTimeout = connection.getUndertowOptions().get(UndertowOptions.NO_REQUEST_TIMEOUT, -1); if(requestIdleTimeout < 0 && requestParseTimeout < 0) { this.parseTimeoutUpdater = null; } else { this.parseTimeoutUpdater = new ParseTimeoutUpdater(connection, requestParseTimeout, requestIdleTimeout); connection.addCloseListener(parseTimeoutUpdater); } } public void startRequest() { connection.resetChannel(); state = new AjpRequestParseState(); read = 0; if(parseTimeoutUpdater != null) { parseTimeoutUpdater.connectionIdle(); } connection.setCurrentExchange(null); } public void handleEvent(final StreamSourceChannel channel) { if(connection.getOriginalSinkConduit().isWriteShutdown() || connection.getOriginalSourceConduit().isReadShutdown()) { safeClose(connection); channel.suspendReads(); return; } PooledByteBuffer existing = connection.getExtraBytes(); final PooledByteBuffer pooled = existing == null ? connection.getByteBufferPool().allocate() : existing; final ByteBuffer buffer = pooled.getBuffer(); boolean free = true; boolean bytesRead = false; try { int res; do { if (existing == null) { buffer.clear(); res = channel.read(buffer); } else { res = buffer.remaining(); } if (res == 0) { if(bytesRead && parseTimeoutUpdater != null) { parseTimeoutUpdater.failedParse(); } if (!channel.isReadResumed()) { channel.getReadSetter().set(this); channel.resumeReads(); } return; } if (res == -1) { channel.shutdownReads(); final StreamSinkChannel responseChannel = connection.getChannel().getSinkChannel(); responseChannel.shutdownWrites(); safeClose(connection); return; } bytesRead = true; //TODO: we need to handle parse errors if (existing != null) { existing = null; connection.setExtraBytes(null); } else { buffer.flip(); } int begin = buffer.remaining(); if(httpServerExchange == null) { httpServerExchange = new HttpServerExchange(connection, maxEntitySize); } parser.parse(buffer, state, httpServerExchange); read += begin - buffer.remaining(); if (buffer.hasRemaining()) { free = false; connection.setExtraBytes(pooled); } if (read > maxRequestSize) { UndertowLogger.REQUEST_LOGGER.requestHeaderWasTooLarge(connection.getPeerAddress(), maxRequestSize); safeClose(connection); return; } } while (!state.isComplete()); if(parseTimeoutUpdater != null) { parseTimeoutUpdater.requestStarted(); } if (state.prefix != AjpRequestParser.FORWARD_REQUEST) { if (state.prefix == AjpRequestParser.CPING) { UndertowLogger.REQUEST_LOGGER.debug("Received CPING, sending CPONG"); handleCPing(); } else if (state.prefix == AjpRequestParser.CPONG) { UndertowLogger.REQUEST_LOGGER.debug("Received CPONG, starting next request"); state = new AjpRequestParseState(); channel.getReadSetter().set(this); channel.resumeReads(); } else { UndertowLogger.REQUEST_LOGGER.ignoringAjpRequestWithPrefixCode(state.prefix); safeClose(connection); } return; } // we remove ourselves as the read listener from the channel; // if the http handler doesn't set any then reads will suspend, which is the right thing to do channel.getReadSetter().set(null); channel.suspendReads(); final HttpServerExchange httpServerExchange = this.httpServerExchange; final AjpServerResponseConduit responseConduit = new AjpServerResponseConduit(connection.getChannel().getSinkChannel().getConduit(), connection.getByteBufferPool(), httpServerExchange, new ConduitListener() { @Override public void handleEvent(AjpServerResponseConduit channel) { Connectors.terminateResponse(httpServerExchange); } }, httpServerExchange.getRequestMethod().equals(Methods.HEAD)); connection.getChannel().getSinkChannel().setConduit(responseConduit); connection.getChannel().getSourceChannel().setConduit(createSourceConduit(connection.getChannel().getSourceChannel().getConduit(), responseConduit, httpServerExchange)); //we need to set the write ready handler. This allows the response conduit to wrap it responseConduit.setWriteReadyHandler(writeReadyHandler); connection.setSSLSessionInfo(state.createSslSessionInfo()); httpServerExchange.setSourceAddress(state.createPeerAddress()); httpServerExchange.setDestinationAddress(state.createDestinationAddress()); if(scheme != null) { httpServerExchange.setRequestScheme(scheme); } if(state.attributes != null) { httpServerExchange.putAttachment(HttpServerExchange.REQUEST_ATTRIBUTES, state.attributes); } AjpRequestParseState oldState = state; state = null; this.httpServerExchange = null; httpServerExchange.setPersistent(true); if(recordRequestStartTime) { Connectors.setRequestStartTime(httpServerExchange); } connection.setCurrentExchange(httpServerExchange); if(connectorStatistics != null) { connectorStatistics.setup(httpServerExchange); } if(!Connectors.areRequestHeadersValid(httpServerExchange.getRequestHeaders())) { oldState.badRequest = true; UndertowLogger.REQUEST_IO_LOGGER.debugf("Invalid AJP request from %s, request contained invalid headers", connection.getPeerAddress()); } if(oldState.badRequest) { httpServerExchange.setStatusCode(StatusCodes.BAD_REQUEST); httpServerExchange.endExchange(); handleBadRequest(); safeClose(connection); } else { Connectors.executeRootHandler(connection.getRootHandler(), httpServerExchange); } } catch (BadRequestException e) { UndertowLogger.REQUEST_IO_LOGGER.failedToParseRequest(e); handleBadRequest(); safeClose(connection); } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); handleInternalServerError(); safeClose(connection); } catch (Throwable t) { UndertowLogger.REQUEST_LOGGER.exceptionProcessingRequest(t); handleInternalServerError(); safeClose(connection); } finally { if (free) pooled.close(); } } private void handleInternalServerError() { sendMessages(SEND_HEADERS_INTERNAL_SERVER_ERROR_MSG, END_RESPONSE); } private void handleBadRequest() { sendMessages(SEND_HEADERS_BAD_REQUEST_MSG, END_RESPONSE); } private void handleCPing() { if (sendMessages(CPONG)) { AjpReadListener.this.handleEvent(connection.getChannel().getSourceChannel()); } } private boolean sendMessages(final byte[]... rawMessages) { state = new AjpRequestParseState(); final StreamConnection underlyingChannel = connection.getChannel(); underlyingChannel.getSourceChannel().suspendReads(); // detect buffer size int bufferSize = 0; for (int i = 0; i < rawMessages.length; i++) { bufferSize += rawMessages[i].length; } // fill in buffer final ByteBuffer buffer = ByteBuffer.allocate(bufferSize); for (int i = 0; i < rawMessages.length; i++) { buffer.put(rawMessages[i]); } buffer.flip(); // send buffer content int res; try { do { res = underlyingChannel.getSinkChannel().write(buffer); if (res == 0) { underlyingChannel.getSinkChannel().setWriteListener(new ChannelListener() { @Override public void handleEvent(ConduitStreamSinkChannel channel) { int res; do { try { res = channel.write(buffer); if (res == 0) { return; } } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); safeClose(connection); } } while (buffer.hasRemaining()); channel.suspendWrites(); AjpReadListener.this.handleEvent(underlyingChannel.getSourceChannel()); } }); underlyingChannel.getSinkChannel().resumeWrites(); return false; } } while (buffer.hasRemaining()); return true; } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); safeClose(connection); return false; } } public void exchangeComplete(final HttpServerExchange exchange) { if (!exchange.isUpgrade() && exchange.isPersistent()) { startRequest(); ConduitStreamSourceChannel channel = ((AjpServerConnection) exchange.getConnection()).getChannel().getSourceChannel(); channel.getReadSetter().set(this); channel.wakeupReads(); } else if(!exchange.isPersistent()) { safeClose(exchange.getConnection()); } } private StreamSourceConduit createSourceConduit(StreamSourceConduit underlyingConduit, AjpServerResponseConduit responseConduit, final HttpServerExchange exchange) throws BadRequestException { ReadDataStreamSourceConduit conduit = new ReadDataStreamSourceConduit(underlyingConduit, (AbstractServerConnection) exchange.getConnection()); final HeaderMap requestHeaders = exchange.getRequestHeaders(); HttpString transferEncoding = Headers.IDENTITY; Long length; final String teHeader = requestHeaders.getLast(Headers.TRANSFER_ENCODING); boolean hasTransferEncoding = teHeader != null; if (hasTransferEncoding) { transferEncoding = new HttpString(teHeader); } final String requestContentLength = requestHeaders.getFirst(Headers.CONTENT_LENGTH); if (hasTransferEncoding && !transferEncoding.equals(Headers.IDENTITY)) { length = null; //unknown length } else if (requestContentLength != null) { try { final long contentLength = Long.parseLong(requestContentLength); if (contentLength == 0L) { UndertowLogger.REQUEST_LOGGER.trace("No content, starting next request"); // no content - immediately start the next request, returning an empty stream for this one Connectors.terminateRequest(httpServerExchange); return new EmptyStreamSourceConduit(conduit.getReadThread()); } else { length = contentLength; } } catch (NumberFormatException e) { throw new BadRequestException("Invalid Content-Length header", e); } } else { UndertowLogger.REQUEST_LOGGER.trace("No content length or transfer coding, starting next request"); // no content - immediately start the next request, returning an empty stream for this one Connectors.terminateRequest(exchange); return new EmptyStreamSourceConduit(conduit.getReadThread()); } return new AjpServerRequestConduit(conduit, exchange, responseConduit, length, new ConduitListener() { @Override public void handleEvent(AjpServerRequestConduit channel) { Connectors.terminateRequest(exchange); } }); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/ajp/AjpRequestParseState.java000066400000000000000000000117351420065311100332060ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.ajp; import io.undertow.UndertowLogger; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.security.cert.CertificateException; import java.util.Map; import io.undertow.server.BasicSSLSessionInfo; import io.undertow.util.HttpString; /** * @author Stuart Douglas */ class AjpRequestParseState { //states public static final int BEGIN = 0; public static final int READING_MAGIC_NUMBER = 1; public static final int READING_DATA_SIZE = 2; public static final int READING_PREFIX_CODE = 3; public static final int READING_METHOD = 4; public static final int READING_PROTOCOL = 5; public static final int READING_REQUEST_URI = 6; public static final int READING_REMOTE_ADDR = 7; public static final int READING_REMOTE_HOST = 8; public static final int READING_SERVER_NAME = 9; public static final int READING_SERVER_PORT = 10; public static final int READING_IS_SSL = 11; public static final int READING_NUM_HEADERS = 12; public static final int READING_HEADERS = 13; public static final int READING_ATTRIBUTES = 14; public static final int DONE = 15; int state; byte prefix; int dataSize; int numHeaders = 0; HttpString currentHeader; String currentAttribute; //TODO: can there be more than one attribute? Map attributes; String remoteAddress; int remotePort = -1; int serverPort = 80; String serverAddress; /** * The length of the string being read */ public int stringLength = -1; /** * The current string being read */ private StringBuilder currentString = new StringBuilder(); /** * when reading the first byte of an integer this stores the first value. It is set to -1 to signify that * the first byte has not been read yet. */ public int currentIntegerPart = -1; boolean containsUrlCharacters = false; public int readHeaders = 0; public String sslSessionId; public String sslCipher; public String sslCert; public String sslKeySize; boolean badRequest; public boolean containsUnencodedUrlCharacters; public void reset() { stringLength = -1; currentIntegerPart = -1; readHeaders = 0; badRequest = false; currentString.setLength(0); containsUnencodedUrlCharacters = false; } public boolean isComplete() { return state == 15; } BasicSSLSessionInfo createSslSessionInfo() { String sessionId = sslSessionId; String cypher = sslCipher; String cert = sslCert; Integer keySize = null; if (cert == null && sessionId == null) { return null; } if (sslKeySize != null) { try { keySize = Integer.parseUnsignedInt(sslKeySize); } catch (NumberFormatException e) { UndertowLogger.REQUEST_LOGGER.debugf("Invalid sslKeySize %s", sslKeySize); } } try { return new BasicSSLSessionInfo(sessionId, cypher, cert, keySize); } catch (CertificateException e) { return null; } catch (javax.security.cert.CertificateException e) { return null; } } InetSocketAddress createPeerAddress() { if (remoteAddress == null) { return null; } int port = remotePort > 0 ? remotePort : 0; try { InetAddress address = InetAddress.getByName(remoteAddress); return new InetSocketAddress(address, port); } catch (UnknownHostException e) { return null; } } InetSocketAddress createDestinationAddress() { if (serverAddress == null) { return null; } return InetSocketAddress.createUnresolved(serverAddress, serverPort); } public void addStringByte(byte b) { currentString.append((char)(b & 0xFF)); } public String getStringAndClear() throws UnsupportedEncodingException { String ret = currentString.toString(); currentString.setLength(0); return ret; } public int getCurrentStringLength() { return currentString.length(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/ajp/AjpRequestParser.java000066400000000000000000000717551420065311100323770ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.ajp; import static io.undertow.util.Methods.ACL; import static io.undertow.util.Methods.BASELINE_CONTROL; import static io.undertow.util.Methods.CHECKIN; import static io.undertow.util.Methods.CHECKOUT; import static io.undertow.util.Methods.COPY; import static io.undertow.util.Methods.DELETE; import static io.undertow.util.Methods.GET; import static io.undertow.util.Methods.HEAD; import static io.undertow.util.Methods.LABEL; import static io.undertow.util.Methods.LOCK; import static io.undertow.util.Methods.MERGE; import static io.undertow.util.Methods.MKACTIVITY; import static io.undertow.util.Methods.MKCOL; import static io.undertow.util.Methods.MKWORKSPACE; import static io.undertow.util.Methods.MOVE; import static io.undertow.util.Methods.OPTIONS; import static io.undertow.util.Methods.POST; import static io.undertow.util.Methods.PROPFIND; import static io.undertow.util.Methods.PROPPATCH; import static io.undertow.util.Methods.PUT; import static io.undertow.util.Methods.REPORT; import static io.undertow.util.Methods.SEARCH; import static io.undertow.util.Methods.TRACE; import static io.undertow.util.Methods.UNCHECKOUT; import static io.undertow.util.Methods.UNLOCK; import static io.undertow.util.Methods.UPDATE; import static io.undertow.util.Methods.VERSION_CONTROL; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.security.impl.ExternalAuthenticationMechanism; import io.undertow.server.Connectors; import io.undertow.server.HttpServerExchange; import io.undertow.util.BadRequestException; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.ParameterLimitException; import io.undertow.util.URLUtils; /** * @author Stuart Douglas */ public class AjpRequestParser { private final String encoding; private final boolean doDecode; private final boolean allowEncodedSlash; private final int maxParameters; private final int maxHeaders; private StringBuilder decodeBuffer; private final boolean allowUnescapedCharactersInUrl; private final Pattern allowedRequestAttributesPattern; private static final HttpString[] HTTP_HEADERS; public static final int FORWARD_REQUEST = 2; public static final int CPONG = 9; public static final int CPING = 10; public static final int SHUTDOWN = 7; private static final HttpString[] HTTP_METHODS; private static final String[] ATTRIBUTES; private static final Set ATTR_SET; public static final String QUERY_STRING = "query_string"; public static final String SSL_CERT = "ssl_cert"; public static final String CONTEXT = "context"; public static final String SERVLET_PATH = "servlet_path"; public static final String REMOTE_USER = "remote_user"; public static final String AUTH_TYPE = "auth_type"; public static final String ROUTE = "route"; public static final String SSL_CIPHER = "ssl_cipher"; public static final String SSL_SESSION = "ssl_session"; public static final String REQ_ATTRIBUTE = "req_attribute"; public static final String SSL_KEY_SIZE = "ssl_key_size"; public static final String SECRET = "secret"; public static final String STORED_METHOD = "stored_method"; public static final String AJP_REMOTE_PORT = "AJP_REMOTE_PORT"; static { HTTP_METHODS = new HttpString[28]; HTTP_METHODS[1] = OPTIONS; HTTP_METHODS[2] = GET; HTTP_METHODS[3] = HEAD; HTTP_METHODS[4] = POST; HTTP_METHODS[5] = PUT; HTTP_METHODS[6] = DELETE; HTTP_METHODS[7] = TRACE; HTTP_METHODS[8] = PROPFIND; HTTP_METHODS[9] = PROPPATCH; HTTP_METHODS[10] = MKCOL; HTTP_METHODS[11] = COPY; HTTP_METHODS[12] = MOVE; HTTP_METHODS[13] = LOCK; HTTP_METHODS[14] = UNLOCK; HTTP_METHODS[15] = ACL; HTTP_METHODS[16] = REPORT; HTTP_METHODS[17] = VERSION_CONTROL; HTTP_METHODS[18] = CHECKIN; HTTP_METHODS[19] = CHECKOUT; HTTP_METHODS[20] = UNCHECKOUT; HTTP_METHODS[21] = SEARCH; HTTP_METHODS[22] = MKWORKSPACE; HTTP_METHODS[23] = UPDATE; HTTP_METHODS[24] = LABEL; HTTP_METHODS[25] = MERGE; HTTP_METHODS[26] = BASELINE_CONTROL; HTTP_METHODS[27] = MKACTIVITY; HTTP_HEADERS = new HttpString[0xF]; HTTP_HEADERS[1] = Headers.ACCEPT; HTTP_HEADERS[2] = Headers.ACCEPT_CHARSET; HTTP_HEADERS[3] = Headers.ACCEPT_ENCODING; HTTP_HEADERS[4] = Headers.ACCEPT_LANGUAGE; HTTP_HEADERS[5] = Headers.AUTHORIZATION; HTTP_HEADERS[6] = Headers.CONNECTION; HTTP_HEADERS[7] = Headers.CONTENT_TYPE; HTTP_HEADERS[8] = Headers.CONTENT_LENGTH; HTTP_HEADERS[9] = Headers.COOKIE; HTTP_HEADERS[0xA] = Headers.COOKIE2; HTTP_HEADERS[0xB] = Headers.HOST; HTTP_HEADERS[0xC] = Headers.PRAGMA; HTTP_HEADERS[0xD] = Headers.REFERER; HTTP_HEADERS[0xE] = Headers.USER_AGENT; ATTRIBUTES = new String[0xE]; ATTRIBUTES[1] = CONTEXT; ATTRIBUTES[2] = SERVLET_PATH; ATTRIBUTES[3] = REMOTE_USER; ATTRIBUTES[4] = AUTH_TYPE; ATTRIBUTES[5] = QUERY_STRING; ATTRIBUTES[6] = ROUTE; ATTRIBUTES[7] = SSL_CERT; ATTRIBUTES[8] = SSL_CIPHER; ATTRIBUTES[9] = SSL_SESSION; ATTRIBUTES[10] = REQ_ATTRIBUTE; ATTRIBUTES[11] = SSL_KEY_SIZE; ATTRIBUTES[12] = SECRET; ATTRIBUTES[13] = STORED_METHOD; ATTR_SET = new HashSet(Arrays.asList(ATTRIBUTES)); } public AjpRequestParser(String encoding, boolean doDecode, int maxParameters, int maxHeaders, boolean allowEncodedSlash, boolean allowUnescapedCharactersInUrl) { this(encoding, doDecode, maxParameters, maxHeaders, allowEncodedSlash, allowUnescapedCharactersInUrl, null); } public AjpRequestParser(String encoding, boolean doDecode, int maxParameters, int maxHeaders, boolean allowEncodedSlash, boolean allowUnescapedCharactersInUrl, String allowedRequestAttributesPattern) { this.encoding = encoding; this.doDecode = doDecode; this.maxParameters = maxParameters; this.maxHeaders = maxHeaders; this.allowEncodedSlash = allowEncodedSlash; this.allowUnescapedCharactersInUrl = allowUnescapedCharactersInUrl; if (allowedRequestAttributesPattern != null && !allowedRequestAttributesPattern.isEmpty()) { this.allowedRequestAttributesPattern = Pattern.compile(allowedRequestAttributesPattern); } else { this.allowedRequestAttributesPattern = null; } } public void parse(final ByteBuffer buf, final AjpRequestParseState state, final HttpServerExchange exchange) throws IOException, BadRequestException { if (!buf.hasRemaining()) { return; } switch (state.state) { case AjpRequestParseState.BEGIN: { IntegerHolder result = parse16BitInteger(buf, state); if (!result.readComplete) { return; } else { if (result.value != 0x1234) { throw new BadRequestException(UndertowMessages.MESSAGES.wrongMagicNumber(result.value)); } } } case AjpRequestParseState.READING_DATA_SIZE: { IntegerHolder result = parse16BitInteger(buf, state); if (!result.readComplete) { state.state = AjpRequestParseState.READING_DATA_SIZE; return; } else { state.dataSize = result.value; } } case AjpRequestParseState.READING_PREFIX_CODE: { if (!buf.hasRemaining()) { state.state = AjpRequestParseState.READING_PREFIX_CODE; return; } else { final byte prefix = buf.get(); state.prefix = prefix; if (prefix != 2) { state.state = AjpRequestParseState.DONE; return; } } } case AjpRequestParseState.READING_METHOD: { if (!buf.hasRemaining()) { state.state = AjpRequestParseState.READING_METHOD; return; } else { int method = buf.get(); if (method > 0 && method < 28) { exchange.setRequestMethod(HTTP_METHODS[method]); } else if((method & 0xFF) != 0xFF) { throw new BadRequestException("Unknown method type " + method); } } } case AjpRequestParseState.READING_PROTOCOL: { StringHolder result = parseString(buf, state, StringType.OTHER); if (result.readComplete) { //TODO: more efficient way of doing this exchange.setProtocol(HttpString.tryFromString(result.value)); } else { state.state = AjpRequestParseState.READING_PROTOCOL; return; } } case AjpRequestParseState.READING_REQUEST_URI: { StringHolder result = parseString(buf, state, StringType.URL); if (result.readComplete) { int colon = result.value.indexOf(';'); if (colon == -1) { String res = decode(result.value, result.containsUrlCharacters); if(result.containsUnencodedCharacters) { //we decode if the URL was non-compliant, and contained incorrectly encoded characters //there is not really a 'correct' thing to do in this situation, but this seems the least incorrect exchange.setRequestURI(res); } else { exchange.setRequestURI(result.value); } exchange.setRequestPath(res); exchange.setRelativePath(res); } else { final StringBuffer resBuffer = new StringBuffer(); int pathParamParsingIndex = 0; try { do { final String url = result.value.substring(pathParamParsingIndex, colon); resBuffer.append(decode(url, result.containsUrlCharacters)); pathParamParsingIndex = colon + 1 + URLUtils.parsePathParams(result.value.substring(colon + 1), exchange, encoding, doDecode && result.containsUrlCharacters, maxParameters); colon = result.value.indexOf(';', pathParamParsingIndex + 1); } while (pathParamParsingIndex < result.value.length() && colon != -1); } catch (ParameterLimitException e) { UndertowLogger.REQUEST_IO_LOGGER.failedToParseRequest(e); state.badRequest = true; } if (pathParamParsingIndex < result.value.length()) { final String url = result.value.substring(pathParamParsingIndex); resBuffer.append(decode(url, result.containsUrlCharacters)); } final String res = resBuffer.toString(); if(result.containsUnencodedCharacters) { exchange.setRequestURI(res); } else { exchange.setRequestURI(result.value); } exchange.setRequestPath(res); exchange.setRelativePath(res); } } else { state.state = AjpRequestParseState.READING_REQUEST_URI; return; } } case AjpRequestParseState.READING_REMOTE_ADDR: { StringHolder result = parseString(buf, state, StringType.OTHER); if (result.readComplete) { state.remoteAddress = result.value; } else { state.state = AjpRequestParseState.READING_REMOTE_ADDR; return; } } case AjpRequestParseState.READING_REMOTE_HOST: { StringHolder result = parseString(buf, state, StringType.OTHER); if (!result.readComplete) { state.state = AjpRequestParseState.READING_REMOTE_HOST; return; } } case AjpRequestParseState.READING_SERVER_NAME: { StringHolder result = parseString(buf, state, StringType.OTHER); if (result.readComplete) { state.serverAddress = result.value; } else { state.state = AjpRequestParseState.READING_SERVER_NAME; return; } } case AjpRequestParseState.READING_SERVER_PORT: { IntegerHolder result = parse16BitInteger(buf, state); if (result.readComplete) { state.serverPort = result.value; } else { state.state = AjpRequestParseState.READING_SERVER_PORT; return; } } case AjpRequestParseState.READING_IS_SSL: { if (!buf.hasRemaining()) { state.state = AjpRequestParseState.READING_IS_SSL; return; } else { final byte isSsl = buf.get(); if (isSsl != 0) { exchange.setRequestScheme("https"); } else { exchange.setRequestScheme("http"); } } } case AjpRequestParseState.READING_NUM_HEADERS: { IntegerHolder result = parse16BitInteger(buf, state); if (!result.readComplete) { state.state = AjpRequestParseState.READING_NUM_HEADERS; return; } else { state.numHeaders = result.value; if(state.numHeaders > maxHeaders) { UndertowLogger.REQUEST_IO_LOGGER.failedToParseRequest(new BadRequestException(UndertowMessages.MESSAGES.tooManyHeaders(maxHeaders))); state.badRequest = true; } } } case AjpRequestParseState.READING_HEADERS: { int readHeaders = state.readHeaders; while (readHeaders < state.numHeaders) { if (state.currentHeader == null) { StringHolder result = parseString(buf, state, StringType.HEADER); if (!result.readComplete) { state.state = AjpRequestParseState.READING_HEADERS; state.readHeaders = readHeaders; return; } if (result.header != null) { state.currentHeader = result.header; } else { state.currentHeader = HttpString.tryFromString(result.value); Connectors.verifyToken(state.currentHeader); } } StringHolder result = parseString(buf, state, StringType.OTHER); if (!result.readComplete) { state.state = AjpRequestParseState.READING_HEADERS; state.readHeaders = readHeaders; return; } if(!state.badRequest) { exchange.getRequestHeaders().add(state.currentHeader, result.value); } state.currentHeader = null; ++readHeaders; } } case AjpRequestParseState.READING_ATTRIBUTES: { for (; ; ) { if (state.currentAttribute == null && state.currentIntegerPart == -1) { if (!buf.hasRemaining()) { state.state = AjpRequestParseState.READING_ATTRIBUTES; return; } int val = (0xFF & buf.get()); if (val == 0xFF) { state.state = AjpRequestParseState.DONE; return; } else if (val == 0x0A) { //we need to read the name. We overload currentIntegerPart to avoid adding another state field state.currentIntegerPart = 1; } else { if(val == 0 || val >= ATTRIBUTES.length) { //ignore unknown codes for compatibility continue; } state.currentAttribute = ATTRIBUTES[val]; } } if (state.currentIntegerPart == 1) { StringHolder result = parseString(buf, state, StringType.OTHER); if (!result.readComplete) { state.state = AjpRequestParseState.READING_ATTRIBUTES; return; } state.currentAttribute = result.value; state.currentIntegerPart = -1; } String result; boolean decodingAlreadyDone = false; if (state.currentAttribute.equals(SSL_KEY_SIZE)) { IntegerHolder resultHolder = parse16BitInteger(buf, state); if (!resultHolder.readComplete) { state.state = AjpRequestParseState.READING_ATTRIBUTES; return; } result = Integer.toString(resultHolder.value); } else { StringHolder resultHolder = parseString(buf, state, state.currentAttribute.equals(QUERY_STRING) ? StringType.QUERY_STRING : StringType.OTHER); if (!resultHolder.readComplete) { state.state = AjpRequestParseState.READING_ATTRIBUTES; return; } if(resultHolder.containsUnencodedCharacters) { result = decode(resultHolder.value, true); decodingAlreadyDone = true; } else { result = resultHolder.value; } } //query string. if (state.currentAttribute.equals(QUERY_STRING)) { String resultAsQueryString = result == null ? "" : result; exchange.setQueryString(resultAsQueryString); try { URLUtils.parseQueryString(resultAsQueryString, exchange, encoding, doDecode && !decodingAlreadyDone, maxParameters); } catch (ParameterLimitException | IllegalArgumentException e) { UndertowLogger.REQUEST_IO_LOGGER.failedToParseRequest(e); state.badRequest = true; } } else if (state.currentAttribute.equals(REMOTE_USER)) { exchange.putAttachment(ExternalAuthenticationMechanism.EXTERNAL_PRINCIPAL, result); exchange.putAttachment(HttpServerExchange.REMOTE_USER, result); } else if (state.currentAttribute.equals(AUTH_TYPE)) { exchange.putAttachment(ExternalAuthenticationMechanism.EXTERNAL_AUTHENTICATION_TYPE, result); } else if (state.currentAttribute.equals(STORED_METHOD)) { HttpString requestMethod = new HttpString(result); Connectors.verifyToken(requestMethod); exchange.setRequestMethod(requestMethod); } else if (state.currentAttribute.equals(AJP_REMOTE_PORT)) { state.remotePort = Integer.parseInt(result); } else if (state.currentAttribute.equals(SSL_SESSION)) { state.sslSessionId = result; } else if (state.currentAttribute.equals(SSL_CIPHER)) { state.sslCipher = result; } else if (state.currentAttribute.equals(SSL_CERT)) { state.sslCert = result; } else if (state.currentAttribute.equals(SSL_KEY_SIZE)) { state.sslKeySize = result; } else { // other attributes if (state.attributes == null) { state.attributes = new TreeMap<>(); } if (ATTR_SET.contains(state.currentAttribute)) { // known attirubtes state.attributes.put(state.currentAttribute, result); } else if (allowedRequestAttributesPattern != null) { // custom allowed attributes Matcher m = allowedRequestAttributesPattern.matcher(state.currentAttribute); if (m.matches()) { state.attributes.put(state.currentAttribute, result); } } } state.currentAttribute = null; } } } state.state = AjpRequestParseState.DONE; } private String decode(String url, final boolean containsUrlCharacters) throws UnsupportedEncodingException { if (doDecode && containsUrlCharacters) { try { if(decodeBuffer == null) { decodeBuffer = new StringBuilder(); } return URLUtils.decode(url, this.encoding, allowEncodedSlash, false, decodeBuffer); } catch (Exception e) { throw UndertowMessages.MESSAGES.failedToDecodeURL(url, encoding, e); } } return url; } protected HttpString headers(int offset) { return HTTP_HEADERS[offset]; } public static final int STRING_LENGTH_MASK = 1 << 31; protected IntegerHolder parse16BitInteger(ByteBuffer buf, AjpRequestParseState state) { if (!buf.hasRemaining()) { return new IntegerHolder(-1, false); } int number = state.currentIntegerPart; if (number == -1) { number = (buf.get() & 0xFF); } if (buf.hasRemaining()) { final byte b = buf.get(); int result = ((0xFF & number) << 8) + (b & 0xFF); state.currentIntegerPart = -1; return new IntegerHolder(result, true); } else { state.currentIntegerPart = number; return new IntegerHolder(-1, false); } } protected StringHolder parseString(ByteBuffer buf, AjpRequestParseState state, StringType type) throws UnsupportedEncodingException, BadRequestException { boolean containsUrlCharacters = state.containsUrlCharacters; boolean containsUnencodedUrlCharacters = state.containsUnencodedUrlCharacters; if (!buf.hasRemaining()) { return new StringHolder(null, false, false, false); } int stringLength = state.stringLength; if (stringLength == -1) { int number = buf.get() & 0xFF; if (buf.hasRemaining()) { final byte b = buf.get(); stringLength = ((0xFF & number) << 8) + (b & 0xFF); } else { state.stringLength = number | STRING_LENGTH_MASK; return new StringHolder(null, false, false, false); } } else if ((stringLength & STRING_LENGTH_MASK) != 0) { int number = stringLength & ~STRING_LENGTH_MASK; stringLength = ((0xFF & number) << 8) + (buf.get() & 0xFF); } if (type == StringType.HEADER && (stringLength & 0xFF00) != 0) { state.stringLength = -1; return new StringHolder(headers(stringLength & 0xFF)); } if (stringLength == 0xFFFF) { //OxFFFF means null state.stringLength = -1; return new StringHolder(null, true, false, false); } int length = state.getCurrentStringLength(); while (length < stringLength) { if (!buf.hasRemaining()) { state.stringLength = stringLength; state.containsUrlCharacters = containsUrlCharacters; state.containsUnencodedUrlCharacters = containsUnencodedUrlCharacters; return new StringHolder(null, false, false, false); } byte c = buf.get(); if(type == StringType.QUERY_STRING && (c == '+' || c == '%' || c < 0 )) { if (c < 0) { if (!allowUnescapedCharactersInUrl) { throw new BadRequestException(); } else { containsUnencodedUrlCharacters = true; } } containsUrlCharacters = true; } else if(type == StringType.URL && (c == '%' || c < 0 )) { if(c < 0 ) { if(!allowUnescapedCharactersInUrl) { throw new BadRequestException(); } else { containsUnencodedUrlCharacters = true; } } containsUrlCharacters = true; } state.addStringByte(c); ++length; } if (buf.hasRemaining()) { buf.get(); //null terminator String value = state.getStringAndClear(); state.stringLength = -1; state.containsUrlCharacters = false; state.containsUnencodedUrlCharacters = containsUnencodedUrlCharacters; return new StringHolder(value, true, containsUrlCharacters, containsUnencodedUrlCharacters); } else { state.stringLength = stringLength; state.containsUrlCharacters = containsUrlCharacters; state.containsUnencodedUrlCharacters = containsUnencodedUrlCharacters; return new StringHolder(null, false, false, false); } } protected static class IntegerHolder { public final int value; public final boolean readComplete; private IntegerHolder(int value, boolean readComplete) { this.value = value; this.readComplete = readComplete; } } protected static class StringHolder { public final String value; public final HttpString header; final boolean readComplete; final boolean containsUrlCharacters; final boolean containsUnencodedCharacters; private StringHolder(String value, boolean readComplete, boolean containsUrlCharacters, boolean containsUnencodedCharacters) { this.value = value; this.readComplete = readComplete; this.containsUrlCharacters = containsUrlCharacters; this.containsUnencodedCharacters = containsUnencodedCharacters; this.header = null; } private StringHolder(HttpString value) { this.value = null; this.readComplete = true; this.header = value; this.containsUrlCharacters = false; this.containsUnencodedCharacters = false; } } enum StringType { HEADER, URL, QUERY_STRING, OTHER } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/ajp/AjpServerConnection.java000066400000000000000000000111721420065311100330430ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.ajp; import io.undertow.UndertowMessages; import io.undertow.server.AbstractServerConnection; import io.undertow.server.BasicSSLSessionInfo; import io.undertow.server.HttpUpgradeListener; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.SSLSessionInfo; import io.undertow.util.DateUtils; import org.xnio.IoUtils; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import org.xnio.StreamConnection; import org.xnio.conduits.ConduitStreamSinkChannel; import org.xnio.conduits.StreamSinkConduit; import org.xnio.conduits.WriteReadyHandler; /** * A server-side AJP connection. *

* * @author David M. Lloyd */ public final class AjpServerConnection extends AbstractServerConnection { private SSLSessionInfo sslSessionInfo; private WriteReadyHandler.ChannelListenerHandler writeReadyHandler; private AjpReadListener ajpReadListener; public AjpServerConnection(StreamConnection channel, ByteBufferPool bufferPool, HttpHandler rootHandler, OptionMap undertowOptions, int bufferSize) { super(channel, bufferPool, rootHandler, undertowOptions, bufferSize); this.writeReadyHandler = new WriteReadyHandler.ChannelListenerHandler<>(channel.getSinkChannel()); } @Override public HttpServerExchange sendOutOfBandResponse(HttpServerExchange exchange) { throw UndertowMessages.MESSAGES.outOfBandResponseNotSupported(); } @Override public boolean isContinueResponseSupported() { return false; } @Override public void terminateRequestChannel(HttpServerExchange exchange) { if (!exchange.isPersistent()) { IoUtils.safeClose(getChannel().getSourceChannel()); } } @Override public void restoreChannel(ConduitState state) { super.restoreChannel(state); channel.getSinkChannel().getConduit().setWriteReadyHandler(writeReadyHandler); } @Override public ConduitState resetChannel() { ConduitState state = super.resetChannel(); channel.getSinkChannel().getConduit().setWriteReadyHandler(writeReadyHandler); return state; } @Override public void clearChannel() { super.clearChannel(); channel.getSinkChannel().getConduit().setWriteReadyHandler(writeReadyHandler); } @Override public SSLSessionInfo getSslSessionInfo() { return sslSessionInfo; } @Override public void setSslSessionInfo(SSLSessionInfo sessionInfo) { this.sslSessionInfo = sessionInfo; } void setSSLSessionInfo(BasicSSLSessionInfo sslSessionInfo) { this.sslSessionInfo = sslSessionInfo; } @Override protected StreamConnection upgradeChannel() { throw UndertowMessages.MESSAGES.upgradeNotSupported(); } @Override protected StreamSinkConduit getSinkConduit(HttpServerExchange exchange, StreamSinkConduit conduit) { DateUtils.addDateHeaderIfRequired(exchange); return conduit; } @Override protected boolean isUpgradeSupported() { return false; } @Override protected boolean isConnectSupported() { return false; } void setAjpReadListener(AjpReadListener ajpReadListener) { this.ajpReadListener = ajpReadListener; } @Override protected void exchangeComplete(HttpServerExchange exchange) { ajpReadListener.exchangeComplete(exchange); } @Override protected void setConnectListener(HttpUpgradeListener connectListener) { throw UndertowMessages.MESSAGES.connectNotSupported(); } void setCurrentExchange(HttpServerExchange exchange) { this.current = exchange; } @Override public String getTransportProtocol() { return "ajp"; } @Override public boolean isRequestTrailerFieldsSupported() { return false; } } AjpServerRequestConduit.java000066400000000000000000000337241420065311100336520ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/ajp/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.ajp; import static org.xnio.Bits.anyAreSet; import static org.xnio.Bits.longBitMask; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.util.concurrent.TimeUnit; import io.undertow.UndertowMessages; import io.undertow.conduits.ConduitListener; import io.undertow.server.Connectors; import io.undertow.server.HttpServerExchange; import io.undertow.util.ImmediatePooledByteBuffer; import org.xnio.IoUtils; import org.xnio.channels.StreamSinkChannel; import org.xnio.conduits.AbstractStreamSourceConduit; import org.xnio.conduits.ConduitReadableByteChannel; import org.xnio.conduits.StreamSourceConduit; /** * Underlying AJP request channel. * * @author Stuart Douglas */ public class AjpServerRequestConduit extends AbstractStreamSourceConduit { private static final ByteBuffer READ_BODY_CHUNK; static { ByteBuffer readBody = ByteBuffer.allocateDirect(7); readBody.put((byte) 'A'); readBody.put((byte) 'B'); readBody.put((byte) 0); readBody.put((byte) 3); readBody.put((byte) 6); readBody.put((byte) 0x1F); readBody.put((byte) 0xFA); readBody.flip(); READ_BODY_CHUNK = readBody; } private static final int HEADER_LENGTH = 6; /** * There is a packet coming from apache. */ private static final long STATE_READING = 1L << 63L; /** * There is no packet coming, as we need to set a GET_BODY_CHUNK message */ private static final long STATE_SEND_REQUIRED = 1L << 62L; /** * read is done */ private static final long STATE_FINISHED = 1L << 61L; /** * The remaining bits are used to store the remaining chunk size. */ private static final long STATE_MASK = longBitMask(0, 60); private final HttpServerExchange exchange; private final AjpServerResponseConduit ajpResponseConduit; /** * byte buffer that is used to hold header data */ private final ByteBuffer headerBuffer = ByteBuffer.allocateDirect(HEADER_LENGTH); private final ConduitListener finishListener; /** /** * The total amount of remaining data. If this is unknown it is -1. */ private long remaining; /** * State flags, with the chunk remaining stored in the low bytes */ private long state; /** * The total amount of data that has been read */ private long totalRead; public AjpServerRequestConduit(final StreamSourceConduit delegate, HttpServerExchange exchange, AjpServerResponseConduit ajpResponseConduit, Long size, ConduitListener finishListener) { super(delegate); this.exchange = exchange; this.ajpResponseConduit = ajpResponseConduit; this.finishListener = finishListener; if (size == null) { state = STATE_SEND_REQUIRED; remaining = -1; } else if (size.longValue() == 0L) { state = STATE_FINISHED; remaining = 0; } else { state = STATE_READING; remaining = size; } } @Override public long transferTo(long position, long count, FileChannel target) throws IOException { try { return target.transferFrom(new ConduitReadableByteChannel(this), position, count); } catch (IOException | RuntimeException e) { IoUtils.safeClose(exchange.getConnection()); throw e; } } @Override public long transferTo(long count, ByteBuffer throughBuffer, StreamSinkChannel target) throws IOException { try { return IoUtils.transfer(new ConduitReadableByteChannel(this), count, throughBuffer, target); } catch (IOException | RuntimeException e) { IoUtils.safeClose(exchange.getConnection()); throw e; } } @Override public void terminateReads() throws IOException { if(exchange.isPersistent() && anyAreSet(state, STATE_FINISHED)) { return; } super.terminateReads(); } @Override public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { try { long total = 0; for (int i = offset; i < length; ++i) { while (dsts[i].hasRemaining()) { int r = read(dsts[i]); if (r <= 0 && total > 0) { return total; } else if (r <= 0) { return r; } else { total += r; } } } return total; } catch (IOException | RuntimeException e) { IoUtils.safeClose(exchange.getConnection()); throw e; } } @Override public int read(ByteBuffer dst) throws IOException { try { long state = this.state; if (anyAreSet(state, STATE_FINISHED)) { return -1; } else if (anyAreSet(state, STATE_SEND_REQUIRED)) { state = this.state = (state & STATE_MASK) | STATE_READING; if (ajpResponseConduit.isWriteShutdown()) { this.state = STATE_FINISHED; if (finishListener != null) { finishListener.handleEvent(this); } return -1; } if (!ajpResponseConduit.doGetRequestBodyChunk(READ_BODY_CHUNK.duplicate(), this)) { return 0; } } //we might have gone into state_reading above if (anyAreSet(state, STATE_READING)) { return doRead(dst, state); } assert STATE_FINISHED == state; return -1; } catch (IOException | RuntimeException e) { IoUtils.safeClose(exchange.getConnection()); throw e; } } private int doRead(final ByteBuffer dst, long state) throws IOException { ByteBuffer headerBuffer = this.headerBuffer; long headerRead = HEADER_LENGTH - headerBuffer.remaining(); long remaining = this.remaining; if (remaining == 0) { this.state = STATE_FINISHED; if (finishListener != null) { finishListener.handleEvent(this); } return -1; } long chunkRemaining; if (headerRead != HEADER_LENGTH) { int read = next.read(headerBuffer); if (read == -1) { this.state = STATE_FINISHED; if (finishListener != null) { finishListener.handleEvent(this); } throw new ClosedChannelException(); } else if (headerBuffer.hasRemaining()) { if(headerBuffer.remaining() <= 2) { //mod_jk can send 12 34 00 00 rather than 12 34 00 02 00 00 byte b1 = headerBuffer.get(0); //0x12 byte b2 = headerBuffer.get(1); //0x34 if (b1 != 0x12 || b2 != 0x34) { throw UndertowMessages.MESSAGES.wrongMagicNumber((b1 & 0xFF) << 8 | (b2 & 0xFF)); } b1 = headerBuffer.get(2);//the length headers, two more than the string length header b2 = headerBuffer.get(3); int totalSize = ((b1 & 0xFF) << 8) | (b2 & 0xFF); if(totalSize == 0) { if(headerBuffer.remaining() < 2) { byte[] data = new byte[1]; ByteBuffer bb = ByteBuffer.wrap(data); bb.put(headerBuffer.get(4)); bb.flip(); Connectors.ungetRequestBytes(exchange, new ImmediatePooledByteBuffer(bb)); } this.remaining = 0; this.state = STATE_FINISHED; if (finishListener != null) { finishListener.handleEvent(this); } return -1; } } return 0; } else { headerBuffer.flip(); byte b1 = headerBuffer.get(); //0x12 byte b2 = headerBuffer.get(); //0x34 if (b1 != 0x12 || b2 != 0x34) { throw UndertowMessages.MESSAGES.wrongMagicNumber((b1 & 0xFF) << 8 | (b2 & 0xFF)); } b1 = headerBuffer.get();//the length headers, two more than the string length header b2 = headerBuffer.get(); int totalSize = ((b1 & 0xFF) << 8) | (b2 & 0xFF); if(totalSize == 0) { byte[] data = new byte[2]; ByteBuffer bb = ByteBuffer.wrap(data); bb.put(headerBuffer); bb.flip(); Connectors.ungetRequestBytes(exchange, new ImmediatePooledByteBuffer(bb)); this.remaining = 0; this.state = STATE_FINISHED; if (finishListener != null) { finishListener.handleEvent(this); } return -1; } b1 = headerBuffer.get(); b2 = headerBuffer.get(); chunkRemaining = ((b1 & 0xFF) << 8) | (b2 & 0xFF); if (chunkRemaining == 0) { this.remaining = 0; this.state = STATE_FINISHED; if (finishListener != null) { finishListener.handleEvent(this); } return -1; } } } else { chunkRemaining = this.state & STATE_MASK; } int limit = dst.limit(); Throwable originalException = null; int returnValue = 0; try { if (dst.remaining() > chunkRemaining) { dst.limit((int) (dst.position() + chunkRemaining)); } int read = next.read(dst); chunkRemaining -= read; if (remaining != -1) { remaining -= read; } this.totalRead += read; if (remaining != 0) { if (chunkRemaining == 0) { headerBuffer.clear(); this.state = STATE_SEND_REQUIRED; } else { this.state = (state & ~STATE_MASK) | chunkRemaining; } } returnValue = read; } catch (Throwable t) { originalException = t; } finally { Throwable suppressed = originalException; //OK to be null try { this.remaining = remaining; dst.limit(limit); final long maxEntitySize = exchange.getMaxEntitySize(); if (maxEntitySize > 0) { if (totalRead > maxEntitySize) { //kill the connection, nothing else can be sent on it terminateReads(); exchange.setPersistent(false); suppressed = UndertowMessages.MESSAGES.requestEntityWasTooLarge(maxEntitySize); if(originalException != null) { originalException.addSuppressed(suppressed); suppressed = originalException; } } } } catch (Throwable t) { if (suppressed != null) { suppressed.addSuppressed(t); } else { suppressed = t; } } if (suppressed != null) { if (suppressed instanceof RuntimeException) { throw (RuntimeException) suppressed; } if (suppressed instanceof Error) { throw (Error) suppressed; } if (suppressed instanceof IOException) { throw (IOException) suppressed; } } } return returnValue; } @Override public void awaitReadable() throws IOException { try { if (anyAreSet(state, STATE_READING)) { next.awaitReadable(); } } catch (IOException | RuntimeException e) { IoUtils.safeClose(exchange.getConnection()); throw e; } } @Override public void awaitReadable(long time, TimeUnit timeUnit) throws IOException { try { if (anyAreSet(state, STATE_READING)) { next.awaitReadable(time, timeUnit); } } catch (IOException | RuntimeException e) { IoUtils.safeClose(exchange.getConnection()); throw e; } } /** * Method that is called when an error occurs writing out read body chunk frames * @param e */ void setReadBodyChunkError(IOException e) { IoUtils.safeClose(exchange.getConnection()); if(isReadResumed()) { wakeupReads(); } } } AjpServerResponseConduit.java000066400000000000000000000440671420065311100340220ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/ajp/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.ajp; import io.undertow.UndertowMessages; import io.undertow.UndertowOptions; import io.undertow.conduits.AbstractFramedStreamSinkConduit; import io.undertow.conduits.ConduitListener; import io.undertow.server.Connectors; import io.undertow.server.HttpServerExchange; import io.undertow.util.HeaderMap; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.StatusCodes; import org.jboss.logging.Logger; import org.xnio.Buffers; import org.xnio.IoUtils; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.ConduitWritableByteChannel; import org.xnio.conduits.StreamSinkConduit; import org.xnio.conduits.WriteReadyHandler; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.util.Collections; import java.util.HashMap; import java.util.Map; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.anyAreSet; /** * AJP response channel. For now we are going to assume that the buffers are sized to * fit complete packets. As AJP packets are limited to 8k this is a reasonable assumption. * * @author David M. Lloyd * @author Stuart Douglas */ final class AjpServerResponseConduit extends AbstractFramedStreamSinkConduit { private static final Logger log = Logger.getLogger("io.undertow.server.channel.ajp.response"); private static final int DEFAULT_MAX_DATA_SIZE = 8192; private static final Map HEADER_MAP; private static final ByteBuffer FLUSH_PACKET = ByteBuffer.allocateDirect(8); static { final Map headers = new HashMap<>(); headers.put(Headers.CONTENT_TYPE, 0xA001); headers.put(Headers.CONTENT_LANGUAGE, 0xA002); headers.put(Headers.CONTENT_LENGTH, 0xA003); headers.put(Headers.DATE, 0xA004); headers.put(Headers.LAST_MODIFIED, 0xA005); headers.put(Headers.LOCATION, 0xA006); headers.put(Headers.SET_COOKIE, 0xA007); headers.put(Headers.SET_COOKIE2, 0xA008); headers.put(Headers.SERVLET_ENGINE, 0xA009); headers.put(Headers.STATUS, 0xA00A); headers.put(Headers.WWW_AUTHENTICATE, 0xA00B); HEADER_MAP = Collections.unmodifiableMap(headers); FLUSH_PACKET.put((byte) 'A'); FLUSH_PACKET.put((byte) 'B'); FLUSH_PACKET.put((byte) 0); FLUSH_PACKET.put((byte) 4); FLUSH_PACKET.put((byte) 3); FLUSH_PACKET.put((byte) 0); FLUSH_PACKET.put((byte) 0); FLUSH_PACKET.put((byte) 0); FLUSH_PACKET.flip(); } private static final int FLAG_START = 1; //indicates that the header has not been generated yet. private static final int FLAG_WRITE_RESUMED = 1 << 2; private static final int FLAG_WRITE_READ_BODY_CHUNK_FROM_LISTENER = 1 << 3; private static final int FLAG_WRITE_SHUTDOWN = 1 << 4; private static final int FLAG_READS_DONE = 1 << 5; private static final int FLAG_FLUSH_QUEUED = 1 << 6; private static final ByteBuffer CLOSE_FRAME_PERSISTENT; private static final ByteBuffer CLOSE_FRAME_NON_PERSISTENT; static { ByteBuffer buffer = ByteBuffer.wrap(new byte[6]); buffer.put((byte) 'A'); buffer.put((byte) 'B'); buffer.put((byte) 0); buffer.put((byte) 2); buffer.put((byte) 5); buffer.put((byte) 1); //reuse buffer.flip(); CLOSE_FRAME_PERSISTENT = buffer; buffer = ByteBuffer.wrap(new byte[6]); buffer.put(CLOSE_FRAME_PERSISTENT.duplicate()); buffer.put(5, (byte) 0); buffer.flip(); CLOSE_FRAME_NON_PERSISTENT = buffer; } private final ByteBufferPool pool; /** * State flags */ private int state = FLAG_START; private final HttpServerExchange exchange; private final ConduitListener finishListener; private final boolean headRequest; AjpServerResponseConduit(final StreamSinkConduit next, final ByteBufferPool pool, final HttpServerExchange exchange, ConduitListener finishListener, boolean headRequest) { super(next); this.pool = pool; this.exchange = exchange; this.finishListener = finishListener; this.headRequest = headRequest; state = FLAG_START; } private static void putInt(final ByteBuffer buf, int value) { buf.put((byte) ((value >> 8) & 0xFF)); buf.put((byte) (value & 0xFF)); } private static void putString(final ByteBuffer buf, String value) { final int length = value.length(); putInt(buf, length); for (int i = 0; i < length; ++i) { char c = value.charAt(i); if(c != '\r' && c != '\n'){ buf.put((byte) c); } else { buf.put((byte)' '); } } buf.put((byte) 0); } private void putHttpString(final ByteBuffer buf, HttpString value) { final int length = value.length(); putInt(buf, length); value.appendTo(buf); buf.put((byte) 0); } /** * Handles generating the header if required, and adding it to the frame queue. * * No attempt is made to actually flush this, so a gathering write can be used to actually flush the data */ private void processAJPHeader() { int oldState = this.state; if (anyAreSet(oldState, FLAG_START)) { PooledByteBuffer[] byteBuffers = null; //merge the cookies into the header map Connectors.flattenCookies(exchange); PooledByteBuffer pooled = pool.allocate(); ByteBuffer buffer = pooled.getBuffer(); buffer.put((byte) 'A'); buffer.put((byte) 'B'); buffer.put((byte) 0); //we fill the size in later buffer.put((byte) 0); buffer.put((byte) 4); putInt(buffer, exchange.getStatusCode()); String reason = exchange.getReasonPhrase(); if(reason == null) { reason = StatusCodes.getReason(exchange.getStatusCode()); } if(reason.length() + 4 > buffer.remaining()) { pooled.close(); throw UndertowMessages.MESSAGES.reasonPhraseToLargeForBuffer(reason); } putString(buffer, reason); int headers = 0; //we need to count the headers final HeaderMap responseHeaders = exchange.getResponseHeaders(); for (HttpString name : responseHeaders.getHeaderNames()) { headers += responseHeaders.get(name).size(); } putInt(buffer, headers); for (final HttpString header : responseHeaders.getHeaderNames()) { for (String headerValue : responseHeaders.get(header)) { if(buffer.remaining() < header.length() + headerValue.length() + 6) { //if there is not enough room in the buffer we need to allocate more buffer.flip(); if(byteBuffers == null) { byteBuffers = new PooledByteBuffer[2]; byteBuffers[0] = pooled; } else { PooledByteBuffer[] old = byteBuffers; byteBuffers = new PooledByteBuffer[old.length + 1]; System.arraycopy(old, 0, byteBuffers, 0, old.length); } pooled = pool.allocate(); byteBuffers[byteBuffers.length - 1] = pooled; buffer = pooled.getBuffer(); } Integer headerCode = HEADER_MAP.get(header); if (headerCode != null) { putInt(buffer, headerCode); } else { putHttpString(buffer, header); } putString(buffer, headerValue); } } if(byteBuffers == null) { int dataLength = buffer.position() - 4; buffer.put(2, (byte) ((dataLength >> 8) & 0xFF)); buffer.put(3, (byte) (dataLength & 0xFF)); buffer.flip(); queueFrame(new PooledBufferFrameCallback(pooled), buffer); } else { ByteBuffer[] bufs = new ByteBuffer[byteBuffers.length]; for(int i = 0; i < bufs.length; ++i) { bufs[i] = byteBuffers[i].getBuffer(); } int dataLength = (int) (Buffers.remaining(bufs) - 4); bufs[0].put(2, (byte) ((dataLength >> 8) & 0xFF)); bufs[0].put(3, (byte) (dataLength & 0xFF)); buffer.flip(); queueFrame(new PooledBuffersFrameCallback(byteBuffers), bufs); } state &= ~FLAG_START; } } @Override protected void queueCloseFrames() { processAJPHeader(); final ByteBuffer buffer = exchange.isPersistent() ? CLOSE_FRAME_PERSISTENT.duplicate() : CLOSE_FRAME_NON_PERSISTENT.duplicate(); queueFrame(null, buffer); } public int write(final ByteBuffer src) throws IOException { if(queuedDataLength() > 0) { //if there is data in the queue we flush and return //otherwise the queue can grow indefinitely if(!flushQueuedData()) { return 0; } } processAJPHeader(); if (headRequest) { int remaining = src.remaining(); src.position(src.position() + remaining); return remaining; } int limit = src.limit(); try { int maxData = exchange.getConnection().getUndertowOptions().get(UndertowOptions.MAX_AJP_PACKET_SIZE, DEFAULT_MAX_DATA_SIZE) - 8; if (src.remaining() > maxData) { src.limit(src.position() + maxData); } final int writeSize = src.remaining(); final ByteBuffer[] buffers = createHeader(src); int toWrite = 0; for (ByteBuffer buffer : buffers) { toWrite += buffer.remaining(); } final int originalPayloadSize = writeSize; long r = 0; do { r = super.write(buffers, 0, buffers.length); toWrite -= r; if (r == -1) { throw new ClosedChannelException(); } else if (r == 0) { //we need to copy all the remaining bytes //TODO: this assumes the buffer is big enough PooledByteBuffer newPooledBuffer = pool.allocate(); while (src.hasRemaining()) { newPooledBuffer.getBuffer().put(src); } newPooledBuffer.getBuffer().flip(); ByteBuffer[] savedBuffers = new ByteBuffer[3]; savedBuffers[0] = buffers[0]; savedBuffers[1] = newPooledBuffer.getBuffer(); savedBuffers[2] = buffers[2]; queueFrame(new PooledBufferFrameCallback(newPooledBuffer), savedBuffers); return originalPayloadSize; } } while (toWrite > 0); return originalPayloadSize; } catch (IOException | RuntimeException e) { IoUtils.safeClose(exchange.getConnection()); throw e; } finally { src.limit(limit); } } private ByteBuffer[] createHeader(final ByteBuffer src) { int remaining = src.remaining(); int chunkSize = remaining + 4; byte[] header = new byte[7]; header[0] = (byte) 'A'; header[1] = (byte) 'B'; header[2] = (byte) ((chunkSize >> 8) & 0xFF); header[3] = (byte) (chunkSize & 0xFF); header[4] = (byte) (3 & 0xFF); header[5] = (byte) ((remaining >> 8) & 0xFF); header[6] = (byte) (remaining & 0xFF); byte[] footer = new byte[1]; footer[0] = 0; final ByteBuffer[] buffers = new ByteBuffer[3]; buffers[0] = ByteBuffer.wrap(header); buffers[1] = src; buffers[2] = ByteBuffer.wrap(footer); return buffers; } public long write(final ByteBuffer[] srcs) throws IOException { return write(srcs, 0, srcs.length); } public long write(final ByteBuffer[] srcs, final int offset, final int length) throws IOException { long total = 0; for (int i = offset; i < offset + length; ++i) { while (srcs[i].hasRemaining()) { int written = write(srcs[i]); if (written == 0) { return total; } total += written; } } return total; } public long transferFrom(final FileChannel src, final long position, final long count) throws IOException { return src.transferTo(position, count, new ConduitWritableByteChannel(this)); } public long transferFrom(final StreamSourceChannel source, final long count, final ByteBuffer throughBuffer) throws IOException { return IoUtils.transfer(source, count, throughBuffer, new ConduitWritableByteChannel(this)); } @Override protected void finished() { if (finishListener != null) { finishListener.handleEvent(this); } } @Override public void setWriteReadyHandler(WriteReadyHandler handler) { next.setWriteReadyHandler(new AjpServerWriteReadyHandler(handler)); } public void suspendWrites() { log.trace("suspend"); state &= ~FLAG_WRITE_RESUMED; if (allAreClear(state, FLAG_WRITE_READ_BODY_CHUNK_FROM_LISTENER)) { next.suspendWrites(); } } public void resumeWrites() { log.trace("resume"); state |= FLAG_WRITE_RESUMED; next.resumeWrites(); } public boolean flush() throws IOException { processAJPHeader(); if(allAreClear(state, FLAG_FLUSH_QUEUED) && !isWritesTerminated()) { queueFrame(new FrameCallBack() { @Override public void done() { state &= ~FLAG_FLUSH_QUEUED; } @Override public void failed(IOException e) { } }, FLUSH_PACKET.duplicate()); state |= FLAG_FLUSH_QUEUED; } return flushQueuedData(); } public boolean isWriteResumed() { return anyAreSet(state, FLAG_WRITE_RESUMED); } public void wakeupWrites() { log.trace("wakeup"); state |= FLAG_WRITE_RESUMED; next.wakeupWrites(); } @Override protected void doTerminateWrites() throws IOException { try { if (!exchange.isPersistent()) { next.terminateWrites(); } state |= FLAG_WRITE_SHUTDOWN; } catch (IOException | RuntimeException e) { IoUtils.safeClose(exchange.getConnection()); throw e; } } @Override public boolean isWriteShutdown() { return super.isWriteShutdown() || anyAreSet(state, FLAG_WRITE_SHUTDOWN); } boolean doGetRequestBodyChunk(ByteBuffer buffer, final AjpServerRequestConduit requestChannel) throws IOException { //first attempt to just write out the buffer //if there are other frames queued they will be written out first if(isWriteShutdown()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } super.write(buffer); if (buffer.hasRemaining()) { //write it out in a listener this.state |= FLAG_WRITE_READ_BODY_CHUNK_FROM_LISTENER; queueFrame(new FrameCallBack() { @Override public void done() { state &= ~FLAG_WRITE_READ_BODY_CHUNK_FROM_LISTENER; if (allAreClear(state, FLAG_WRITE_RESUMED)) { next.suspendWrites(); } } @Override public void failed(IOException e) { requestChannel.setReadBodyChunkError(e); } }, buffer); next.resumeWrites(); return false; } return true; } private final class AjpServerWriteReadyHandler implements WriteReadyHandler { private final WriteReadyHandler delegate; private AjpServerWriteReadyHandler(WriteReadyHandler delegate) { this.delegate = delegate; } @Override public void writeReady() { if (anyAreSet(state, FLAG_WRITE_READ_BODY_CHUNK_FROM_LISTENER)) { try { flushQueuedData(); } catch (IOException e) { log.debug("Error flushing when doing async READ_BODY_CHUNK flush", e); } } if (anyAreSet(state, FLAG_WRITE_RESUMED)) { delegate.writeReady(); } } @Override public void forceTermination() { delegate.forceTermination(); } @Override public void terminated() { delegate.terminated(); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/ajp/SecurityActions.java000066400000000000000000000027301420065311100322520ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.ajp; import static java.lang.System.getProperty; import static java.lang.System.getSecurityManager; import static java.security.AccessController.doPrivileged; import java.security.PrivilegedAction; /** * Security actions to access system environment information. No methods in * this class are to be made public under any circumstances! */ final class SecurityActions { private SecurityActions() { // forbidden inheritance } static String getSystemProperty(final String key) { return getSecurityManager() == null ? getProperty(key) : doPrivileged(new PrivilegedAction() { @Override public String run() { return getProperty(key); } }); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/framed/000077500000000000000000000000001420065311100267415ustar00rootroot00000000000000AbstractFramedChannel.java000066400000000000000000001351441420065311100337100ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/framed/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.framed; import static org.xnio.IoUtils.safeClose; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Deque; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import org.xnio.Buffers; import org.xnio.ChannelExceptionHandler; import org.xnio.ChannelListener; import org.xnio.ChannelListener.Setter; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.Option; import org.xnio.OptionMap; import org.xnio.StreamConnection; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.CloseableChannel; import org.xnio.channels.ConnectedChannel; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; import org.xnio.channels.SuspendableWriteChannel; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.UndertowOptions; import io.undertow.conduits.IdleTimeoutConduit; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import io.undertow.util.ReferenceCountedPooled; /** * A {@link org.xnio.channels.ConnectedChannel} which can be used to send and receive Frames. *

* This provides a common base for framed protocols such as websockets and SPDY * * @author Stuart Douglas */ public abstract class AbstractFramedChannel, R extends AbstractFramedStreamSourceChannel, S extends AbstractFramedStreamSinkChannel> implements ConnectedChannel { /** * The maximum number of buffers we will queue before suspending reads and * waiting for the buffers to be consumed * * TODO: make the configurable */ private final int maxQueuedBuffers; private final StreamConnection channel; private final IdleTimeoutConduit idleTimeoutConduit; private final ChannelListener.SimpleSetter closeSetter; private final ChannelListener.SimpleSetter receiveSetter; private final ByteBufferPool bufferPool; /** * Frame priority implementation. This is used to determine the order in which frames get sent */ private final FramePriority framePriority; /** * List of frames that are ready to send */ private final List pendingFrames = new LinkedList<>(); /** * Frames that are not yet read to send. */ private final Deque heldFrames = new ArrayDeque<>(); /** * new frames to be sent. These will be added to either the pending or held frames list * depending on the {@link #framePriority} implementation in use. */ private final Deque newFrames = new LinkedBlockingDeque<>(); private volatile long frameDataRemaining; private volatile R receiver; private volatile boolean receivesSuspendedByUser = true; private volatile boolean receivesSuspendedTooManyQueuedMessages = false; private volatile boolean receivesSuspendedTooManyBuffers = false; @SuppressWarnings("unused") private volatile int readsBroken = 0; @SuppressWarnings("unused") private volatile int writesBroken = 0; private static final AtomicIntegerFieldUpdater readsBrokenUpdater = AtomicIntegerFieldUpdater.newUpdater(AbstractFramedChannel.class, "readsBroken"); private static final AtomicIntegerFieldUpdater writesBrokenUpdater = AtomicIntegerFieldUpdater.newUpdater(AbstractFramedChannel.class, "writesBroken"); private volatile ReferenceCountedPooled readData = null; private final List> closeTasks = new CopyOnWriteArrayList<>(); private volatile boolean flushingSenders = false; private boolean partialRead = false; @SuppressWarnings("unused") private volatile int outstandingBuffers; private static final AtomicIntegerFieldUpdater outstandingBuffersUpdater = AtomicIntegerFieldUpdater.newUpdater(AbstractFramedChannel.class, "outstandingBuffers"); private final LinkedBlockingDeque taskRunQueue = new LinkedBlockingDeque<>(); private final Runnable taskRunQueueRunnable = new Runnable() { @Override public void run() { Runnable runnable; while ((runnable = taskRunQueue.poll()) != null) { runnable.run(); } } }; private final OptionMap settings; /** * If this is true then the flush() method must be called to queue writes. This is provided to support batching */ private volatile boolean requireExplicitFlush = false; private volatile boolean readChannelDone = false; private final int queuedFrameHighWaterMark; private final int queuedFrameLowWaterMark; private final ReferenceCountedPooled.FreeNotifier freeNotifier = new ReferenceCountedPooled.FreeNotifier() { @Override public void freed() { int res = outstandingBuffersUpdater.decrementAndGet(AbstractFramedChannel.this); if (!receivesSuspendedByUser && res == maxQueuedBuffers - 1) { //we need to do the resume in the IO thread, as there is a risk of deadlock otherwise, as the calling thread is an application thread //and may hold a lock on a stream source channel, see UNDERTOW-1312 getIoThread().execute(new Runnable() { @Override public void run() { synchronized (AbstractFramedChannel.this) { if (outstandingBuffersUpdater.get(AbstractFramedChannel.this) < maxQueuedBuffers) { if (UndertowLogger.REQUEST_IO_LOGGER.isTraceEnabled()) { UndertowLogger.REQUEST_IO_LOGGER.tracef("Resuming reads on %s as buffers have been consumed", AbstractFramedChannel.this); } new UpdateResumeState(null, false, null).run(); } } } }); } } }; private static final ChannelListener DRAIN_LISTENER = new ChannelListener() { @Override public void handleEvent(AbstractFramedChannel channel) { try { AbstractFramedStreamSourceChannel stream = channel.receive(); if(stream != null) { UndertowLogger.REQUEST_IO_LOGGER.debugf("Draining channel %s as no receive listener has been set", stream); stream.getReadSetter().set(ChannelListeners.drainListener(Long.MAX_VALUE, null, null)); stream.wakeupReads(); } } catch (IOException | RuntimeException | Error e) { IoUtils.safeClose(channel); } } }; /** * Create a new {@link io.undertow.server.protocol.framed.AbstractFramedChannel} * 8 * @param connectedStreamChannel The {@link org.xnio.channels.ConnectedStreamChannel} over which the Frames should get send and received. * Be aware that it already must be "upgraded". * @param bufferPool The {@link ByteBufferPool} which will be used to acquire {@link ByteBuffer}'s from. * @param framePriority * @param settings The settings */ protected AbstractFramedChannel(final StreamConnection connectedStreamChannel, ByteBufferPool bufferPool, FramePriority framePriority, final PooledByteBuffer readData, OptionMap settings) { this.framePriority = framePriority; this.maxQueuedBuffers = settings.get(UndertowOptions.MAX_QUEUED_READ_BUFFERS, 10); this.settings = settings; if (readData != null) { if(readData.getBuffer().hasRemaining()) { this.readData = new ReferenceCountedPooled(readData, 1); } else { readData.close(); } } if(bufferPool == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("bufferPool"); } if(connectedStreamChannel == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("connectedStreamChannel"); } IdleTimeoutConduit idle = createIdleTimeoutChannel(connectedStreamChannel); connectedStreamChannel.getSourceChannel().setConduit(idle); connectedStreamChannel.getSinkChannel().setConduit(idle); this.idleTimeoutConduit = idle; this.channel = connectedStreamChannel; this.bufferPool = bufferPool; closeSetter = new ChannelListener.SimpleSetter<>(); receiveSetter = new ChannelListener.SimpleSetter<>(); channel.getSourceChannel().getReadSetter().set(null); channel.getSourceChannel().suspendReads(); channel.getSourceChannel().getReadSetter().set(new FrameReadListener()); connectedStreamChannel.getSinkChannel().getWriteSetter().set(new FrameWriteListener()); FrameCloseListener closeListener = new FrameCloseListener(); connectedStreamChannel.getSinkChannel().getCloseSetter().set(closeListener); connectedStreamChannel.getSourceChannel().getCloseSetter().set(closeListener); this.queuedFrameHighWaterMark = settings.get(UndertowOptions.QUEUED_FRAMES_HIGH_WATER_MARK, 50); this.queuedFrameLowWaterMark = settings.get(UndertowOptions.QUEUED_FRAMES_LOW_WATER_MARK, 10); } protected IdleTimeoutConduit createIdleTimeoutChannel(StreamConnection connectedStreamChannel) { return new IdleTimeoutConduit(connectedStreamChannel); } void runInIoThread(Runnable task) { this.taskRunQueue.add(task); try { getIoThread().execute(taskRunQueueRunnable); } catch (RejectedExecutionException e) { //thread is shutting down ShutdownFallbackExecutor.execute(taskRunQueueRunnable); } } /** * Get the buffer pool for this connection. * * @return the buffer pool for this connection */ public ByteBufferPool getBufferPool() { return bufferPool; } @Override public SocketAddress getLocalAddress() { return channel.getLocalAddress(); } @Override public A getLocalAddress(Class type) { return channel.getLocalAddress(type); } @Override public XnioWorker getWorker() { return channel.getWorker(); } @Override public XnioIoThread getIoThread() { return channel.getIoThread(); } @Override public boolean supportsOption(Option option) { return channel.supportsOption(option); } @Override public T getOption(Option option) throws IOException { return channel.getOption(option); } @Override public T setOption(Option option, T value) throws IOException { return channel.setOption(option, value); } @Override public boolean isOpen() { return channel.isOpen(); } @Override public SocketAddress getPeerAddress() { return channel.getPeerAddress(); } @Override public A getPeerAddress(Class type) { return channel.getPeerAddress(type); } /** * Get the source address of the Channel. * * @return the source address of the Channel */ public InetSocketAddress getSourceAddress() { return getPeerAddress(InetSocketAddress.class); } /** * Get the destination address of the Channel. * * @return the destination address of the Channel */ public InetSocketAddress getDestinationAddress() { return getLocalAddress(InetSocketAddress.class); } /** * Receive method, returns null if no frame is ready. Otherwise returns a * channel that can be used to read the frame contents. *

* Calling this method can also have the side effect of making additional data available to * existing source channels. In general if you suspend receives or don't have some other way * of calling this method then it can prevent frame channels for being fully consumed. */ public synchronized R receive() throws IOException { ReferenceCountedPooled pooled = this.readData; if (readChannelDone && receiver == null) { //we have received the last frame, we just shut down and return //it would probably make more sense to have the last channel responsible for this //however it is much simpler just to have it here if(pooled != null) { pooled.close(); readData = null; } channel.getSourceChannel().suspendReads(); channel.getSourceChannel().shutdownReads(); return null; } partialRead = false; boolean requiresReinvoke = false; int reinvokeDataRemaining = 0; boolean hasData = false; if (pooled == null) { pooled = allocateReferenceCountedBuffer(); if (pooled == null) { return null; } } else if(pooled.isFreed()) { //we attempt to re-used an existing buffer if(!pooled.tryUnfree()) { pooled = allocateReferenceCountedBuffer(); if (pooled == null) { return null; } } pooled.getBuffer().clear(); } else { hasData = pooled.getBuffer().hasRemaining(); pooled.getBuffer().compact(); } boolean forceFree = false; int read = 0; try { read = channel.getSourceChannel().read(pooled.getBuffer()); if (read == 0 && !hasData) { //no data, we just free the buffer forceFree = true; return null; } else if (read == -1 && !hasData) { forceFree = true; readChannelDone = true; lastDataRead(); return null; } else if(isLastFrameReceived() && frameDataRemaining == 0) { //we got data, although we should have received the last frame forceFree = true; markReadsBroken(new ClosedChannelException()); } pooled.getBuffer().flip(); if(read == -1) { requiresReinvoke = true; reinvokeDataRemaining = pooled.getBuffer().remaining(); } if (frameDataRemaining > 0) { if (frameDataRemaining >= pooled.getBuffer().remaining()) { frameDataRemaining -= pooled.getBuffer().remaining(); if(receiver != null) { //we still create a pooled view, this means that if the buffer is still active we can re-used it //which prevents attacks based on sending lots of small fragments PooledByteBuffer frameData = pooled.createView(); receiver.dataReady(null, frameData); } else { //we are dropping a frame pooled.close(); readData = null; } if(frameDataRemaining == 0) { receiver = null; } return null; } else { PooledByteBuffer frameData = pooled.createView((int) frameDataRemaining); frameDataRemaining = 0; if(receiver != null) { receiver.dataReady(null, frameData); } else{ //we are dropping the frame frameData.close(); } receiver = null; } //if we read data into a frame we just return immediately, even if there is more remaining //see https://issues.jboss.org/browse/UNDERTOW-410 //basically if we don't do this we loose some message ordering semantics //as the second message may be processed before the first one //this is problematic for HTTPS, where the read listener may also be invoked by a queued task //and not by the selector mechanism return null; } FrameHeaderData data = parseFrame(pooled.getBuffer()); if (data != null) { PooledByteBuffer frameData; if (data.getFrameLength() >= pooled.getBuffer().remaining()) { frameDataRemaining = data.getFrameLength() - pooled.getBuffer().remaining(); frameData = pooled.createView(); pooled.getBuffer().position(pooled.getBuffer().limit()); } else { frameData = pooled.createView((int) data.getFrameLength()); } AbstractFramedStreamSourceChannel existing = data.getExistingChannel(); if (existing != null) { if (data.getFrameLength() > frameData.getBuffer().remaining()) { receiver = (R) existing; } existing.dataReady(data, frameData); if(isLastFrameReceived()) { handleLastFrame(existing); } return null; } else { boolean moreData = data.getFrameLength() > frameData.getBuffer().remaining(); R newChannel = createChannel(data, frameData); if (newChannel != null) { if (moreData) { receiver = newChannel; } if(isLastFrameReceived()) { handleLastFrame(newChannel); } } else { frameData.close(); } return newChannel; } } else { //we set partial read to true so the read listener knows not to immediately call receive again partialRead = true; } return null; } catch (IOException|RuntimeException|Error e) { //something has code wrong with parsing, close the read side //we don't close the write side, as the underlying implementation will most likely want to send an error markReadsBroken(e); forceFree = true; throw e; }finally { //if the receive caused the channel to break the close listener may be have been called //which will make readData null if (readData != null) { if (!pooled.getBuffer().hasRemaining() || forceFree) { if(pooled.getBuffer().capacity() < 1024 || forceFree) { //if there is less than 1k left we don't allow it to be re-aquired readData = null; } //even though this is freed we may un-free it if we get a new packet //this prevents many small reads resulting in a large number of allocated buffers pooled.close(); } } if(requiresReinvoke) { if(pooled != null && !pooled.isFreed()) { if(pooled.getBuffer().remaining() == reinvokeDataRemaining) { pooled.close(); readData = null; UndertowLogger.REQUEST_IO_LOGGER.debugf("Partial message read before connection close %s", this); } } channel.getSourceChannel().wakeupReads(); } } } /** * Called when the last frame has been received (note that their may still be data from the last frame than needs to be read) * @param newChannel The channel that received the last frame */ private void handleLastFrame(AbstractFramedStreamSourceChannel newChannel) { //make a defensive copy Set> receivers = new HashSet<>(getReceivers()); for(AbstractFramedStreamSourceChannel r : receivers) { if(r != newChannel) { r.markStreamBroken(); } } } private ReferenceCountedPooled allocateReferenceCountedBuffer() { if(maxQueuedBuffers > 0) { int expect; do { expect = outstandingBuffersUpdater.get(this); if (expect == maxQueuedBuffers) { synchronized (this) { //we need to re-read in a sync block, to prevent races expect = outstandingBuffersUpdater.get(this); if (expect == maxQueuedBuffers) { if (UndertowLogger.REQUEST_IO_LOGGER.isTraceEnabled()) { UndertowLogger.REQUEST_IO_LOGGER.tracef("Suspending reads on %s due to too many outstanding buffers", this); } getIoThread().execute(new UpdateResumeState(null, true, null)); return null; } } } } while (!outstandingBuffersUpdater.compareAndSet(this, expect, expect + 1)); } PooledByteBuffer buf = bufferPool.allocate(); return this.readData = new ReferenceCountedPooled(buf, 1, maxQueuedBuffers > 0 ? freeNotifier : null); } /** * Method than is invoked when read() returns -1. */ protected void lastDataRead() { } /** * Method that creates the actual stream source channel implementation that is in use. * * @param frameHeaderData The header data, as returned by {@link #parseFrame(java.nio.ByteBuffer)} * @param frameData Any additional data for the frame that has already been read. This may not be the complete frame contents * @return A new stream source channel */ protected abstract R createChannel(FrameHeaderData frameHeaderData, PooledByteBuffer frameData) throws IOException; /** * Attempts to parse an incoming frame header from the data in the buffer. * * @param data The data that has been read from the channel * @return The frame header data, or null if the data was incomplete * @throws IOException If the data could not be parsed. */ protected abstract FrameHeaderData parseFrame(ByteBuffer data) throws IOException; protected synchronized void recalculateHeldFrames() throws IOException { if (!heldFrames.isEmpty()) { framePriority.frameAdded(null, pendingFrames, heldFrames); flushSenders(); } } /** * Flushes all ready stream sink conduits to the channel. *

* Frames will be batched up, to allow them all to be written out via a gathering * write. The {@link #framePriority} implementation will be invoked to decide which * frames are eligible for sending and in what order. */ protected synchronized void flushSenders() { if(flushingSenders) { throw UndertowMessages.MESSAGES.recursiveCallToFlushingSenders(); } flushingSenders = true; try { int toSend = 0; S frame; while ((frame = newFrames.poll()) != null) { frame.preWrite(); if (framePriority.insertFrame(frame, pendingFrames)) { if (!heldFrames.isEmpty()) { framePriority.frameAdded(frame, pendingFrames, heldFrames); } } else { heldFrames.add(frame); } } boolean finalFrame = false; ListIterator it = pendingFrames.listIterator(); while (it.hasNext()) { S sender = it.next(); if (sender.isReadyForFlush()) { ++toSend; } else { break; } if (sender.isLastFrame()) { finalFrame = true; } } if (toSend == 0) { //if there is nothing to send we just attempt a flush on the underlying channel try { if(channel.getSinkChannel().flush()) { channel.getSinkChannel().suspendWrites(); } } catch (Throwable e) { safeClose(channel); markWritesBroken(e); } return; } ByteBuffer[] data = new ByteBuffer[toSend * 3]; int j = 0; it = pendingFrames.listIterator(); try { while (j < toSend) { S next = it.next(); //todo: rather than adding empty buffers just store the offsets SendFrameHeader frameHeader = next.getFrameHeader(); PooledByteBuffer frameHeaderByteBuffer = frameHeader.getByteBuffer(); ByteBuffer frameTrailerBuffer = frameHeader.getTrailer(); data[j * 3] = frameHeaderByteBuffer != null ? frameHeaderByteBuffer.getBuffer() : Buffers.EMPTY_BYTE_BUFFER; data[(j * 3) + 1] = next.getBuffer() == null ? Buffers.EMPTY_BYTE_BUFFER : next.getBuffer(); data[(j * 3) + 2] = frameTrailerBuffer != null ? frameTrailerBuffer : Buffers.EMPTY_BYTE_BUFFER; ++j; } long toWrite = Buffers.remaining(data); long res; do { res = channel.getSinkChannel().write(data); toWrite -= res; } while (res > 0 && toWrite > 0); int max = toSend; while (max > 0) { S sinkChannel = pendingFrames.get(0); PooledByteBuffer frameHeaderByteBuffer = sinkChannel.getFrameHeader().getByteBuffer(); ByteBuffer frameTrailerBuffer = sinkChannel.getFrameHeader().getTrailer(); if (frameHeaderByteBuffer != null && frameHeaderByteBuffer.getBuffer().hasRemaining() || sinkChannel.getBuffer() != null && sinkChannel.getBuffer().hasRemaining() || frameTrailerBuffer != null && frameTrailerBuffer.hasRemaining()) { break; } sinkChannel.flushComplete(); pendingFrames.remove(sinkChannel); max--; } if (!pendingFrames.isEmpty() || !channel.getSinkChannel().flush()) { channel.getSinkChannel().resumeWrites(); } else { channel.getSinkChannel().suspendWrites(); } if (pendingFrames.isEmpty() && finalFrame) { //all data has been sent. Close gracefully channel.getSinkChannel().shutdownWrites(); if (!channel.getSinkChannel().flush()) { channel.getSinkChannel().setWriteListener(ChannelListeners.flushingChannelListener(null, null)); channel.getSinkChannel().resumeWrites(); } } else if (pendingFrames.size() > queuedFrameHighWaterMark) { new UpdateResumeState(null, null, true).run(); } else if (receivesSuspendedTooManyQueuedMessages && pendingFrames.size() < queuedFrameLowWaterMark) { new UpdateResumeState(null, null, false).run(); } } catch (IOException|RuntimeException|Error e) { safeClose(channel); markWritesBroken(e); } } finally { flushingSenders = false; if(!newFrames.isEmpty()) { runInIoThread(new Runnable() { @Override public void run() { flushSenders(); } }); } } } void awaitWritable() throws IOException { this.channel.getSinkChannel().awaitWritable(); } void awaitWritable(long time, TimeUnit unit) throws IOException { this.channel.getSinkChannel().awaitWritable(time, unit); } /** * Queues a new frame to be sent, and attempts a flush if this is the first frame in the new frame queue. *

* Depending on the {@link FramePriority} implementation in use the channel may or may not be added to the actual * pending queue * * @param channel The channel */ protected void queueFrame(final S channel) throws IOException { assert !newFrames.contains(channel); if (isWritesBroken() || !this.channel.getSinkChannel().isOpen() || channel.isBroken() || !channel.isOpen()) { IoUtils.safeClose(channel); throw UndertowMessages.MESSAGES.channelIsClosed(); } newFrames.add(channel); if (!requireExplicitFlush || channel.isBufferFull()) { flush(); } } public void flush() { if (!flushingSenders) { if(channel.getIoThread() == Thread.currentThread()) { flushSenders(); } else { runInIoThread(new Runnable() { @Override public void run() { flushSenders(); } }); } } } /** * Returns true if the protocol specific final frame has been received. * * @return true If the last frame has been received */ protected abstract boolean isLastFrameReceived(); /** * @return true If the last frame has been sent */ protected abstract boolean isLastFrameSent(); /** * Method that is invoked when the read side of the channel is broken. This generally happens on a protocol error. */ protected abstract void handleBrokenSourceChannel(Throwable e); /** * Method that is invoked when then write side of a channel is broken. This generally happens on a protocol error. */ protected abstract void handleBrokenSinkChannel(Throwable e); /** * Return the {@link org.xnio.ChannelListener.Setter} which will holds the {@link org.xnio.ChannelListener} that gets notified once a frame was * received. */ public Setter getReceiveSetter() { return receiveSetter; } /** * Suspend the receive of new frames via {@link #receive()} */ public synchronized void suspendReceives() { receivesSuspendedByUser = true; getIoThread().execute(new UpdateResumeState(true, null, null)); } /** * Resume the receive of new frames via {@link #receive()} */ public synchronized void resumeReceives() { receivesSuspendedByUser = false; getIoThread().execute(new UpdateResumeState(false, null, null)); } private void doResume() { //NOTE: this should not require syncing with below part final ReferenceCountedPooled localReadData = this.readData; if (localReadData != null && !localReadData.isFreed()) { channel.getSourceChannel().wakeupReads(); } else { channel.getSourceChannel().resumeReads(); } } public boolean isReceivesResumed() { return !receivesSuspendedByUser; } /** * Forcibly closes the {@link io.undertow.server.protocol.framed.AbstractFramedChannel}. */ @Override public void close() throws IOException { if (UndertowLogger.REQUEST_IO_LOGGER.isTraceEnabled()) { UndertowLogger.REQUEST_IO_LOGGER.tracef(new ClosedChannelException(), "Channel %s is being closed", this); } safeClose(channel); final ReferenceCountedPooled localReadData = this.readData; if (localReadData != null) { localReadData.close(); this.readData = null; } closeSubChannels(); } @Override public Setter getCloseSetter() { return closeSetter; } /** * Called when a source sub channel fails to fulfil its contract, and leaves the channel in an inconsistent state. *

* The underlying read side will be forcibly closed. * * @param cause The possibly null cause */ @SuppressWarnings({"unchecked", "rawtypes"}) protected void markReadsBroken(Throwable cause) { if (readsBrokenUpdater.compareAndSet(this, 0, 1)) { if(UndertowLogger.REQUEST_IO_LOGGER.isDebugEnabled()) { UndertowLogger.REQUEST_IO_LOGGER.debugf(new ClosedChannelException(), "Marking reads broken on channel %s", this); } if(receiver != null) { receiver.markStreamBroken(); } for(AbstractFramedStreamSourceChannel r : new ArrayList<>(getReceivers())) { r.markStreamBroken(); } handleBrokenSourceChannel(cause); safeClose(channel.getSourceChannel()); closeSubChannels(); } } /** * Method that is called when the channel is being forcibly closed, and all sub stream sink/source * channels should also be forcibly closed. */ protected abstract void closeSubChannels(); /** * Called when a sub channel fails to fulfil its contract, and leaves the channel in an inconsistent state. *

* The underlying channel will be closed, and any sub channels that have writes resumed will have their * listeners notified. It is expected that these listeners will then attempt to use the channel, and their standard * error handling logic will take over. * * @param cause The possibly null cause */ @SuppressWarnings({"unchecked", "rawtypes"}) protected void markWritesBroken(Throwable cause) { if (writesBrokenUpdater.compareAndSet(this, 0, 1)) { if(UndertowLogger.REQUEST_IO_LOGGER.isDebugEnabled()) { UndertowLogger.REQUEST_IO_LOGGER.debugf(new ClosedChannelException(), "Marking writes broken on channel %s", this); } handleBrokenSinkChannel(cause); safeClose(channel.getSinkChannel()); synchronized (this) { for (final S channel : pendingFrames) { channel.markBroken(); } pendingFrames.clear(); for (final S channel : newFrames) { channel.markBroken(); } newFrames.clear(); for (final S channel : heldFrames) { channel.markBroken(); } heldFrames.clear(); } } } protected boolean isWritesBroken() { return writesBrokenUpdater.get(this) != 0; } protected boolean isReadsBroken() { return readsBrokenUpdater.get(this) != 0; } void resumeWrites() { channel.getSinkChannel().resumeWrites(); } void suspendWrites() { channel.getSinkChannel().suspendWrites(); } void wakeupWrites() { channel.getSinkChannel().wakeupWrites(); } StreamSourceChannel getSourceChannel() { return channel.getSourceChannel(); } void notifyFrameReadComplete(AbstractFramedStreamSourceChannel channel) { } /** * {@link org.xnio.ChannelListener} which delegates the read notification to the appropriate listener */ private final class FrameReadListener implements ChannelListener { @SuppressWarnings({"unchecked", "rawtypes"}) @Override public void handleEvent(final StreamSourceChannel channel) { //clear the task queue before reading Runnable runnable; while ((runnable = taskRunQueue.poll()) != null) { runnable.run(); } final R receiver = AbstractFramedChannel.this.receiver; if ((readChannelDone || isReadsSuspended()) && receiver == null) { channel.suspendReads(); return; } else { ChannelListener listener = receiveSetter.get(); if (listener == null) { listener = DRAIN_LISTENER; } UndertowLogger.REQUEST_IO_LOGGER.tracef("Invoking receive listener: %s - receiver: %s", listener, receiver); ChannelListeners.invokeChannelListener(AbstractFramedChannel.this, listener); } final boolean partialRead; synchronized (AbstractFramedChannel.this) { partialRead = AbstractFramedChannel.this.partialRead; } final ReferenceCountedPooled localReadData = readData; if (localReadData != null && !localReadData.isFreed() && channel.isOpen() && !partialRead) { try { runInIoThread(new Runnable() { @Override public void run() { ChannelListeners.invokeChannelListener(channel, FrameReadListener.this); } }); } catch (RejectedExecutionException e) { IoUtils.safeClose(AbstractFramedChannel.this); } } synchronized (AbstractFramedChannel.this) { AbstractFramedChannel.this.partialRead = false; } } } private boolean isReadsSuspended() { return receivesSuspendedByUser || receivesSuspendedTooManyBuffers || receivesSuspendedTooManyQueuedMessages; } private class FrameWriteListener implements ChannelListener { @Override public void handleEvent(final StreamSinkChannel channel) { flushSenders(); } } /** * close listener, just goes through and activates any sub channels to make sure their listeners are invoked */ private class FrameCloseListener implements ChannelListener { private boolean sinkClosed; private boolean sourceClosed; @Override public void handleEvent(final CloseableChannel c) { if (Thread.currentThread() != c.getIoThread() && !c.getWorker().isShutdown()) { runInIoThread(new Runnable() { @Override public void run() { ChannelListeners.invokeChannelListener(c, FrameCloseListener.this); } }); return; } if(c instanceof StreamSinkChannel) { sinkClosed = true; } else if(c instanceof StreamSourceChannel) { sourceClosed = true; } final ReferenceCountedPooled localReadData = readData; if(!sourceClosed || !sinkClosed) { return; //both sides need to be closed } else if(localReadData != null && !localReadData.isFreed()) { //we make sure there is no data left to receive, if there is then we invoke the receive listener runInIoThread(new Runnable() { @Override public void run() { while (localReadData != null && !localReadData.isFreed()) { int rem = localReadData.getBuffer().remaining(); ChannelListener listener = receiveSetter.get(); if(listener == null) { listener = DRAIN_LISTENER; } ChannelListeners.invokeChannelListener(AbstractFramedChannel.this, listener); if(!AbstractFramedChannel.this.isOpen()) { break; } if (localReadData != null && rem == localReadData.getBuffer().remaining()) { break;//make sure we are making progress } } handleEvent(c); } }); return; } R receiver = AbstractFramedChannel.this.receiver; try { if (receiver != null && receiver.isOpen() && receiver.isReadResumed()) { ChannelListeners.invokeChannelListener(receiver, ((SimpleSetter) receiver.getReadSetter()).get()); } final List pendingFrames; final List newFrames; final List heldFrames; final List> receivers; synchronized (AbstractFramedChannel.this) { pendingFrames = new ArrayList<>(AbstractFramedChannel.this.pendingFrames); newFrames = new ArrayList<>(AbstractFramedChannel.this.newFrames); heldFrames = new ArrayList<>(AbstractFramedChannel.this.heldFrames); receivers = new ArrayList<>(getReceivers()); } for (final S channel : pendingFrames) { //if this was a clean shutdown there should not be any senders channel.markBroken(); } for (final S channel : newFrames) { //if this was a clean shutdown there should not be any senders channel.markBroken(); } for (final S channel : heldFrames) { //if this was a clean shutdown there should not be any senders channel.markBroken(); } for (AbstractFramedStreamSourceChannel r : receivers) { IoUtils.safeClose(r); } } finally { try { for (ChannelListener task : closeTasks) { ChannelListeners.invokeChannelListener((C) AbstractFramedChannel.this, task); } } finally { synchronized (AbstractFramedChannel.this) { closeSubChannels(); if (localReadData != null) { localReadData.close(); readData = null; } } ChannelListeners.invokeChannelListener((C) AbstractFramedChannel.this, closeSetter.get()); } } } } protected abstract Collection> getReceivers(); public void setIdleTimeout(long timeout) { idleTimeoutConduit.setIdleTimeout(timeout); } public long getIdleTimeout() { return idleTimeoutConduit.getIdleTimeout(); } protected FramePriority getFramePriority() { return framePriority; } public void addCloseTask(final ChannelListener task) { closeTasks.add(task); } @Override public String toString() { final StringBuilder stringBuilder = new StringBuilder(150); stringBuilder.append(getClass().getSimpleName()) .append(" peer ") .append(channel.getPeerAddress()) .append(" local ") .append(channel.getLocalAddress()) .append("[ "); synchronized (this) { stringBuilder.append((receiver == null ? "No Receiver" : receiver.toString())) .append(" ") .append(pendingFrames.toString()) .append(" -- ") .append(heldFrames.toString()) .append(" -- ") .append(newFrames.toString()); } return stringBuilder.toString(); } protected StreamConnection getUnderlyingConnection() { return channel; } protected ChannelExceptionHandler writeExceptionHandler() { return new ChannelExceptionHandler() { @Override public void handleException(SuspendableWriteChannel channel, IOException exception) { markWritesBroken(exception); } }; } public boolean isRequireExplicitFlush() { return requireExplicitFlush; } public void setRequireExplicitFlush(boolean requireExplicitFlush) { this.requireExplicitFlush = requireExplicitFlush; } protected OptionMap getSettings() { return settings; } private class UpdateResumeState implements Runnable { private final Boolean user; private final Boolean buffers; private final Boolean frames; private UpdateResumeState(Boolean user, Boolean buffers, Boolean frames) { this.user = user; this.buffers = buffers; this.frames = frames; } @Override public void run() { if (user != null) { receivesSuspendedByUser = user; } if (buffers != null) { receivesSuspendedTooManyBuffers = buffers; } if (frames != null) { receivesSuspendedTooManyQueuedMessages = frames; } if (receivesSuspendedByUser || receivesSuspendedTooManyQueuedMessages || receivesSuspendedTooManyBuffers) { channel.getSourceChannel().suspendReads(); } else { doResume(); } } } } AbstractFramedStreamSinkChannel.java000066400000000000000000000646401420065311100357130ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/framed/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.framed; import java.io.IOException; import java.io.InterruptedIOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.connector.PooledByteBuffer; import io.undertow.util.ImmediatePooledByteBuffer; import org.xnio.Buffers; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.Option; import org.xnio.XnioExecutor; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.Channels; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.anyAreSet; /** * Framed Stream Sink Channel. *

* Thread safety notes: *

* The general contract is that this channel is only to be used by a single thread at a time. The only exception to this is * during flush. A flush will only happen when {@link #readyForFlush} is set, and while this bit is set the buffer * must not be modified. * * @author Stuart Douglas */ public abstract class AbstractFramedStreamSinkChannel, R extends AbstractFramedStreamSourceChannel, S extends AbstractFramedStreamSinkChannel> implements StreamSinkChannel { /** * The maximum timeout to wait on awaitWritable in milliseconds when not specified. */ private static final int AWAIT_WRITABLE_TIMEOUT; static { final int defaultAwaitWritableTimeout = 600000; int await_writable_timeout = AccessController.doPrivileged((PrivilegedAction) () -> Integer.getInteger("io.undertow.await_writable_timeout", defaultAwaitWritableTimeout)); AWAIT_WRITABLE_TIMEOUT = await_writable_timeout > 0? await_writable_timeout : defaultAwaitWritableTimeout; } private static final PooledByteBuffer EMPTY_BYTE_BUFFER = new ImmediatePooledByteBuffer(ByteBuffer.allocateDirect(0)); private final C channel; private final ChannelListener.SimpleSetter writeSetter = new ChannelListener.SimpleSetter<>(); private final ChannelListener.SimpleSetter closeSetter = new ChannelListener.SimpleSetter<>(); private final Object lock = new Object(); /** * the state variable, this must only be access by the thread that 'owns' the channel */ private volatile int state = 0; /** * If this channel is ready for flush, updated by multiple threads. In general it will be set by the thread * that 'owns' the channel, and cleared by the IO thread */ private volatile boolean readyForFlush; /** * If all the data has been written out and the channel has been fully flushed */ private volatile boolean fullyFlushed; /** * If the last frame has been queued. * * Note that this may not actually be the final frame in some circumstances, e.g. if the final frame * is two large to fit in the flow control window. In this case the flag may be cleared after flush is complete. */ private volatile boolean finalFrameQueued; /** * If this channel is broken, updated by multiple threads */ private volatile boolean broken; private volatile int waiterCount = 0; private volatile SendFrameHeader header; private volatile PooledByteBuffer writeBuffer; private volatile PooledByteBuffer body; private static final int STATE_CLOSED = 1; private static final int STATE_WRITES_SHUTDOWN = 1 << 1; private static final int STATE_FIRST_DATA_WRITTEN = 1 << 2; private static final int STATE_PRE_WRITE_CALLED = 1 << 3; private volatile boolean bufferFull; private volatile boolean writesResumed; @SuppressWarnings("unused") private volatile int inListenerLoop; /* keep track of successful writes to properly prevent a loop UNDERTOW-1624 */ private volatile boolean writeSucceeded; private static final AtomicIntegerFieldUpdater inListenerLoopUpdater = AtomicIntegerFieldUpdater.newUpdater(AbstractFramedStreamSinkChannel.class, "inListenerLoop"); protected AbstractFramedStreamSinkChannel(C channel) { this.channel = channel; } public long transferFrom(final FileChannel src, final long position, final long count) throws IOException { return src.transferTo(position, count, this); } public long transferFrom(final StreamSourceChannel source, final long count, final ByteBuffer throughBuffer) throws IOException { return IoUtils.transfer(source, count, throughBuffer, this); } @Override public void suspendWrites() { writesResumed = false; } /** * Returns the header for the current frame. * * This consists of the frame data, and also an integer specifying how much data is remaining in the buffer. * If this is non-zero then this method must adjust the buffers limit accordingly. * * It is expected that this will be used when limits on the size of a data frame prevent the whole buffer from * being sent at once. * * * @return The header for the current frame, or null */ final SendFrameHeader getFrameHeader() throws IOException { if (header == null) { header = createFrameHeader(); if (header == null) { header = new SendFrameHeader(0, null); } } return header; } protected SendFrameHeader createFrameHeader() throws IOException{ return null; } final void preWrite() { synchronized (lock) { if (allAreClear(state, STATE_PRE_WRITE_CALLED)) { state |= STATE_PRE_WRITE_CALLED; body = preWriteTransform(body); } } } protected PooledByteBuffer preWriteTransform(PooledByteBuffer body) { return body; } @Override public boolean isWriteResumed() { return writesResumed; } @Override public void wakeupWrites() { resumeWritesInternal(true); } @Override public void resumeWrites() { resumeWritesInternal(false); } protected void resumeWritesInternal(boolean wakeup) { boolean alreadyResumed = writesResumed; if(!wakeup && alreadyResumed) { return; } writesResumed = true; if(readyForFlush && !wakeup) { //we already have data queued to be flushed return; } if (inListenerLoopUpdater.compareAndSet(this, 0, 1)) { getChannel().runInIoThread(new Runnable() { // loopCount keeps track of runnable being invoked in a // loop without any successful write operation int loopCount = 0; @Override public void run() { try { ChannelListener listener = getWriteListener(); if (listener == null || !isWriteResumed()) { return; } if (writeSucceeded) { // reset write succeeded and loopCount writeSucceeded = false; loopCount = 0; } else if (loopCount++ == 100) { //should never happen UndertowLogger.ROOT_LOGGER.listenerNotProgressing(); IoUtils.safeClose(AbstractFramedStreamSinkChannel.this); return; } ChannelListeners.invokeChannelListener((S) AbstractFramedStreamSinkChannel.this, listener); } finally { inListenerLoopUpdater.set(AbstractFramedStreamSinkChannel.this, 0); } //if writes are shutdown or we become active then we stop looping //we stop when writes are shutdown because we can't flush until we are active //although we may be flushed as part of a batch if (writesResumed && allAreClear(state, STATE_CLOSED) && !broken && !readyForFlush && !fullyFlushed) { if (inListenerLoopUpdater.compareAndSet(AbstractFramedStreamSinkChannel.this, 0, 1)) { getIoThread().execute(this); } } } }); } } @Override public void shutdownWrites() throws IOException { // Queue prior to shutting down writes, since we might send the write buffer queueFinalFrame(); synchronized (lock) { if (anyAreSet(state, STATE_WRITES_SHUTDOWN) || broken) { return; } state |= STATE_WRITES_SHUTDOWN; } } private void queueFinalFrame() throws IOException { synchronized (lock) { if (!readyForFlush && !fullyFlushed && allAreClear(state, STATE_CLOSED) && !broken && !finalFrameQueued) { if (null == body && null != writeBuffer) { sendWriteBuffer(); } else if (null == body) { body = EMPTY_BYTE_BUFFER; } readyForFlush = true; state |= STATE_FIRST_DATA_WRITTEN; state |= STATE_WRITES_SHUTDOWN; // Mark writes as shutdown as well, since we want that set prior to queueing finalFrameQueued = true; } else return; } channel.queueFrame((S) this); } protected boolean isFinalFrameQueued() { return finalFrameQueued; } @Override public void awaitWritable() throws IOException { if(Thread.currentThread() == getIoThread()) { throw UndertowMessages.MESSAGES.awaitCalledFromIoThread(); } synchronized (lock) { if (anyAreSet(state, STATE_CLOSED) || broken) { return; } if (readyForFlush) { try { waiterCount++; //we need to re-check after incrementing the waiters count if(readyForFlush && !anyAreSet(state, STATE_CLOSED) && !broken) { lock.wait(AWAIT_WRITABLE_TIMEOUT); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InterruptedIOException(); } finally { waiterCount--; } } } } @Override public void awaitWritable(long l, TimeUnit timeUnit) throws IOException { if(Thread.currentThread() == getIoThread()) { throw UndertowMessages.MESSAGES.awaitCalledFromIoThread(); } synchronized (lock) { if (anyAreSet(state, STATE_CLOSED) || broken) { return; } if (readyForFlush) { try { waiterCount++; if(readyForFlush && !anyAreSet(state, STATE_CLOSED) && !broken) { lock.wait(timeUnit.toMillis(l)); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InterruptedIOException(); } finally { waiterCount--; } } } } @Override public XnioExecutor getWriteThread() { return channel.getIoThread(); } @Override public ChannelListener.Setter getWriteSetter() { return writeSetter; } @Override public ChannelListener.Setter getCloseSetter() { return closeSetter; } @Override public XnioWorker getWorker() { return channel.getWorker(); } @Override public XnioIoThread getIoThread() { return channel.getIoThread(); } @Override public boolean flush() throws IOException { if(anyAreSet(state, STATE_CLOSED)) { return true; } if (broken) { throw UndertowMessages.MESSAGES.channelIsClosed(); } if (readyForFlush) { return false; } synchronized (lock) { if (fullyFlushed) { state |= STATE_CLOSED; return true; } } if (anyAreSet(state, STATE_WRITES_SHUTDOWN) && !finalFrameQueued) { queueFinalFrame(); return false; } if(anyAreSet(state, STATE_WRITES_SHUTDOWN)) { return false; } if(isFlushRequiredOnEmptyBuffer() || (writeBuffer != null && writeBuffer.getBuffer().position() > 0)) { handleBufferFull(); return !readyForFlush; } return true; } protected boolean isFlushRequiredOnEmptyBuffer() { return false; } @Override public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { if(!safeToSend()) { return 0; } if(writeBuffer == null) { writeBuffer = getChannel().getBufferPool().allocate(); } ByteBuffer buffer = writeBuffer.getBuffer(); int copied = Buffers.copy(buffer, srcs, offset, length); if(!buffer.hasRemaining()) { handleBufferFull(); } writeSucceeded = writeSucceeded || copied > 0; return copied; } @Override public long write(ByteBuffer[] srcs) throws IOException { return write(srcs, 0, srcs.length); } @Override public int write(ByteBuffer src) throws IOException { if(!safeToSend()) { return 0; } if(writeBuffer == null) { writeBuffer = getChannel().getBufferPool().allocate(); } ByteBuffer buffer = writeBuffer.getBuffer(); int copied = Buffers.copy(buffer, src); if(!buffer.hasRemaining()) { handleBufferFull(); } writeSucceeded = writeSucceeded || copied > 0; return copied; } /** * Send a buffer to this channel. * * @param pooled Pooled ByteBuffer to send. The buffer should have data available. This channel will free the buffer * after sending data * @return true if the buffer was accepted; false if the channel needs to first be flushed * @throws IOException if this channel is closed */ public boolean send(PooledByteBuffer pooled) throws IOException { if(isWritesShutdown()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } boolean result = sendInternal(pooled); if(result) { flush(); } return result; } protected boolean sendInternal(PooledByteBuffer pooled) throws IOException { if (safeToSend()) { this.body = pooled; writeSucceeded = true; return true; } return false; } protected boolean safeToSend() throws IOException { int state = this.state; if (anyAreSet(state, STATE_CLOSED) || broken) { throw UndertowMessages.MESSAGES.channelIsClosed(); } if (readyForFlush) { return false; //we can't do anything, we are waiting for a flush } if( null != this.body) { throw UndertowMessages.MESSAGES.bodyIsSetAndNotReadyForFlush(); } return true; } /** * Return the timeout used by awaitWritable. * * @return the awaitWritable timeout, in milliseconds */ protected long getAwaitWritableTimeout() { return AWAIT_WRITABLE_TIMEOUT; } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { return Channels.writeFinalBasic(this, srcs, offset, length); } @Override public long writeFinal(ByteBuffer[] srcs) throws IOException { return writeFinal(srcs, 0, srcs.length); } @Override public int writeFinal(ByteBuffer src) throws IOException { return Channels.writeFinalBasic(this, src); } private void handleBufferFull() throws IOException { synchronized (lock) { bufferFull = true; if (readyForFlush) return; sendWriteBuffer(); readyForFlush = true; state |= STATE_FIRST_DATA_WRITTEN; } channel.queueFrame((S) this); } private void sendWriteBuffer() throws IOException { if(writeBuffer == null) { writeBuffer = EMPTY_BYTE_BUFFER; } writeBuffer.getBuffer().flip(); if(!sendInternal(writeBuffer)) { throw UndertowMessages.MESSAGES.failedToSendAfterBeingSafe(); } writeBuffer = null; } /** * @return true If this is the last frame that will be sent on this connection */ protected abstract boolean isLastFrame(); /** * @return true if the channel is ready to be flushed. When a channel is ready to be flushed nothing should modify the buffer, * as it may be written out by another thread. */ public boolean isReadyForFlush() { return readyForFlush; } /** * Returns true writes have been shutdown */ public boolean isWritesShutdown() { return anyAreSet(state, STATE_WRITES_SHUTDOWN); } @Override public boolean isOpen() { return allAreClear(state, STATE_CLOSED); } @Override public void close() throws IOException { if(fullyFlushed || anyAreSet(state, STATE_CLOSED)) { return; } try { synchronized (lock) { // Double check to avoid executing the the rest of this method multiple times if(fullyFlushed || anyAreSet(state, STATE_CLOSED)) { return; } state |= STATE_CLOSED; if (writeBuffer != null) { writeBuffer.close(); writeBuffer = null; } if (body != null) { body.close(); body = null; } if (header != null && header.getByteBuffer() != null) { header.getByteBuffer().close(); header = null; } } channelForciblyClosed(); //we need to wake up/invoke the write listener if (isWriteResumed()) { ChannelListeners.invokeChannelListener(getIoThread(), this, (ChannelListener) getWriteListener()); } wakeupWrites(); } finally { wakeupWaiters(); } } /** * Called when a channel has been forcibly closed, and data (frames) have already been written. * * The action this should take is protocol dependent, e.g. for SPDY a RST_STREAM should be sent, * for websockets the channel should be closed. * * By default this will just close the underlying channel * * @throws IOException */ protected void channelForciblyClosed() throws IOException { if(isFirstDataWritten()) { getChannel().markWritesBroken(null); } wakeupWaiters(); } @Override public boolean supportsOption(Option option) { return false; } @Override public T getOption(Option tOption) throws IOException { return null; } @Override public T setOption(Option tOption, T t) throws IllegalArgumentException, IOException { return null; } public ByteBuffer getBuffer() { if(anyAreSet(state, STATE_CLOSED)) { throw new IllegalStateException(); } if(body == null) { // TODO should we IllegalState here? we expect a buffer to already exist body = EMPTY_BYTE_BUFFER; } return body.getBuffer(); } /** * Method that is invoked when a frame has been fully flushed. This method is only invoked by the IO thread */ final void flushComplete() throws IOException { synchronized (lock) { try { boolean resetReadyForFlush = true; bufferFull = false; int remaining = header.getRemainingInBuffer(); boolean finalFrame = finalFrameQueued; boolean channelClosed = finalFrame && remaining == 0 && !header.isAnotherFrameRequired(); if (remaining > 0) { // We still have a body, but since we just flushed, we transfer it to the write buffer. // This works as long as you call write() again or if finalFrame is true //TODO: this code may not work if the channel has frame level compression and flow control //we don't have an implementation that needs this yet so it is ok for now body.getBuffer().limit(body.getBuffer().limit() + remaining); body.getBuffer().compact(); writeBuffer = body; body = null; state &= ~STATE_PRE_WRITE_CALLED; if (finalFrame) { // we clear the final frame flag, as it could not actually be written out this.finalFrameQueued = false; // setting readyForFlush will prevent the final frame to be requeued by write listener, so mark // it as false; and do not reset it to false later on // (queueFinalFrame() will set readyForFlush to true and will do so iff readyForFlush is false) resetReadyForFlush = readyForFlush = false; queueFinalFrame(); } } else if (header.isAnotherFrameRequired()) { this.finalFrameQueued = false; if (body != null) { body.close(); body = null; state &= ~STATE_PRE_WRITE_CALLED; } } else if (body != null) { body.close(); body = null; state &= ~STATE_PRE_WRITE_CALLED; } if (channelClosed) { fullyFlushed = true; if (body != null) { body.close(); body = null; state &= ~STATE_PRE_WRITE_CALLED; } } if (header.getByteBuffer() != null) { header.getByteBuffer().close(); } header = null; if (resetReadyForFlush) { readyForFlush = false; } if (isWriteResumed() && !channelClosed) { wakeupWrites(); } else if (isWriteResumed()) { //we need to execute the write listener one last time //we need to dispatch it back to the IO thread, so we don't invoke it recursivly ChannelListeners.invokeChannelListener(getIoThread(), (S) this, getWriteListener()); } final ChannelListener closeListener = this.closeSetter.get(); if (channelClosed && closeListener != null) { ChannelListeners.invokeChannelListener(getIoThread(), (S) AbstractFramedStreamSinkChannel.this, closeListener); } handleFlushComplete(channelClosed); } finally { wakeupWaiters(); } } } protected void handleFlushComplete(boolean finalFrame) { } protected boolean isFirstDataWritten() { return anyAreSet(state, STATE_FIRST_DATA_WRITTEN); } public void markBroken() { this.broken = true; try { wakeupWrites(); wakeupWaiters(); if (isWriteResumed()) { ChannelListener writeListener = this.writeSetter.get(); if (writeListener != null) { ChannelListeners.invokeChannelListener(getIoThread(), (S) this, writeListener); } } ChannelListener closeListener = this.closeSetter.get(); if (closeListener != null) { ChannelListeners.invokeChannelListener(getIoThread(), (S) this, closeListener); } } finally { if(header != null) { if( header.getByteBuffer() != null) { header.getByteBuffer().close(); header = null; } } if(body != null) { body.close(); body = null; } if(writeBuffer != null) { writeBuffer.close(); writeBuffer = null; } } } ChannelListener getWriteListener() { return writeSetter.get(); } private void wakeupWaiters() { if(waiterCount > 0) { synchronized (lock) { // It is possible that waiter count would be updated before gaining the lock, lets check one more // time whether the condition wasn't changed in the meantime. if (waiterCount > 0) { lock.notifyAll(); } } } } public C getChannel() { return channel; } public boolean isBroken() { return broken; } public boolean isBufferFull() { return bufferFull; } } AbstractFramedStreamSourceChannel.java000066400000000000000000000615261420065311100362470ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/framed/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.framed; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.allAreSet; import static org.xnio.Bits.anyAreSet; import java.io.IOException; import java.io.InterruptedIOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.Deque; import java.util.LinkedList; import java.util.concurrent.TimeUnit; import io.undertow.UndertowLogger; import org.xnio.Buffers; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.Option; import io.undertow.connector.PooledByteBuffer; import org.xnio.XnioExecutor; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; import io.undertow.UndertowMessages; /** * Source channel, used to receive framed messages. * * @author Stuart Douglas * @author Flavia Rainone */ public abstract class AbstractFramedStreamSourceChannel, R extends AbstractFramedStreamSourceChannel, S extends AbstractFramedStreamSinkChannel> implements StreamSourceChannel { private final ChannelListener.SimpleSetter readSetter = new ChannelListener.SimpleSetter(); private final ChannelListener.SimpleSetter closeSetter = new ChannelListener.SimpleSetter(); private final C framedChannel; private final Deque pendingFrameData = new LinkedList<>(); private int state = 0; private static final int STATE_DONE = 1 << 1; private static final int STATE_READS_RESUMED = 1 << 2; private static final int STATE_READS_AWAKEN = 1 << 3; private static final int STATE_CLOSED = 1 << 4; private static final int STATE_LAST_FRAME = 1 << 5; private static final int STATE_IN_LISTENER_LOOP = 1 << 6; private static final int STATE_STREAM_BROKEN = 1 << 7; private static final int STATE_RETURNED_MINUS_ONE = 1 << 8; private static final int STATE_WAITNG_MINUS_ONE = 1 << 9; /** * The backing data for the current frame. */ private volatile PooledByteBuffer data; private int currentDataOriginalSize; /** * The amount of data left in the frame. If this is larger than the data in the backing buffer then */ private long frameDataRemaining; private final Object lock = new Object(); // Guarded by lock private int waiters; private volatile boolean waitingForFrame; private int readFrameCount = 0; private long maxStreamSize = -1; private long currentStreamSize; private ChannelListener[] closeListeners = null; public AbstractFramedStreamSourceChannel(C framedChannel) { this.framedChannel = framedChannel; this.waitingForFrame = true; } public AbstractFramedStreamSourceChannel(C framedChannel, PooledByteBuffer data, long frameDataRemaining) { this.framedChannel = framedChannel; this.waitingForFrame = data == null && frameDataRemaining <= 0; this.frameDataRemaining = frameDataRemaining; this.currentStreamSize = frameDataRemaining; if (data != null) { if (!data.getBuffer().hasRemaining()) { data.close(); this.data = null; this.waitingForFrame = frameDataRemaining <= 0; } else { dataReady(null, data); } } } @Override public long transferTo(long position, long count, FileChannel target) throws IOException { if (anyAreSet(state, STATE_DONE)) { return -1; } beforeRead(); if (waitingForFrame) { return 0; } try { if (frameDataRemaining == 0 && anyAreSet(state, STATE_LAST_FRAME)) { synchronized (lock) { state |= STATE_RETURNED_MINUS_ONE; return -1; } } else if (data != null) { int old = data.getBuffer().limit(); try { if (count < data.getBuffer().remaining()) { data.getBuffer().limit((int) (data.getBuffer().position() + count)); } return target.write(data.getBuffer(), position); } finally { data.getBuffer().limit(old); decrementFrameDataRemaining(); } } return 0; } finally { exitRead(); } } private void decrementFrameDataRemaining() { if(!data.getBuffer().hasRemaining()) { frameDataRemaining -= currentDataOriginalSize; } } @Override public long transferTo(long count, ByteBuffer throughBuffer, StreamSinkChannel streamSinkChannel) throws IOException { if (anyAreSet(state, STATE_DONE)) { return -1; } beforeRead(); if (waitingForFrame) { throughBuffer.position(throughBuffer.limit()); return 0; } try { if (frameDataRemaining == 0 && anyAreSet(state, STATE_LAST_FRAME)) { synchronized (lock) { state |= STATE_RETURNED_MINUS_ONE; return -1; } } else if (data != null && data.getBuffer().hasRemaining()) { int old = data.getBuffer().limit(); try { if (count < data.getBuffer().remaining()) { data.getBuffer().limit((int) (data.getBuffer().position() + count)); } int written = streamSinkChannel.write(data.getBuffer()); if(data.getBuffer().hasRemaining()) { //we can still add more data //stick it it throughbuffer, otherwise transfer code will continue to attempt to use this method throughBuffer.clear(); Buffers.copy(throughBuffer, data.getBuffer()); throughBuffer.flip(); } else { throughBuffer.position(throughBuffer.limit()); } return written; } finally { data.getBuffer().limit(old); decrementFrameDataRemaining(); } } else { throughBuffer.position(throughBuffer.limit()); } return 0; } finally { exitRead(); } } public long getMaxStreamSize() { return maxStreamSize; } public void setMaxStreamSize(long maxStreamSize) { this.maxStreamSize = maxStreamSize; if(maxStreamSize > 0) { if(maxStreamSize < currentStreamSize) { handleStreamTooLarge(); } } } private void handleStreamTooLarge() { IoUtils.safeClose(this); } @Override public void suspendReads() { synchronized (lock) { state &= ~(STATE_READS_RESUMED | STATE_READS_AWAKEN); } } /** * Method that is invoked when all data has been read. * * @throws IOException */ protected void complete() throws IOException { close(); } protected boolean isComplete() { return anyAreSet(state, STATE_DONE); } @Override public void resumeReads() { resumeReadsInternal(false); } @Override public boolean isReadResumed() { return anyAreSet(state, STATE_READS_RESUMED); } @Override public void wakeupReads() { resumeReadsInternal(true); } public void addCloseTask(ChannelListener channelListener) { if(closeListeners == null) { closeListeners = new ChannelListener[]{channelListener}; } else { ChannelListener[] old = closeListeners; closeListeners = new ChannelListener[old.length + 1]; System.arraycopy(old, 0, closeListeners, 0, old.length); closeListeners[old.length] = channelListener; } } /** * For this class there is no difference between a resume and a wakeup */ void resumeReadsInternal(boolean wakeup) { synchronized (lock) { state |= STATE_READS_RESUMED; // mark state awaken if wakeup is true if (wakeup) state |= STATE_READS_AWAKEN; // if not waked && not resumed, return else if (!anyAreSet(state, STATE_READS_RESUMED)) return; if (!anyAreSet(state, STATE_IN_LISTENER_LOOP)) { state |= STATE_IN_LISTENER_LOOP; getFramedChannel().runInIoThread(new Runnable() { @Override public void run() { try { boolean readAgain; do { synchronized(lock) { state &= ~STATE_READS_AWAKEN; } ChannelListener listener = getReadListener(); if (listener == null || !isReadResumed()) { return; } ChannelListeners.invokeChannelListener((R) AbstractFramedStreamSourceChannel.this, listener); //if writes are shutdown or we become active then we stop looping //we stop when writes are shutdown because we can't flush until we are active //although we may be flushed as part of a batch final boolean moreData = (frameDataRemaining > 0 && data != null) || !pendingFrameData.isEmpty() || anyAreSet(state, STATE_WAITNG_MINUS_ONE); synchronized (lock) { // keep running if either reads are resumed and there is more data to read, or if reads are awaken readAgain =((isReadResumed() && moreData) || allAreSet(state, STATE_READS_AWAKEN)) // as long as channel is not closed and there is no stream broken && allAreClear(state,STATE_CLOSED | STATE_STREAM_BROKEN); if (!readAgain) state &= ~STATE_IN_LISTENER_LOOP; } } while (readAgain); } catch (RuntimeException | Error e) { synchronized (lock) { state &= ~STATE_IN_LISTENER_LOOP; } } } }); } } } private ChannelListener getReadListener() { return (ChannelListener) readSetter.get(); } @Override public void shutdownReads() throws IOException { close(); } protected void lastFrame() { synchronized (lock) { state |= STATE_LAST_FRAME; } waitingForFrame = false; if(data == null && pendingFrameData.isEmpty() && frameDataRemaining == 0) { synchronized (lock) { state |= STATE_DONE; } getFramedChannel().notifyFrameReadComplete(this); IoUtils.safeClose(this); } } protected boolean isLastFrame() { return anyAreSet(state, STATE_LAST_FRAME); } @Override public void awaitReadable() throws IOException { if(Thread.currentThread() == getIoThread()) { throw UndertowMessages.MESSAGES.awaitCalledFromIoThread(); } if (data == null && pendingFrameData.isEmpty() && !anyAreSet(state, STATE_STREAM_BROKEN | STATE_CLOSED)) { synchronized (lock) { if (data == null && pendingFrameData.isEmpty() && !anyAreSet(state, STATE_STREAM_BROKEN | STATE_CLOSED)) { try { waiters++; lock.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InterruptedIOException(); } finally { waiters--; } } } } } @Override public void awaitReadable(long l, TimeUnit timeUnit) throws IOException { if(Thread.currentThread() == getIoThread()) { throw UndertowMessages.MESSAGES.awaitCalledFromIoThread(); } if (data == null && pendingFrameData.isEmpty() && !anyAreSet(state, STATE_STREAM_BROKEN | STATE_CLOSED)) { synchronized (lock) { if (data == null && pendingFrameData.isEmpty() && !anyAreSet(state, STATE_STREAM_BROKEN | STATE_CLOSED)) { try { waiters++; lock.wait(timeUnit.toMillis(l)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InterruptedIOException(); } finally { waiters--; } } } } } /** * Called when data has been read from the underlying channel. * * @param headerData The frame header data. This may be null if the data is part of a an existing frame * @param frameData The frame data */ protected void dataReady(FrameHeaderData headerData, PooledByteBuffer frameData) { if(anyAreSet(state, STATE_STREAM_BROKEN | STATE_CLOSED)) { frameData.close(); return; } synchronized (lock) { boolean newData = pendingFrameData.isEmpty(); this.pendingFrameData.add(new FrameData(headerData, frameData)); if (newData) { if (waiters > 0) { lock.notifyAll(); } } waitingForFrame = false; } if (anyAreSet(state, STATE_READS_RESUMED)) { resumeReadsInternal(true); } if(headerData != null) { currentStreamSize += headerData.getFrameLength(); if(maxStreamSize > 0 && currentStreamSize > maxStreamSize) { handleStreamTooLarge(); } } } protected long updateFrameDataRemaining(PooledByteBuffer frameData, long frameDataRemaining) { return frameDataRemaining; } protected PooledByteBuffer processFrameData(PooledByteBuffer data, boolean lastFragmentOfFrame) throws IOException { return data; } protected void handleHeaderData(FrameHeaderData headerData) { } @Override public XnioExecutor getReadThread() { return framedChannel.getIoThread(); } @Override public ChannelListener.Setter getReadSetter() { return readSetter; } @Override public ChannelListener.Setter getCloseSetter() { return closeSetter; } @Override public XnioWorker getWorker() { return framedChannel.getWorker(); } @Override public XnioIoThread getIoThread() { return framedChannel.getIoThread(); } @Override public boolean supportsOption(Option option) { return false; } @Override public T getOption(Option tOption) throws IOException { return null; } @Override public T setOption(Option tOption, T t) throws IllegalArgumentException, IOException { return null; } @Override public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { if (anyAreSet(state, STATE_DONE)) { return -1; } beforeRead(); if (waitingForFrame) { return 0; } try { if (frameDataRemaining == 0 && anyAreSet(state, STATE_LAST_FRAME)) { synchronized (lock) { state |= STATE_RETURNED_MINUS_ONE; } return -1; } else if (data != null) { int old = data.getBuffer().limit(); try { long count = Buffers.remaining(dsts, offset, length); if (count < data.getBuffer().remaining()) { data.getBuffer().limit((int) (data.getBuffer().position() + count)); } else { count = data.getBuffer().remaining(); } return Buffers.copy((int) count, dsts, offset, length, data.getBuffer()); } finally { data.getBuffer().limit(old); decrementFrameDataRemaining(); } } return 0; } finally { exitRead(); } } @Override public long read(ByteBuffer[] dsts) throws IOException { return read(dsts, 0, dsts.length); } @Override public int read(ByteBuffer dst) throws IOException { if (anyAreSet(state, STATE_DONE)) { return -1; } if (!dst.hasRemaining()) { return 0; } beforeRead(); if (waitingForFrame) { return 0; } try { if (frameDataRemaining == 0 && anyAreSet(state, STATE_LAST_FRAME)) { synchronized (lock) { state |= STATE_RETURNED_MINUS_ONE; } return -1; } else if (data != null) { int old = data.getBuffer().limit(); try { int count = dst.remaining(); if (count < data.getBuffer().remaining()) { data.getBuffer().limit(data.getBuffer().position() + count); } else { count = data.getBuffer().remaining(); } return Buffers.copy(count, dst, data.getBuffer()); } finally { data.getBuffer().limit(old); decrementFrameDataRemaining(); } } return 0; } finally { try { exitRead(); } catch (Throwable e) { markStreamBroken(); } } } private void beforeRead() throws IOException { if (anyAreSet(state, STATE_STREAM_BROKEN)) { throw UndertowMessages.MESSAGES.channelIsClosed(); } if (data == null) { synchronized (lock) { FrameData pending = pendingFrameData.poll(); if (pending != null) { PooledByteBuffer frameData = pending.getFrameData(); boolean hasData = true; if(!frameData.getBuffer().hasRemaining()) { frameData.close(); hasData = false; } if (pending.getFrameHeaderData() != null) { this.frameDataRemaining = pending.getFrameHeaderData().getFrameLength(); handleHeaderData(pending.getFrameHeaderData()); } if(hasData) { this.frameDataRemaining = updateFrameDataRemaining(frameData, frameDataRemaining); this.currentDataOriginalSize = frameData.getBuffer().remaining(); try { this.data = processFrameData(frameData, frameDataRemaining - currentDataOriginalSize == 0); } catch (Throwable e) { frameData.close(); UndertowLogger.REQUEST_IO_LOGGER.ioException(new IOException(e)); markStreamBroken(); } } } } } } private void exitRead() throws IOException { if (data != null && !data.getBuffer().hasRemaining()) { data.close(); data = null; } if (frameDataRemaining == 0) { try { synchronized (lock) { readFrameCount++; if (pendingFrameData.isEmpty()) { if (anyAreSet(state, STATE_RETURNED_MINUS_ONE)) { state |= STATE_DONE; complete(); close(); } else if(anyAreSet(state, STATE_LAST_FRAME)) { state |= STATE_WAITNG_MINUS_ONE; } else { waitingForFrame = true; } } } } finally { if (pendingFrameData.isEmpty()) { framedChannel.notifyFrameReadComplete(this); } } } } @Override public boolean isOpen() { return allAreClear(state, STATE_CLOSED); } @Override public void close() { if(anyAreSet(state, STATE_CLOSED)) { return; } synchronized (lock) { // Double check to avoid executing the the rest of this method multiple times if(anyAreSet(state, STATE_CLOSED)) { return; } state |= STATE_CLOSED; if (allAreClear(state, STATE_DONE | STATE_LAST_FRAME)) { state |= STATE_STREAM_BROKEN; channelForciblyClosed(); } if (data != null) { data.close(); data = null; } while (!pendingFrameData.isEmpty()) { pendingFrameData.poll().frameData.close(); } ChannelListeners.invokeChannelListener(this, (ChannelListener>) closeSetter.get()); if (closeListeners != null) { for (int i = 0; i < closeListeners.length; ++i) { closeListeners[i].handleEvent(this); } } // UNDERTOW-1639: Close may be called from an I/O thread while a worker is blocked on awaitReadable. // Once the channel is closed, callers must be awoken. if (waiters > 0) { lock.notifyAll(); } } } protected void channelForciblyClosed() { //TODO: what should be the default action? //we can probably just ignore it, as it does not affect the underlying protocol } protected C getFramedChannel() { return framedChannel; } protected int getReadFrameCount() { return readFrameCount; } /** * Called when this stream is no longer valid. Reads from the stream will result * in an exception. */ protected void markStreamBroken() { if(anyAreSet(state, STATE_STREAM_BROKEN)) { return; } synchronized (lock) { state |= STATE_STREAM_BROKEN; PooledByteBuffer data = this.data; if(data != null) { try { data.close(); //may have been closed by the read thread } catch (Throwable e) { //ignore } this.data = null; } for(FrameData frame : pendingFrameData) { frame.frameData.close(); } pendingFrameData.clear(); if(isReadResumed()) { resumeReadsInternal(true); } if (waiters > 0) { lock.notifyAll(); } } } private class FrameData { private final FrameHeaderData frameHeaderData; private final PooledByteBuffer frameData; FrameData(FrameHeaderData frameHeaderData, PooledByteBuffer frameData) { this.frameHeaderData = frameHeaderData; this.frameData = frameData; } FrameHeaderData getFrameHeaderData() { return frameHeaderData; } PooledByteBuffer getFrameData() { return frameData; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/framed/FrameHeaderData.java000066400000000000000000000017241420065311100325450ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.framed; /** * Frame header data for frames that are received * * @author Stuart Douglas */ public interface FrameHeaderData { long getFrameLength(); AbstractFramedStreamSourceChannel getExistingChannel(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/framed/FramePriority.java000066400000000000000000000053151420065311100324040ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.framed; import java.util.Deque; import java.util.List; /** * * Interface that can be used to determine where to insert a given frame into the pending frame queue. * * @author Stuart Douglas */ public interface FramePriority, R extends AbstractFramedStreamSourceChannel, S extends AbstractFramedStreamSinkChannel> { /** * Inserts the new frame at the correct location in the pending frame list. Note that this must * never insert a frame at the very start of the senders list, as this frame has already been activated. * * This method should return true if the frame was successfully inserted into the pending frame list, * if it returns false the frame must not be inserted and will be added to the held frames list instead. * * Frames held in the held frames list are frames that are not yet ready to be included in the pending frame * list, generally because other frames have to be written first. * * Note that if this method returns true without adding the frame the frame will be dropped. * * @param newFrame The new frame to insert into the pending frame list * @param pendingFrames The pending frame list * @return true if the frame can be inserted into the pending frame list */ boolean insertFrame(S newFrame, final List pendingFrames); /** * Invoked when a new frame is successfully added to the pending frames queue. * * If frames in the held frame queue are now eligible to be sent they can be added * to the pending frames queue. * * Note that if the protocol has explicitly asked for the held frames to be recalculated * then the added frame may be null. * * @param addedFrame The newly added frame * @param pendingFrames The pending frame queue * @param holdFrames The held frame queue */ void frameAdded(S addedFrame, final List pendingFrames, final Deque holdFrames); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/framed/SendFrameHeader.java000066400000000000000000000052251420065311100325650ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.framed; import java.nio.ByteBuffer; import io.undertow.connector.PooledByteBuffer; /** * @author Stuart Douglas */ public class SendFrameHeader { private final int reminingInBuffer; private final PooledByteBuffer byteBuffer; private final boolean anotherFrameRequired; private final ByteBuffer trailer; public SendFrameHeader(int reminingInBuffer, PooledByteBuffer byteBuffer, boolean anotherFrameRequired) { this(reminingInBuffer, byteBuffer, anotherFrameRequired, null); } public SendFrameHeader(int reminingInBuffer, PooledByteBuffer byteBuffer, boolean anotherFrameRequired, ByteBuffer trailer) { this.byteBuffer = byteBuffer; this.reminingInBuffer = reminingInBuffer; this.anotherFrameRequired = anotherFrameRequired; this.trailer = trailer; } public SendFrameHeader(int reminingInBuffer, PooledByteBuffer byteBuffer) { this.byteBuffer = byteBuffer; this.reminingInBuffer = reminingInBuffer; this.anotherFrameRequired = false; this.trailer = null; } public SendFrameHeader(PooledByteBuffer byteBuffer) { this.byteBuffer = byteBuffer; this.reminingInBuffer = 0; this.anotherFrameRequired = false; this.trailer = null; } /** * * @return The header byte buffer */ public PooledByteBuffer getByteBuffer() { return byteBuffer; } public ByteBuffer getTrailer() { return trailer; } /** * * @return */ public int getRemainingInBuffer() { return reminingInBuffer; } /** * Returns true if another frame is required after this one. Note that returning false * does not mean that this is the last frame. This is used for protocols that require a trailing packet * after all data has been written. */ public boolean isAnotherFrameRequired() { return anotherFrameRequired; } } ShutdownFallbackExecutor.java000066400000000000000000000045341420065311100345050ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/framed/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.framed; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; /** * {@link ShutdownFallbackExecutor} wrapper around a single threaded executor * which will execute pending tasks when the worker has been shut down. */ final class ShutdownFallbackExecutor { private static volatile Executor EXECUTOR = null; private ShutdownFallbackExecutor() { // Static Utility } static void execute(Runnable runnable) { if (EXECUTOR == null) { synchronized (ShutdownFallbackExecutor.class) { if (EXECUTOR == null) { EXECUTOR = new ThreadPoolExecutor(0, 1, 10, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), new ShutdownFallbackThreadFactory()); } } } EXECUTOR.execute(runnable); } static final class ShutdownFallbackThreadFactory implements ThreadFactory { private final AtomicLong count = new AtomicLong(); private final ThreadFactory threadFactory = Executors.defaultThreadFactory(); @Override public Thread newThread(Runnable r) { Thread thread = threadFactory.newThread(r); thread.setName("undertow-shutdown-" + count.getAndIncrement()); thread.setDaemon(true); return thread; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http/000077500000000000000000000000001420065311100264625ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http/ALPNBannedCiphers.java000066400000000000000000001261521420065311100325140ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * @author Stuart Douglas */ class ALPNBannedCiphers { static class Key { private final byte b1, b2; Key(int b1, int b2) { this.b1 = (byte) b1; this.b2 = (byte) b2; } } private static final Map CIPHERS; private static final Map REVERSE_CIPHERS; private static final Set ALPN_BANNED_CIPHERS; static { Map ciphers = new HashMap<>(); ciphers.put("TLS_NULL_WITH_NULL_NULL", new Key(0x00, 0x00)); ciphers.put("TLS_RSA_WITH_NULL_MD5", new Key(0x00, 0x01)); ciphers.put("TLS_RSA_WITH_NULL_SHA", new Key(0x00, 0x02)); ciphers.put("TLS_RSA_EXPORT_WITH_RC4_40_MD5", new Key(0x00, 0x03)); ciphers.put("TLS_RSA_WITH_RC4_128_MD5", new Key(0x00, 0x04)); ciphers.put("TLS_RSA_WITH_RC4_128_SHA", new Key(0x00, 0x05)); ciphers.put("TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", new Key(0x00, 0x06)); ciphers.put("TLS_RSA_WITH_IDEA_CBC_SHA", new Key(0x00, 0x07)); ciphers.put("TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", new Key(0x00, 0x08)); ciphers.put("TLS_RSA_WITH_DES_CBC_SHA", new Key(0x00, 0x09)); ciphers.put("TLS_RSA_WITH_3DES_EDE_CBC_SHA", new Key(0x00, 0x0A)); ciphers.put("TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", new Key(0x00, 0x0B)); ciphers.put("TLS_DH_DSS_WITH_DES_CBC_SHA", new Key(0x00, 0x0C)); ciphers.put("TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", new Key(0x00, 0x0D)); ciphers.put("TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", new Key(0x00, 0x0E)); ciphers.put("TLS_DH_RSA_WITH_DES_CBC_SHA", new Key(0x00, 0x0F)); ciphers.put("TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", new Key(0x00, 0x10)); ciphers.put("TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", new Key(0x00, 0x11)); ciphers.put("TLS_DHE_DSS_WITH_DES_CBC_SHA", new Key(0x00, 0x12)); ciphers.put("TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", new Key(0x00, 0x13)); ciphers.put("TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", new Key(0x00, 0x14)); ciphers.put("TLS_DHE_RSA_WITH_DES_CBC_SHA", new Key(0x00, 0x15)); ciphers.put("TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", new Key(0x00, 0x16)); ciphers.put("TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", new Key(0x00, 0x17)); ciphers.put("TLS_DH_anon_WITH_RC4_128_MD5", new Key(0x00, 0x18)); ciphers.put("TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", new Key(0x00, 0x19)); ciphers.put("TLS_DH_anon_WITH_DES_CBC_SHA", new Key(0x00, 0x1A)); ciphers.put("TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", new Key(0x00, 0x1B)); ciphers.put("TLS_KRB5_WITH_DES_CBC_SHA", new Key(0x00, 0x1E)); ciphers.put("TLS_KRB5_WITH_3DES_EDE_CBC_SHA", new Key(0x00, 0x1F)); ciphers.put("TLS_KRB5_WITH_RC4_128_SHA", new Key(0x00, 0x20)); ciphers.put("TLS_KRB5_WITH_IDEA_CBC_SHA", new Key(0x00, 0x21)); ciphers.put("TLS_KRB5_WITH_DES_CBC_MD5", new Key(0x00, 0x22)); ciphers.put("TLS_KRB5_WITH_3DES_EDE_CBC_MD5", new Key(0x00, 0x23)); ciphers.put("TLS_KRB5_WITH_RC4_128_MD5", new Key(0x00, 0x24)); ciphers.put("TLS_KRB5_WITH_IDEA_CBC_MD5", new Key(0x00, 0x25)); ciphers.put("TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA", new Key(0x00, 0x26)); ciphers.put("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA", new Key(0x00, 0x27)); ciphers.put("TLS_KRB5_EXPORT_WITH_RC4_40_SHA", new Key(0x00, 0x28)); ciphers.put("TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5", new Key(0x00, 0x29)); ciphers.put("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5", new Key(0x00, 0x2A)); ciphers.put("TLS_KRB5_EXPORT_WITH_RC4_40_MD5", new Key(0x00, 0x2B)); ciphers.put("TLS_PSK_WITH_NULL_SHA", new Key(0x00, 0x2C)); ciphers.put("TLS_DHE_PSK_WITH_NULL_SHA", new Key(0x00, 0x2D)); ciphers.put("TLS_RSA_PSK_WITH_NULL_SHA", new Key(0x00, 0x2E)); ciphers.put("TLS_RSA_WITH_AES_128_CBC_SHA", new Key(0x00, 0x2F)); ciphers.put("TLS_DH_DSS_WITH_AES_128_CBC_SHA", new Key(0x00, 0x30)); ciphers.put("TLS_DH_RSA_WITH_AES_128_CBC_SHA", new Key(0x00, 0x31)); ciphers.put("TLS_DHE_DSS_WITH_AES_128_CBC_SHA", new Key(0x00, 0x32)); ciphers.put("TLS_DHE_RSA_WITH_AES_128_CBC_SHA", new Key(0x00, 0x33)); ciphers.put("TLS_DH_anon_WITH_AES_128_CBC_SHA", new Key(0x00, 0x34)); ciphers.put("TLS_RSA_WITH_AES_256_CBC_SHA", new Key(0x00, 0x35)); ciphers.put("TLS_DH_DSS_WITH_AES_256_CBC_SHA", new Key(0x00, 0x36)); ciphers.put("TLS_DH_RSA_WITH_AES_256_CBC_SHA", new Key(0x00, 0x37)); ciphers.put("TLS_DHE_DSS_WITH_AES_256_CBC_SHA", new Key(0x00, 0x38)); ciphers.put("TLS_DHE_RSA_WITH_AES_256_CBC_SHA", new Key(0x00, 0x39)); ciphers.put("TLS_DH_anon_WITH_AES_256_CBC_SHA", new Key(0x00, 0x3A)); ciphers.put("TLS_RSA_WITH_NULL_SHA256", new Key(0x00, 0x3B)); ciphers.put("TLS_RSA_WITH_AES_128_CBC_SHA256", new Key(0x00, 0x3C)); ciphers.put("TLS_RSA_WITH_AES_256_CBC_SHA256", new Key(0x00, 0x3D)); ciphers.put("TLS_DH_DSS_WITH_AES_128_CBC_SHA256", new Key(0x00, 0x3E)); ciphers.put("TLS_DH_RSA_WITH_AES_128_CBC_SHA256", new Key(0x00, 0x3F)); ciphers.put("TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", new Key(0x00, 0x40)); ciphers.put("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", new Key(0x00, 0x41)); ciphers.put("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", new Key(0x00, 0x42)); ciphers.put("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", new Key(0x00, 0x43)); ciphers.put("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", new Key(0x00, 0x44)); ciphers.put("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", new Key(0x00, 0x45)); ciphers.put("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", new Key(0x00, 0x46)); ciphers.put("TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", new Key(0x00, 0x67)); ciphers.put("TLS_DH_DSS_WITH_AES_256_CBC_SHA256", new Key(0x00, 0x68)); ciphers.put("TLS_DH_RSA_WITH_AES_256_CBC_SHA256", new Key(0x00, 0x69)); ciphers.put("TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", new Key(0x00, 0x6A)); ciphers.put("TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", new Key(0x00, 0x6B)); ciphers.put("TLS_DH_anon_WITH_AES_128_CBC_SHA256", new Key(0x00, 0x6C)); ciphers.put("TLS_DH_anon_WITH_AES_256_CBC_SHA256", new Key(0x00, 0x6D)); ciphers.put("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", new Key(0x00, 0x84)); ciphers.put("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", new Key(0x00, 0x85)); ciphers.put("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", new Key(0x00, 0x86)); ciphers.put("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", new Key(0x00, 0x87)); ciphers.put("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", new Key(0x00, 0x88)); ciphers.put("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", new Key(0x00, 0x89)); ciphers.put("TLS_PSK_WITH_RC4_128_SHA", new Key(0x00, 0x8A)); ciphers.put("TLS_PSK_WITH_3DES_EDE_CBC_SHA", new Key(0x00, 0x8B)); ciphers.put("TLS_PSK_WITH_AES_128_CBC_SHA", new Key(0x00, 0x8C)); ciphers.put("TLS_PSK_WITH_AES_256_CBC_SHA", new Key(0x00, 0x8D)); ciphers.put("TLS_DHE_PSK_WITH_RC4_128_SHA", new Key(0x00, 0x8E)); ciphers.put("TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA", new Key(0x00, 0x8F)); ciphers.put("TLS_DHE_PSK_WITH_AES_128_CBC_SHA", new Key(0x00, 0x90)); ciphers.put("TLS_DHE_PSK_WITH_AES_256_CBC_SHA", new Key(0x00, 0x91)); ciphers.put("TLS_RSA_PSK_WITH_RC4_128_SHA", new Key(0x00, 0x92)); ciphers.put("TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA", new Key(0x00, 0x93)); ciphers.put("TLS_RSA_PSK_WITH_AES_128_CBC_SHA", new Key(0x00, 0x94)); ciphers.put("TLS_RSA_PSK_WITH_AES_256_CBC_SHA", new Key(0x00, 0x95)); ciphers.put("TLS_RSA_WITH_SEED_CBC_SHA", new Key(0x00, 0x96)); ciphers.put("TLS_DH_DSS_WITH_SEED_CBC_SHA", new Key(0x00, 0x97)); ciphers.put("TLS_DH_RSA_WITH_SEED_CBC_SHA", new Key(0x00, 0x98)); ciphers.put("TLS_DHE_DSS_WITH_SEED_CBC_SHA", new Key(0x00, 0x99)); ciphers.put("TLS_DHE_RSA_WITH_SEED_CBC_SHA", new Key(0x00, 0x9A)); ciphers.put("TLS_DH_anon_WITH_SEED_CBC_SHA", new Key(0x00, 0x9B)); ciphers.put("TLS_RSA_WITH_AES_128_GCM_SHA256", new Key(0x00, 0x9C)); ciphers.put("TLS_RSA_WITH_AES_256_GCM_SHA384", new Key(0x00, 0x9D)); ciphers.put("TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", new Key(0x00, 0x9E)); ciphers.put("TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", new Key(0x00, 0x9F)); ciphers.put("TLS_DH_RSA_WITH_AES_128_GCM_SHA256", new Key(0x00, 0xA0)); ciphers.put("TLS_DH_RSA_WITH_AES_256_GCM_SHA384", new Key(0x00, 0xA1)); ciphers.put("TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", new Key(0x00, 0xA2)); ciphers.put("TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", new Key(0x00, 0xA3)); ciphers.put("TLS_DH_DSS_WITH_AES_128_GCM_SHA256", new Key(0x00, 0xA4)); ciphers.put("TLS_DH_DSS_WITH_AES_256_GCM_SHA384", new Key(0x00, 0xA5)); ciphers.put("TLS_DH_anon_WITH_AES_128_GCM_SHA256", new Key(0x00, 0xA6)); ciphers.put("TLS_DH_anon_WITH_AES_256_GCM_SHA384", new Key(0x00, 0xA7)); ciphers.put("TLS_PSK_WITH_AES_128_GCM_SHA256", new Key(0x00, 0xA8)); ciphers.put("TLS_PSK_WITH_AES_256_GCM_SHA384", new Key(0x00, 0xA9)); ciphers.put("TLS_DHE_PSK_WITH_AES_128_GCM_SHA256", new Key(0x00, 0xAA)); ciphers.put("TLS_DHE_PSK_WITH_AES_256_GCM_SHA384", new Key(0x00, 0xAB)); ciphers.put("TLS_RSA_PSK_WITH_AES_128_GCM_SHA256", new Key(0x00, 0xAC)); ciphers.put("TLS_RSA_PSK_WITH_AES_256_GCM_SHA384", new Key(0x00, 0xAD)); ciphers.put("TLS_PSK_WITH_AES_128_CBC_SHA256", new Key(0x00, 0xAE)); ciphers.put("TLS_PSK_WITH_AES_256_CBC_SHA384", new Key(0x00, 0xAF)); ciphers.put("TLS_PSK_WITH_NULL_SHA256", new Key(0x00, 0xB0)); ciphers.put("TLS_PSK_WITH_NULL_SHA384", new Key(0x00, 0xB1)); ciphers.put("TLS_DHE_PSK_WITH_AES_128_CBC_SHA256", new Key(0x00, 0xB2)); ciphers.put("TLS_DHE_PSK_WITH_AES_256_CBC_SHA384", new Key(0x00, 0xB3)); ciphers.put("TLS_DHE_PSK_WITH_NULL_SHA256", new Key(0x00, 0xB4)); ciphers.put("TLS_DHE_PSK_WITH_NULL_SHA384", new Key(0x00, 0xB5)); ciphers.put("TLS_RSA_PSK_WITH_AES_128_CBC_SHA256", new Key(0x00, 0xB6)); ciphers.put("TLS_RSA_PSK_WITH_AES_256_CBC_SHA384", new Key(0x00, 0xB7)); ciphers.put("TLS_RSA_PSK_WITH_NULL_SHA256", new Key(0x00, 0xB8)); ciphers.put("TLS_RSA_PSK_WITH_NULL_SHA384", new Key(0x00, 0xB9)); ciphers.put("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", new Key(0x00, 0xBA)); ciphers.put("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256", new Key(0x00, 0xBB)); ciphers.put("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256", new Key(0x00, 0xBC)); ciphers.put("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", new Key(0x00, 0xBD)); ciphers.put("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", new Key(0x00, 0xBE)); ciphers.put("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256", new Key(0x00, 0xBF)); ciphers.put("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", new Key(0x00, 0xC0)); ciphers.put("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256", new Key(0x00, 0xC1)); ciphers.put("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256", new Key(0x00, 0xC2)); ciphers.put("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", new Key(0x00, 0xC3)); ciphers.put("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", new Key(0x00, 0xC4)); ciphers.put("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256", new Key(0x00, 0xC5)); ciphers.put("TLS_EMPTY_RENEGOTIATION_INFO_SCSV", new Key(0x00, 0xFF)); ciphers.put("TLS_FALLBACK_SCSV", new Key(0x56, 0x00)); ciphers.put("TLS_ECDH_ECDSA_WITH_NULL_SHA", new Key(0xC0, 0x01)); ciphers.put("TLS_ECDH_ECDSA_WITH_RC4_128_SHA", new Key(0xC0, 0x02)); ciphers.put("TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", new Key(0xC0, 0x03)); ciphers.put("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", new Key(0xC0, 0x04)); ciphers.put("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", new Key(0xC0, 0x05)); ciphers.put("TLS_ECDHE_ECDSA_WITH_NULL_SHA", new Key(0xC0, 0x06)); ciphers.put("TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", new Key(0xC0, 0x07)); ciphers.put("TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", new Key(0xC0, 0x08)); ciphers.put("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", new Key(0xC0, 0x09)); ciphers.put("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", new Key(0xC0, 0x0A)); ciphers.put("TLS_ECDH_RSA_WITH_NULL_SHA", new Key(0xC0, 0x0B)); ciphers.put("TLS_ECDH_RSA_WITH_RC4_128_SHA", new Key(0xC0, 0x0C)); ciphers.put("TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", new Key(0xC0, 0x0D)); ciphers.put("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", new Key(0xC0, 0x0E)); ciphers.put("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", new Key(0xC0, 0x0F)); ciphers.put("TLS_ECDHE_RSA_WITH_NULL_SHA", new Key(0xC0, 0x10)); ciphers.put("TLS_ECDHE_RSA_WITH_RC4_128_SHA", new Key(0xC0, 0x11)); ciphers.put("TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", new Key(0xC0, 0x12)); ciphers.put("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", new Key(0xC0, 0x13)); ciphers.put("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", new Key(0xC0, 0x14)); ciphers.put("TLS_ECDH_anon_WITH_NULL_SHA", new Key(0xC0, 0x15)); ciphers.put("TLS_ECDH_anon_WITH_RC4_128_SHA", new Key(0xC0, 0x16)); ciphers.put("TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", new Key(0xC0, 0x17)); ciphers.put("TLS_ECDH_anon_WITH_AES_128_CBC_SHA", new Key(0xC0, 0x18)); ciphers.put("TLS_ECDH_anon_WITH_AES_256_CBC_SHA", new Key(0xC0, 0x19)); ciphers.put("TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA", new Key(0xC0, 0x1A)); ciphers.put("TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA", new Key(0xC0, 0x1B)); ciphers.put("TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA", new Key(0xC0, 0x1C)); ciphers.put("TLS_SRP_SHA_WITH_AES_128_CBC_SHA", new Key(0xC0, 0x1D)); ciphers.put("TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA", new Key(0xC0, 0x1E)); ciphers.put("TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA", new Key(0xC0, 0x1F)); ciphers.put("TLS_SRP_SHA_WITH_AES_256_CBC_SHA", new Key(0xC0, 0x20)); ciphers.put("TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA", new Key(0xC0, 0x21)); ciphers.put("TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA", new Key(0xC0, 0x22)); ciphers.put("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", new Key(0xC0, 0x23)); ciphers.put("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", new Key(0xC0, 0x24)); ciphers.put("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", new Key(0xC0, 0x25)); ciphers.put("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", new Key(0xC0, 0x26)); ciphers.put("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", new Key(0xC0, 0x27)); ciphers.put("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", new Key(0xC0, 0x28)); ciphers.put("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", new Key(0xC0, 0x29)); ciphers.put("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", new Key(0xC0, 0x2A)); ciphers.put("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", new Key(0xC0, 0x2B)); ciphers.put("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", new Key(0xC0, 0x2C)); ciphers.put("TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", new Key(0xC0, 0x2D)); ciphers.put("TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", new Key(0xC0, 0x2E)); ciphers.put("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", new Key(0xC0, 0x2F)); ciphers.put("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", new Key(0xC0, 0x30)); ciphers.put("TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", new Key(0xC0, 0x31)); ciphers.put("TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", new Key(0xC0, 0x32)); ciphers.put("TLS_ECDHE_PSK_WITH_RC4_128_SHA", new Key(0xC0, 0x33)); ciphers.put("TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA", new Key(0xC0, 0x34)); ciphers.put("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA", new Key(0xC0, 0x35)); ciphers.put("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA", new Key(0xC0, 0x36)); ciphers.put("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256", new Key(0xC0, 0x37)); ciphers.put("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384", new Key(0xC0, 0x38)); ciphers.put("TLS_ECDHE_PSK_WITH_NULL_SHA", new Key(0xC0, 0x39)); ciphers.put("TLS_ECDHE_PSK_WITH_NULL_SHA256", new Key(0xC0, 0x3A)); ciphers.put("TLS_ECDHE_PSK_WITH_NULL_SHA384", new Key(0xC0, 0x3B)); ciphers.put("TLS_RSA_WITH_ARIA_128_CBC_SHA256", new Key(0xC0, 0x3C)); ciphers.put("TLS_RSA_WITH_ARIA_256_CBC_SHA384", new Key(0xC0, 0x3D)); ciphers.put("TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256", new Key(0xC0, 0x3E)); ciphers.put("TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384", new Key(0xC0, 0x3F)); ciphers.put("TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256", new Key(0xC0, 0x40)); ciphers.put("TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384", new Key(0xC0, 0x41)); ciphers.put("TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256", new Key(0xC0, 0x42)); ciphers.put("TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384", new Key(0xC0, 0x43)); ciphers.put("TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256", new Key(0xC0, 0x44)); ciphers.put("TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384", new Key(0xC0, 0x45)); ciphers.put("TLS_DH_anon_WITH_ARIA_128_CBC_SHA256", new Key(0xC0, 0x46)); ciphers.put("TLS_DH_anon_WITH_ARIA_256_CBC_SHA384", new Key(0xC0, 0x47)); ciphers.put("TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256", new Key(0xC0, 0x48)); ciphers.put("TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384", new Key(0xC0, 0x49)); ciphers.put("TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256", new Key(0xC0, 0x4A)); ciphers.put("TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384", new Key(0xC0, 0x4B)); ciphers.put("TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256", new Key(0xC0, 0x4C)); ciphers.put("TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384", new Key(0xC0, 0x4D)); ciphers.put("TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256", new Key(0xC0, 0x4E)); ciphers.put("TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384", new Key(0xC0, 0x4F)); ciphers.put("TLS_RSA_WITH_ARIA_128_GCM_SHA256", new Key(0xC0, 0x50)); ciphers.put("TLS_RSA_WITH_ARIA_256_GCM_SHA384", new Key(0xC0, 0x51)); ciphers.put("TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256", new Key(0xC0, 0x52)); ciphers.put("TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384", new Key(0xC0, 0x53)); ciphers.put("TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256", new Key(0xC0, 0x54)); ciphers.put("TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384", new Key(0xC0, 0x55)); ciphers.put("TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256", new Key(0xC0, 0x56)); ciphers.put("TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384", new Key(0xC0, 0x57)); ciphers.put("TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256", new Key(0xC0, 0x58)); ciphers.put("TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384", new Key(0xC0, 0x59)); ciphers.put("TLS_DH_anon_WITH_ARIA_128_GCM_SHA256", new Key(0xC0, 0x5A)); ciphers.put("TLS_DH_anon_WITH_ARIA_256_GCM_SHA384", new Key(0xC0, 0x5B)); ciphers.put("TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256", new Key(0xC0, 0x5C)); ciphers.put("TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384", new Key(0xC0, 0x5D)); ciphers.put("TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256", new Key(0xC0, 0x5E)); ciphers.put("TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384", new Key(0xC0, 0x5F)); ciphers.put("TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256", new Key(0xC0, 0x60)); ciphers.put("TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384", new Key(0xC0, 0x61)); ciphers.put("TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256", new Key(0xC0, 0x62)); ciphers.put("TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384", new Key(0xC0, 0x63)); ciphers.put("TLS_PSK_WITH_ARIA_128_CBC_SHA256", new Key(0xC0, 0x64)); ciphers.put("TLS_PSK_WITH_ARIA_256_CBC_SHA384", new Key(0xC0, 0x65)); ciphers.put("TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256", new Key(0xC0, 0x66)); ciphers.put("TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384", new Key(0xC0, 0x67)); ciphers.put("TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256", new Key(0xC0, 0x68)); ciphers.put("TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384", new Key(0xC0, 0x69)); ciphers.put("TLS_PSK_WITH_ARIA_128_GCM_SHA256", new Key(0xC0, 0x6A)); ciphers.put("TLS_PSK_WITH_ARIA_256_GCM_SHA384", new Key(0xC0, 0x6B)); ciphers.put("TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256", new Key(0xC0, 0x6C)); ciphers.put("TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384", new Key(0xC0, 0x6D)); ciphers.put("TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256", new Key(0xC0, 0x6E)); ciphers.put("TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384", new Key(0xC0, 0x6F)); ciphers.put("TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256", new Key(0xC0, 0x70)); ciphers.put("TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384", new Key(0xC0, 0x71)); ciphers.put("TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", new Key(0xC0, 0x72)); ciphers.put("TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", new Key(0xC0, 0x73)); ciphers.put("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", new Key(0xC0, 0x74)); ciphers.put("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", new Key(0xC0, 0x75)); ciphers.put("TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", new Key(0xC0, 0x76)); ciphers.put("TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", new Key(0xC0, 0x77)); ciphers.put("TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256", new Key(0xC0, 0x78)); ciphers.put("TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384", new Key(0xC0, 0x79)); ciphers.put("TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256", new Key(0xC0, 0x7A)); ciphers.put("TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384", new Key(0xC0, 0x7B)); ciphers.put("TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", new Key(0xC0, 0x7C)); ciphers.put("TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", new Key(0xC0, 0x7D)); ciphers.put("TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256", new Key(0xC0, 0x7E)); ciphers.put("TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384", new Key(0xC0, 0x7F)); ciphers.put("TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256", new Key(0xC0, 0x80)); ciphers.put("TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384", new Key(0xC0, 0x81)); ciphers.put("TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256", new Key(0xC0, 0x82)); ciphers.put("TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384", new Key(0xC0, 0x83)); ciphers.put("TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256", new Key(0xC0, 0x84)); ciphers.put("TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384", new Key(0xC0, 0x85)); ciphers.put("TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", new Key(0xC0, 0x86)); ciphers.put("TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", new Key(0xC0, 0x87)); ciphers.put("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", new Key(0xC0, 0x88)); ciphers.put("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", new Key(0xC0, 0x89)); ciphers.put("TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", new Key(0xC0, 0x8A)); ciphers.put("TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", new Key(0xC0, 0x8B)); ciphers.put("TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256", new Key(0xC0, 0x8C)); ciphers.put("TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384", new Key(0xC0, 0x8D)); ciphers.put("TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256", new Key(0xC0, 0x8E)); ciphers.put("TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384", new Key(0xC0, 0x8F)); ciphers.put("TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256", new Key(0xC0, 0x90)); ciphers.put("TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384", new Key(0xC0, 0x91)); ciphers.put("TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256", new Key(0xC0, 0x92)); ciphers.put("TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384", new Key(0xC0, 0x93)); ciphers.put("TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256", new Key(0xC0, 0x94)); ciphers.put("TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384", new Key(0xC0, 0x95)); ciphers.put("TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", new Key(0xC0, 0x96)); ciphers.put("TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", new Key(0xC0, 0x97)); ciphers.put("TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256", new Key(0xC0, 0x98)); ciphers.put("TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384", new Key(0xC0, 0x99)); ciphers.put("TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", new Key(0xC0, 0x9A)); ciphers.put("TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", new Key(0xC0, 0x9B)); ciphers.put("TLS_RSA_WITH_AES_128_CCM", new Key(0xC0, 0x9C)); ciphers.put("TLS_RSA_WITH_AES_256_CCM", new Key(0xC0, 0x9D)); ciphers.put("TLS_DHE_RSA_WITH_AES_128_CCM", new Key(0xC0, 0x9E)); ciphers.put("TLS_DHE_RSA_WITH_AES_256_CCM", new Key(0xC0, 0x9F)); ciphers.put("TLS_RSA_WITH_AES_128_CCM_8", new Key(0xC0, 0xA0)); ciphers.put("TLS_RSA_WITH_AES_256_CCM_8", new Key(0xC0, 0xA1)); ciphers.put("TLS_DHE_RSA_WITH_AES_128_CCM_8", new Key(0xC0, 0xA2)); ciphers.put("TLS_DHE_RSA_WITH_AES_256_CCM_8", new Key(0xC0, 0xA3)); ciphers.put("TLS_PSK_WITH_AES_128_CCM", new Key(0xC0, 0xA4)); ciphers.put("TLS_PSK_WITH_AES_256_CCM", new Key(0xC0, 0xA5)); ciphers.put("TLS_DHE_PSK_WITH_AES_128_CCM", new Key(0xC0, 0xA6)); ciphers.put("TLS_DHE_PSK_WITH_AES_256_CCM", new Key(0xC0, 0xA7)); ciphers.put("TLS_PSK_WITH_AES_128_CCM_8", new Key(0xC0, 0xA8)); ciphers.put("TLS_PSK_WITH_AES_256_CCM_8", new Key(0xC0, 0xA9)); ciphers.put("TLS_PSK_DHE_WITH_AES_128_CCM_8", new Key(0xC0, 0xAA)); ciphers.put("TLS_PSK_DHE_WITH_AES_256_CCM_8", new Key(0xC0, 0xAB)); ciphers.put("TLS_ECDHE_ECDSA_WITH_AES_128_CCM", new Key(0xC0, 0xAC)); ciphers.put("TLS_ECDHE_ECDSA_WITH_AES_256_CCM", new Key(0xC0, 0xAD)); ciphers.put("TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8", new Key(0xC0, 0xAE)); ciphers.put("TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8", new Key(0xC0, 0xAF)); ciphers.put("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", new Key(0xCC, 0xA8)); ciphers.put("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", new Key(0xCC, 0xA9)); ciphers.put("TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", new Key(0xCC, 0xAA)); ciphers.put("TLS_PSK_WITH_CHACHA20_POLY1305_SHA256", new Key(0xCC, 0xAB)); ciphers.put("TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256", new Key(0xCC, 0xAC)); ciphers.put("TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256", new Key(0xCC, 0xAD)); ciphers.put("TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256", new Key(0xCC, 0xAE)); CIPHERS = Collections.unmodifiableMap(ciphers); Map reverse = new HashMap<>(); for(Map.Entry e : ciphers.entrySet()) { reverse.put(e.getValue(), e.getKey()); } REVERSE_CIPHERS = Collections.unmodifiableMap(reverse); Set banned = new HashSet<>() ; banned.add("TLS_NULL_WITH_NULL_NULL"); banned.add("TLS_RSA_WITH_NULL_MD5"); banned.add("TLS_RSA_WITH_NULL_SHA"); banned.add("TLS_RSA_EXPORT_WITH_RC4_40_MD5"); banned.add("TLS_RSA_WITH_RC4_128_MD5"); banned.add("TLS_RSA_WITH_RC4_128_SHA"); banned.add("TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5"); banned.add("TLS_RSA_WITH_IDEA_CBC_SHA"); banned.add("TLS_RSA_EXPORT_WITH_DES40_CBC_SHA"); banned.add("TLS_RSA_WITH_DES_CBC_SHA"); banned.add("TLS_RSA_WITH_3DES_EDE_CBC_SHA"); banned.add("TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA"); banned.add("TLS_DH_DSS_WITH_DES_CBC_SHA"); banned.add("TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA"); banned.add("TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA"); banned.add("TLS_DH_RSA_WITH_DES_CBC_SHA"); banned.add("TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA"); banned.add("TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA"); banned.add("TLS_DHE_DSS_WITH_DES_CBC_SHA"); banned.add("TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA"); banned.add("TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA"); banned.add("TLS_DHE_RSA_WITH_DES_CBC_SHA"); banned.add("TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA"); banned.add("TLS_DH_anon_EXPORT_WITH_RC4_40_MD5"); banned.add("TLS_DH_anon_WITH_RC4_128_MD5"); banned.add("TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA"); banned.add("TLS_DH_anon_WITH_DES_CBC_SHA"); banned.add("TLS_DH_anon_WITH_3DES_EDE_CBC_SHA"); banned.add("TLS_KRB5_WITH_DES_CBC_SHA"); banned.add("TLS_KRB5_WITH_3DES_EDE_CBC_SHA"); banned.add("TLS_KRB5_WITH_RC4_128_SHA"); banned.add("TLS_KRB5_WITH_IDEA_CBC_SHA"); banned.add("TLS_KRB5_WITH_DES_CBC_MD5"); banned.add("TLS_KRB5_WITH_3DES_EDE_CBC_MD5"); banned.add("TLS_KRB5_WITH_RC4_128_MD5"); banned.add("TLS_KRB5_WITH_IDEA_CBC_MD5"); banned.add("TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA"); banned.add("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA"); banned.add("TLS_KRB5_EXPORT_WITH_RC4_40_SHA"); banned.add("TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5"); banned.add("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5"); banned.add("TLS_KRB5_EXPORT_WITH_RC4_40_MD5"); banned.add("TLS_PSK_WITH_NULL_SHA"); banned.add("TLS_DHE_PSK_WITH_NULL_SHA"); banned.add("TLS_RSA_PSK_WITH_NULL_SHA"); banned.add("TLS_RSA_WITH_AES_128_CBC_SHA"); banned.add("TLS_DH_DSS_WITH_AES_128_CBC_SHA"); banned.add("TLS_DH_RSA_WITH_AES_128_CBC_SHA"); banned.add("TLS_DHE_DSS_WITH_AES_128_CBC_SHA"); banned.add("TLS_DHE_RSA_WITH_AES_128_CBC_SHA"); banned.add("TLS_DH_anon_WITH_AES_128_CBC_SHA"); banned.add("TLS_RSA_WITH_AES_256_CBC_SHA"); banned.add("TLS_DH_DSS_WITH_AES_256_CBC_SHA"); banned.add("TLS_DH_RSA_WITH_AES_256_CBC_SHA"); banned.add("TLS_DHE_DSS_WITH_AES_256_CBC_SHA"); banned.add("TLS_DHE_RSA_WITH_AES_256_CBC_SHA"); banned.add("TLS_DH_anon_WITH_AES_256_CBC_SHA"); banned.add("TLS_RSA_WITH_NULL_SHA256"); banned.add("TLS_RSA_WITH_AES_128_CBC_SHA256"); banned.add("TLS_RSA_WITH_AES_256_CBC_SHA256"); banned.add("TLS_DH_DSS_WITH_AES_128_CBC_SHA256"); banned.add("TLS_DH_RSA_WITH_AES_128_CBC_SHA256"); banned.add("TLS_DHE_DSS_WITH_AES_128_CBC_SHA256"); banned.add("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA"); banned.add("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA"); banned.add("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA"); banned.add("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA"); banned.add("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA"); banned.add("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA"); banned.add("TLS_DHE_RSA_WITH_AES_128_CBC_SHA256"); banned.add("TLS_DH_DSS_WITH_AES_256_CBC_SHA256"); banned.add("TLS_DH_RSA_WITH_AES_256_CBC_SHA256"); banned.add("TLS_DHE_DSS_WITH_AES_256_CBC_SHA256"); banned.add("TLS_DHE_RSA_WITH_AES_256_CBC_SHA256"); banned.add("TLS_DH_anon_WITH_AES_128_CBC_SHA256"); banned.add("TLS_DH_anon_WITH_AES_256_CBC_SHA256"); banned.add("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA"); banned.add("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA"); banned.add("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA"); banned.add("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA"); banned.add("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA"); banned.add("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA"); banned.add("TLS_PSK_WITH_RC4_128_SHA"); banned.add("TLS_PSK_WITH_3DES_EDE_CBC_SHA"); banned.add("TLS_PSK_WITH_AES_128_CBC_SHA"); banned.add("TLS_PSK_WITH_AES_256_CBC_SHA"); banned.add("TLS_DHE_PSK_WITH_RC4_128_SHA"); banned.add("TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA"); banned.add("TLS_DHE_PSK_WITH_AES_128_CBC_SHA"); banned.add("TLS_DHE_PSK_WITH_AES_256_CBC_SHA"); banned.add("TLS_RSA_PSK_WITH_RC4_128_SHA"); banned.add("TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA"); banned.add("TLS_RSA_PSK_WITH_AES_128_CBC_SHA"); banned.add("TLS_RSA_PSK_WITH_AES_256_CBC_SHA"); banned.add("TLS_RSA_WITH_SEED_CBC_SHA"); banned.add("TLS_DH_DSS_WITH_SEED_CBC_SHA"); banned.add("TLS_DH_RSA_WITH_SEED_CBC_SHA"); banned.add("TLS_DHE_DSS_WITH_SEED_CBC_SHA"); banned.add("TLS_DHE_RSA_WITH_SEED_CBC_SHA"); banned.add("TLS_DH_anon_WITH_SEED_CBC_SHA"); banned.add("TLS_RSA_WITH_AES_128_GCM_SHA256"); banned.add("TLS_RSA_WITH_AES_256_GCM_SHA384"); banned.add("TLS_DH_RSA_WITH_AES_128_GCM_SHA256"); banned.add("TLS_DH_RSA_WITH_AES_256_GCM_SHA384"); banned.add("TLS_DH_DSS_WITH_AES_128_GCM_SHA256"); banned.add("TLS_DH_DSS_WITH_AES_256_GCM_SHA384"); banned.add("TLS_DH_anon_WITH_AES_128_GCM_SHA256"); banned.add("TLS_DH_anon_WITH_AES_256_GCM_SHA384"); banned.add("TLS_PSK_WITH_AES_128_GCM_SHA256"); banned.add("TLS_PSK_WITH_AES_256_GCM_SHA384"); banned.add("TLS_RSA_PSK_WITH_AES_128_GCM_SHA256"); banned.add("TLS_RSA_PSK_WITH_AES_256_GCM_SHA384"); banned.add("TLS_PSK_WITH_AES_128_CBC_SHA256"); banned.add("TLS_PSK_WITH_AES_256_CBC_SHA384"); banned.add("TLS_PSK_WITH_NULL_SHA256"); banned.add("TLS_PSK_WITH_NULL_SHA384"); banned.add("TLS_DHE_PSK_WITH_AES_128_CBC_SHA256"); banned.add("TLS_DHE_PSK_WITH_AES_256_CBC_SHA384"); banned.add("TLS_DHE_PSK_WITH_NULL_SHA256"); banned.add("TLS_DHE_PSK_WITH_NULL_SHA384"); banned.add("TLS_RSA_PSK_WITH_AES_128_CBC_SHA256"); banned.add("TLS_RSA_PSK_WITH_AES_256_CBC_SHA384"); banned.add("TLS_RSA_PSK_WITH_NULL_SHA256"); banned.add("TLS_RSA_PSK_WITH_NULL_SHA384"); banned.add("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256"); banned.add("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256"); banned.add("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256"); banned.add("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256"); banned.add("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256"); banned.add("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256"); banned.add("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256"); banned.add("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256"); banned.add("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256"); banned.add("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256"); banned.add("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256"); banned.add("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256"); banned.add("TLS_EMPTY_RENEGOTIATION_INFO_SCSV"); banned.add("TLS_ECDH_ECDSA_WITH_NULL_SHA"); banned.add("TLS_ECDH_ECDSA_WITH_RC4_128_SHA"); banned.add("TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA"); banned.add("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA"); banned.add("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA"); banned.add("TLS_ECDHE_ECDSA_WITH_NULL_SHA"); banned.add("TLS_ECDHE_ECDSA_WITH_RC4_128_SHA"); banned.add("TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA"); banned.add("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"); banned.add("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA"); banned.add("TLS_ECDH_RSA_WITH_NULL_SHA"); banned.add("TLS_ECDH_RSA_WITH_RC4_128_SHA"); banned.add("TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA"); banned.add("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA"); banned.add("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA"); banned.add("TLS_ECDHE_RSA_WITH_NULL_SHA"); banned.add("TLS_ECDHE_RSA_WITH_RC4_128_SHA"); banned.add("TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA"); banned.add("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"); banned.add("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"); banned.add("TLS_ECDH_anon_WITH_NULL_SHA"); banned.add("TLS_ECDH_anon_WITH_RC4_128_SHA"); banned.add("TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA"); banned.add("TLS_ECDH_anon_WITH_AES_128_CBC_SHA"); banned.add("TLS_ECDH_anon_WITH_AES_256_CBC_SHA"); banned.add("TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA"); banned.add("TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA"); banned.add("TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA"); banned.add("TLS_SRP_SHA_WITH_AES_128_CBC_SHA"); banned.add("TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA"); banned.add("TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA"); banned.add("TLS_SRP_SHA_WITH_AES_256_CBC_SHA"); banned.add("TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA"); banned.add("TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA"); banned.add("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"); banned.add("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384"); banned.add("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256"); banned.add("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384"); banned.add("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"); banned.add("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384"); banned.add("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256"); banned.add("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384"); banned.add("TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256"); banned.add("TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384"); banned.add("TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256"); banned.add("TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384"); banned.add("TLS_ECDHE_PSK_WITH_RC4_128_SHA"); banned.add("TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA"); banned.add("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA"); banned.add("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA"); banned.add("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256"); banned.add("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384"); banned.add("TLS_ECDHE_PSK_WITH_NULL_SHA"); banned.add("TLS_ECDHE_PSK_WITH_NULL_SHA256"); banned.add("TLS_ECDHE_PSK_WITH_NULL_SHA384"); banned.add("TLS_RSA_WITH_ARIA_128_CBC_SHA256"); banned.add("TLS_RSA_WITH_ARIA_256_CBC_SHA384"); banned.add("TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256"); banned.add("TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384"); banned.add("TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256"); banned.add("TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384"); banned.add("TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256"); banned.add("TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384"); banned.add("TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256"); banned.add("TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384"); banned.add("TLS_DH_anon_WITH_ARIA_128_CBC_SHA256"); banned.add("TLS_DH_anon_WITH_ARIA_256_CBC_SHA384"); banned.add("TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256"); banned.add("TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384"); banned.add("TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256"); banned.add("TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384"); banned.add("TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256"); banned.add("TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384"); banned.add("TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256"); banned.add("TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384"); banned.add("TLS_RSA_WITH_ARIA_128_GCM_SHA256"); banned.add("TLS_RSA_WITH_ARIA_256_GCM_SHA384"); banned.add("TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256"); banned.add("TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384"); banned.add("TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256"); banned.add("TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384"); banned.add("TLS_DH_anon_WITH_ARIA_128_GCM_SHA256"); banned.add("TLS_DH_anon_WITH_ARIA_256_GCM_SHA384"); banned.add("TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256"); banned.add("TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384"); banned.add("TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256"); banned.add("TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384"); banned.add("TLS_PSK_WITH_ARIA_128_CBC_SHA256"); banned.add("TLS_PSK_WITH_ARIA_256_CBC_SHA384"); banned.add("TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256"); banned.add("TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384"); banned.add("TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256"); banned.add("TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384"); banned.add("TLS_PSK_WITH_ARIA_128_GCM_SHA256"); banned.add("TLS_PSK_WITH_ARIA_256_GCM_SHA384"); banned.add("TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256"); banned.add("TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384"); banned.add("TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256"); banned.add("TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384"); banned.add("TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256"); banned.add("TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384"); banned.add("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256"); banned.add("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384"); banned.add("TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256"); banned.add("TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384"); banned.add("TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256"); banned.add("TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384"); banned.add("TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256"); banned.add("TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384"); banned.add("TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256"); banned.add("TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384"); banned.add("TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256"); banned.add("TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384"); banned.add("TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256"); banned.add("TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384"); banned.add("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256"); banned.add("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384"); banned.add("TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256"); banned.add("TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384"); banned.add("TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256"); banned.add("TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384"); banned.add("TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256"); banned.add("TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384"); banned.add("TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256"); banned.add("TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384"); banned.add("TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256"); banned.add("TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384"); banned.add("TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256"); banned.add("TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384"); banned.add("TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256"); banned.add("TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384"); banned.add("TLS_RSA_WITH_AES_128_CCM"); banned.add("TLS_RSA_WITH_AES_256_CCM"); banned.add("TLS_RSA_WITH_AES_128_CCM_8"); banned.add("TLS_RSA_WITH_AES_256_CCM_8"); banned.add("TLS_PSK_WITH_AES_128_CCM"); banned.add("TLS_PSK_WITH_AES_256_CCM"); banned.add("TLS_PSK_WITH_AES_128_CCM_8"); banned.add("TLS_PSK_WITH_AES_256_CCM_8"); ALPN_BANNED_CIPHERS = Collections.unmodifiableSet(banned); } static boolean isAllowed(byte b1, byte b2) { String cipher = REVERSE_CIPHERS.get(new Key(b1, b2)); if(cipher == null) { //new cipher, should be allowed return true; } return !ALPN_BANNED_CIPHERS.contains(cipher); } static boolean isAllowed(String cipher) { return !ALPN_BANNED_CIPHERS.contains(cipher); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http/ALPNLimitingSSLEngine.java000066400000000000000000000175131420065311100332730ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSession; /** * SSLEngine that will limit the cipher selection to HTTP/2 suitable protocols if the client is offering h2 as an option. *

* In theory this is not a perfect solution to the HTTP/2 cipher strength issue, but in practice it should be sufficient * as any RFC compliant implementation should be able to negotiate TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 * * @author Stuart Douglas */ public class ALPNLimitingSSLEngine extends SSLEngine { private static final SSLEngineResult UNDERFLOW_RESULT = new SSLEngineResult( SSLEngineResult.Status.BUFFER_UNDERFLOW, SSLEngineResult.HandshakeStatus.NEED_UNWRAP, 0, 0); private final SSLEngine delegate; private final Runnable invalidAlpnRunnable; private boolean done; public ALPNLimitingSSLEngine(SSLEngine delegate, Runnable invalidAlpnRunnable) { this.delegate = delegate; this.invalidAlpnRunnable = invalidAlpnRunnable; } @Override public String getPeerHost() { return delegate.getPeerHost(); } @Override public int getPeerPort() { return delegate.getPeerPort(); } @Override public SSLEngineResult wrap(ByteBuffer src, ByteBuffer dst) throws SSLException { return delegate.wrap(src, dst); } @Override public SSLEngineResult wrap(ByteBuffer[] srcs, ByteBuffer dst) throws SSLException { return wrap(srcs, 0, srcs.length, dst); } @Override public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer dst) throws SSLException { if (done) { return delegate.unwrap(src, dst); } if (ALPNOfferedClientHelloExplorer.isIncompleteHeader(src)) { return UNDERFLOW_RESULT; } try { List clientCiphers = ALPNOfferedClientHelloExplorer.parseClientHello(src); if (clientCiphers != null) { limitCiphers(clientCiphers); done = true; } else { done = true; } } catch (BufferUnderflowException e) { return UNDERFLOW_RESULT; } return delegate.unwrap(src, dst); } private void limitCiphers(List clientCiphers) { boolean clientIsCompliant = false; for (int cipher : clientCiphers) { if (cipher == 0xC02F) { //TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, required to be offered by spec clientIsCompliant = true; } } if (!clientIsCompliant) { invalidAlpnRunnable.run(); } else { List ciphers = new ArrayList<>(); for (String cipher : delegate.getEnabledCipherSuites()) { if (ALPNBannedCiphers.isAllowed(cipher)) { ciphers.add(cipher); } } delegate.setEnabledCipherSuites(ciphers.toArray(new String[ciphers.size()])); } } @Override public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts) throws SSLException { return unwrap(src, dsts, 0, dsts.length); } @Override public SSLSession getHandshakeSession() { return delegate.getHandshakeSession(); } @Override public SSLParameters getSSLParameters() { return delegate.getSSLParameters(); } @Override public void setSSLParameters(final SSLParameters sslParameters) { delegate.setSSLParameters(sslParameters); } @Override public SSLEngineResult wrap(ByteBuffer[] srcs, int off, int len, ByteBuffer dst) throws SSLException { return delegate.wrap(srcs, off, len, dst); } @Override public SSLEngineResult unwrap(ByteBuffer byteBuffer, ByteBuffer[] byteBuffers, int i, int i1) throws SSLException { if (done) { return delegate.unwrap(byteBuffer, byteBuffers, i, i1); } if (ALPNOfferedClientHelloExplorer.isIncompleteHeader(byteBuffer)) { return UNDERFLOW_RESULT; } try { List clientCiphers = ALPNOfferedClientHelloExplorer.parseClientHello(byteBuffer); if (clientCiphers != null) { limitCiphers(clientCiphers); done = true; } else { done = true; } } catch (BufferUnderflowException e) { return UNDERFLOW_RESULT; } return delegate.unwrap(byteBuffer, byteBuffers, i, i1); } @Override public Runnable getDelegatedTask() { return delegate.getDelegatedTask(); } @Override public void closeInbound() throws SSLException { delegate.closeInbound(); } @Override public boolean isInboundDone() { return delegate.isInboundDone(); } @Override public void closeOutbound() { delegate.closeOutbound(); } @Override public boolean isOutboundDone() { return delegate.isOutboundDone(); } @Override public String[] getSupportedCipherSuites() { return delegate.getSupportedCipherSuites(); } @Override public String[] getEnabledCipherSuites() { return delegate.getEnabledCipherSuites(); } @Override public void setEnabledCipherSuites(final String[] strings) { delegate.setEnabledCipherSuites(strings); } @Override public String[] getSupportedProtocols() { return delegate.getSupportedProtocols(); } @Override public String[] getEnabledProtocols() { return delegate.getEnabledProtocols(); } @Override public void setEnabledProtocols(final String[] strings) { delegate.setEnabledProtocols(strings); } @Override public SSLSession getSession() { return delegate.getSession(); } @Override public void beginHandshake() throws SSLException { delegate.beginHandshake(); } @Override public SSLEngineResult.HandshakeStatus getHandshakeStatus() { return delegate.getHandshakeStatus(); } @Override public void setUseClientMode(boolean b) { if (b) { throw new IllegalArgumentException(); } } @Override public boolean getUseClientMode() { return delegate.getUseClientMode(); } @Override public void setNeedClientAuth(final boolean b) { delegate.setNeedClientAuth(b); } @Override public boolean getNeedClientAuth() { return delegate.getNeedClientAuth(); } @Override public void setWantClientAuth(final boolean b) { delegate.setWantClientAuth(b); } @Override public boolean getWantClientAuth() { return delegate.getWantClientAuth(); } @Override public void setEnableSessionCreation(final boolean b) { delegate.setEnableSessionCreation(b); } @Override public boolean getEnableSessionCreation() { return delegate.getEnableSessionCreation(); } } ALPNOfferedClientHelloExplorer.java000066400000000000000000000217171420065311100351470ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import javax.net.ssl.SSLException; import io.undertow.UndertowMessages; /** * Parses the client handshake to retrieve ALPN and cipher info */ final class ALPNOfferedClientHelloExplorer { // Private constructor prevents construction outside this class. private ALPNOfferedClientHelloExplorer() { } /** * The header size of TLS/SSL records. *

* The value of this constant is {@value}. */ private static final int RECORD_HEADER_SIZE = 0x05; static boolean isIncompleteHeader(ByteBuffer source) { return source.remaining() < RECORD_HEADER_SIZE; } /** * Checks if a client handshake is offering ALPN, and if so it returns a list of all ciphers. If ALPN is not being * offered then this will return null. */ static List parseClientHello(ByteBuffer source) throws SSLException { ByteBuffer input = source.duplicate(); // Do we have a complete header? if (isIncompleteHeader(input)) { throw new BufferUnderflowException(); } // Is it a handshake message? byte firstByte = input.get(); byte secondByte = input.get(); byte thirdByte = input.get(); if ((firstByte & 0x80) != 0 && thirdByte == 0x01) { // looks like a V2ClientHello, we ignore it. return null; } else if (firstByte == 22) { // 22: handshake record if (secondByte == 3 && thirdByte >= 1 && thirdByte <= 3) { return exploreTLSRecord(input, firstByte, secondByte, thirdByte); } } return null; } /* * struct { * uint8 major; * uint8 minor; * } ProtocolVersion; * * enum { * change_cipher_spec(20), alert(21), handshake(22), * application_data(23), (255) * } ContentType; * * struct { * ContentType type; * ProtocolVersion version; * uint16 length; * opaque fragment[TLSPlaintext.length]; * } TLSPlaintext; */ private static List exploreTLSRecord( ByteBuffer input, byte firstByte, byte secondByte, byte thirdByte) throws SSLException { // Is it a handshake message? if (firstByte != 22) { // 22: handshake record throw UndertowMessages.MESSAGES.notHandshakeRecord(); } // Is there enough data for a full record? int recordLength = getInt16(input); if (recordLength > input.remaining()) { throw new BufferUnderflowException(); } // We have already had enough source bytes. try { return exploreHandshake(input, secondByte, thirdByte, recordLength); } catch (BufferUnderflowException ignored) { throw UndertowMessages.MESSAGES.invalidHandshakeRecord(); } } /* * enum { * hello_request(0), client_hello(1), server_hello(2), * certificate(11), server_key_exchange (12), * certificate_request(13), server_hello_done(14), * certificate_verify(15), client_key_exchange(16), * finished(20) * (255) * } HandshakeType; * * struct { * HandshakeType msg_type; * uint24 length; * select (HandshakeType) { * case hello_request: HelloRequest; * case client_hello: ClientHello; * case server_hello: ServerHello; * case certificate: Certificate; * case server_key_exchange: ServerKeyExchange; * case certificate_request: CertificateRequest; * case server_hello_done: ServerHelloDone; * case certificate_verify: CertificateVerify; * case client_key_exchange: ClientKeyExchange; * case finished: Finished; * } body; * } Handshake; */ private static List exploreHandshake( ByteBuffer input, byte recordMajorVersion, byte recordMinorVersion, int recordLength) throws SSLException { // What is the handshake type? byte handshakeType = input.get(); if (handshakeType != 0x01) { // 0x01: client_hello message throw UndertowMessages.MESSAGES.expectedClientHello(); } // What is the handshake body length? int handshakeLength = getInt24(input); // Theoretically, a single handshake message might span multiple // records, but in practice this does not occur. if (handshakeLength > recordLength - 4) { // 4: handshake header size throw UndertowMessages.MESSAGES.multiRecordSSLHandshake(); } input = input.duplicate(); input.limit(handshakeLength + input.position()); return exploreRecord(input); } /* * struct { * uint32 gmt_unix_time; * opaque random_bytes[28]; * } Random; * * opaque SessionID<0..32>; * * uint8 CipherSuite[2]; * * enum { null(0), (255) } CompressionMethod; * * struct { * ProtocolVersion client_version; * Random random; * SessionID session_id; * CipherSuite cipher_suites<2..2^16-2>; * CompressionMethod compression_methods<1..2^8-1>; * select (extensions_present) { * case false: * struct {}; * case true: * Extension extensions<0..2^16-1>; * }; * } ClientHello; */ private static List exploreRecord( ByteBuffer input) throws SSLException { // client version byte helloMajorVersion = input.get(); byte helloMinorVersion = input.get(); if (helloMajorVersion != 3 && helloMinorVersion != 3) { //we only care about TLS 1.2 return null; } // ignore random for (int i = 0; i < 32; ++i) {// 32: the length of Random byte d = input.get(); } // session id processByteVector8(input); // cipher_suites int int16 = getInt16(input); List ciphers = new ArrayList<>(); for (int i = 0; i < int16; i += 2) { ciphers.add(getInt16(input)); } // compression methods processByteVector8(input); if (input.remaining() > 0) { return exploreExtensions(input, ciphers); } return null; } /* * struct { * ExtensionType extension_type; * opaque extension_data<0..2^16-1>; * } Extension; * * enum { * server_name(0), max_fragment_length(1), * client_certificate_url(2), trusted_ca_keys(3), * truncated_hmac(4), status_request(5), (65535) * } ExtensionType; */ private static List exploreExtensions(ByteBuffer input, List ciphers) throws SSLException { int length = getInt16(input); // length of extensions while (length > 0) { int extType = getInt16(input); // extenson type int extLen = getInt16(input); // length of extension data if (extType == 16) { // 0x00: ty return ciphers; } else { // ignore other extensions processByteVector(input, extLen); } length -= extLen + 4; } return null; } private static int getInt8(ByteBuffer input) { return input.get(); } private static int getInt16(ByteBuffer input) { return (input.get() & 0xFF) << 8 | input.get() & 0xFF; } private static int getInt24(ByteBuffer input) { return (input.get() & 0xFF) << 16 | (input.get() & 0xFF) << 8 | input.get() & 0xFF; } private static void processByteVector8(ByteBuffer input) { int int8 = getInt8(input); processByteVector(input, int8); } private static void processByteVector(ByteBuffer input, int length) { for (int i = 0; i < length; ++i) { byte b = input.get(); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http/AlpnOpenListener.java000066400000000000000000000426211420065311100325540ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.function.Function; import javax.net.ssl.SSLEngine; import org.xnio.ChannelListener; import org.xnio.IoUtils; import org.xnio.OptionMap; import org.xnio.Pool; import org.xnio.StreamConnection; import org.xnio.channels.StreamSourceChannel; import org.xnio.ssl.SslConnection; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.UndertowOptions; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import io.undertow.protocols.alpn.ALPNManager; import io.undertow.protocols.alpn.ALPNProvider; import io.undertow.protocols.ssl.SslConduit; import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.server.AggregateConnectorStatistics; import io.undertow.server.ConnectorStatistics; import io.undertow.server.DelegateOpenListener; import io.undertow.server.HttpHandler; import io.undertow.server.OpenListener; import io.undertow.server.XnioByteBufferPool; /** * Open listener adaptor for ALPN connections *

* Not a proper open listener as such, but more a mechanism for selecting between them. * * @author Stuart Douglas */ public class AlpnOpenListener implements ChannelListener, OpenListener { /** * HTTP/2 required cipher. Not strictly part of ALPN but it can live here for now till we have a better solution. */ public static final String REQUIRED_CIPHER = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"; /** * Names of ciphers in IBM JVM are prefixed with `SSL` instead of `TLS`, see e.g.: * https://www.ibm.com/support/knowledgecenter/SSFKSJ_9.0.0/com.ibm.mq.dev.doc/q113210_.htm. * Thus let's have IBM alternative for the REQUIRED_CIPHER variable too. */ public static final String IBM_REQUIRED_CIPHER = "SSL_ECDHE_RSA_WITH_AES_128_GCM_SHA256"; private static final Set REQUIRED_PROTOCOLS = Collections.unmodifiableSet( new HashSet<>(Arrays.asList("TLSv1.2","TLSv1.3"))); private final ALPNManager alpnManager = ALPNManager.INSTANCE; //todo: configurable private final ByteBufferPool bufferPool; private final Map listeners = new HashMap<>(); private String[] protocols; private final String fallbackProtocol; private volatile HttpHandler rootHandler; private volatile OptionMap undertowOptions; private volatile boolean statisticsEnabled; private volatile boolean providerLogged; private volatile boolean alpnFailLogged; public AlpnOpenListener(Pool bufferPool, OptionMap undertowOptions, DelegateOpenListener httpListener) { this(bufferPool, undertowOptions, "http/1.1", httpListener); } public AlpnOpenListener(Pool bufferPool, OptionMap undertowOptions) { this(bufferPool, undertowOptions, null, null); } public AlpnOpenListener(Pool bufferPool, OptionMap undertowOptions, String fallbackProtocol, DelegateOpenListener fallbackListener) { this(new XnioByteBufferPool(bufferPool), undertowOptions, fallbackProtocol, fallbackListener); } public AlpnOpenListener(ByteBufferPool bufferPool, OptionMap undertowOptions, DelegateOpenListener httpListener) { this(bufferPool, undertowOptions, "http/1.1", httpListener); } public AlpnOpenListener(ByteBufferPool bufferPool) { this(bufferPool, OptionMap.EMPTY, null, null); } public AlpnOpenListener(ByteBufferPool bufferPool, OptionMap undertowOptions) { this(bufferPool, undertowOptions, null, null); } public AlpnOpenListener(ByteBufferPool bufferPool, OptionMap undertowOptions, String fallbackProtocol, DelegateOpenListener fallbackListener) { this.bufferPool = bufferPool; this.undertowOptions = undertowOptions; this.fallbackProtocol = fallbackProtocol; statisticsEnabled = undertowOptions.get(UndertowOptions.ENABLE_CONNECTOR_STATISTICS, false); if (fallbackProtocol != null && fallbackListener != null) { addProtocol(fallbackProtocol, fallbackListener, 0); } } @Override public HttpHandler getRootHandler() { return rootHandler; } @Override public void setRootHandler(HttpHandler rootHandler) { this.rootHandler = rootHandler; for (Map.Entry delegate : listeners.entrySet()) { delegate.getValue().listener.setRootHandler(rootHandler); } } @Override public OptionMap getUndertowOptions() { return undertowOptions; } @Override public void setUndertowOptions(OptionMap undertowOptions) { if (undertowOptions == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("undertowOptions"); } this.undertowOptions = undertowOptions; for (Map.Entry delegate : listeners.entrySet()) { delegate.getValue().listener.setRootHandler(rootHandler); } statisticsEnabled = undertowOptions.get(UndertowOptions.ENABLE_CONNECTOR_STATISTICS, false); } @Override public ByteBufferPool getBufferPool() { return bufferPool; } @Override public ConnectorStatistics getConnectorStatistics() { if (statisticsEnabled) { List stats = new ArrayList<>(); for (Map.Entry l : listeners.entrySet()) { ConnectorStatistics c = l.getValue().listener.getConnectorStatistics(); if (c != null) { stats.add(c); } } return new AggregateConnectorStatistics(stats.toArray(new ConnectorStatistics[stats.size()])); } return null; } @Override public void closeConnections() { for(Map.Entry i : listeners.entrySet()) { i.getValue().listener.closeConnections(); } } private static class ListenerEntry implements Comparable { final DelegateOpenListener listener; final int weight; final String protocol; ListenerEntry(DelegateOpenListener listener, int weight, String protocol) { this.listener = listener; this.weight = weight; this.protocol = protocol; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof ListenerEntry)) return false; ListenerEntry that = (ListenerEntry) o; if (weight != that.weight) return false; if (!listener.equals(that.listener)) return false; return protocol.equals(that.protocol); } @Override public int hashCode() { int result = listener.hashCode(); result = 31 * result + weight; result = 31 * result + protocol.hashCode(); return result; } @Override public int compareTo(ListenerEntry o) { return -Integer.compare(this.weight, o.weight); } } public AlpnOpenListener addProtocol(String name, DelegateOpenListener listener, int weight) { listeners.put(name, new ListenerEntry(listener, weight, name)); List list = new ArrayList<>(listeners.values()); Collections.sort(list); protocols = new String[list.size()]; for (int i = 0; i < list.size(); ++i) { protocols[i] = list.get(i).protocol; } return this; } public void handleEvent(final StreamConnection channel) { if (UndertowLogger.REQUEST_LOGGER.isTraceEnabled()) { UndertowLogger.REQUEST_LOGGER.tracef("Opened connection with %s", channel.getPeerAddress()); } final SslConduit sslConduit = UndertowXnioSsl.getSslConduit((SslConnection) channel); final SSLEngine originalSSlEngine = sslConduit.getSSLEngine(); //this will end up with the ALPN engine, or null if the engine did not support ALPN final CompletableFuture selectedALPNEngine = new CompletableFuture<>(); alpnManager.registerEngineCallback(originalSSlEngine, new SSLConduitUpdater(sslConduit, new Function() { @Override public SSLEngine apply(SSLEngine engine) { if (!engineSupportsHTTP2(engine)) { if (!alpnFailLogged) { synchronized (this) { if (!alpnFailLogged) { UndertowLogger.REQUEST_LOGGER.debugf("ALPN has been configured however %s is not present or TLS1.2 is not enabled, falling back to default protocol", REQUIRED_CIPHER); alpnFailLogged = true; } } } if (fallbackProtocol != null) { ListenerEntry listener = listeners.get(fallbackProtocol); if (listener != null) { selectedALPNEngine.complete(null); return engine; } } } final ALPNProvider provider = alpnManager.getProvider(engine); if (provider == null) { if (!providerLogged) { synchronized (this) { if (!providerLogged) { UndertowLogger.REQUEST_LOGGER.debugf("ALPN has been configured however no provider could be found for engine %s for connector at %s", engine, channel.getLocalAddress()); providerLogged = true; } } } if (fallbackProtocol != null) { ListenerEntry listener = listeners.get(fallbackProtocol); if (listener != null) { selectedALPNEngine.complete(null); return engine; } } UndertowLogger.REQUEST_LOGGER.debugf("No ALPN provider available and no fallback defined"); IoUtils.safeClose(channel); selectedALPNEngine.complete(null); return engine; } if (!providerLogged) { synchronized (this) { if (!providerLogged) { UndertowLogger.REQUEST_LOGGER.debugf("Using ALPN provider %s for connector at %s", provider, channel.getLocalAddress()); providerLogged = true; } } } final SSLEngine newEngine = provider.setProtocols(engine, protocols); ALPNLimitingSSLEngine alpnLimitingSSLEngine = new ALPNLimitingSSLEngine(newEngine, new Runnable() { @Override public void run() { provider.setProtocols(newEngine, new String[]{fallbackProtocol}); } }); selectedALPNEngine.complete(new SelectedAlpn(newEngine, provider)); //we don't want the limiting engine, but the actual one we can use with a provider return alpnLimitingSSLEngine; } })); final AlpnConnectionListener potentialConnection = new AlpnConnectionListener(channel, selectedALPNEngine); channel.getSourceChannel().setReadListener(potentialConnection); potentialConnection.handleEvent(channel.getSourceChannel()); } public static boolean engineSupportsHTTP2(SSLEngine engine) { //check to make sure the engine meets the minimum requirements for HTTP/2 //if not then ALPN will not be attempted String[] protcols = engine.getEnabledProtocols(); boolean found = false; for (String proto : protcols) { if (REQUIRED_PROTOCOLS.contains(proto)) { found = true; break; } } if (!found) { return false; } String[] ciphers = engine.getEnabledCipherSuites(); for (String i : ciphers) { if (i.equals(REQUIRED_CIPHER) || i.equals(IBM_REQUIRED_CIPHER)) { return true; } } return false; } private class AlpnConnectionListener implements ChannelListener { private final StreamConnection channel; private final CompletableFuture selectedAlpn; private AlpnConnectionListener(StreamConnection channel, CompletableFuture selectedAlpn) { this.channel = channel; this.selectedAlpn = selectedAlpn; } @Override public void handleEvent(StreamSourceChannel source) { PooledByteBuffer buffer = bufferPool.allocate(); boolean free = true; try { while (true) { int res = channel.getSourceChannel().read(buffer.getBuffer()); if (res == -1) { IoUtils.safeClose(channel); return; } buffer.getBuffer().flip(); SelectedAlpn selectedAlpn = this.selectedAlpn.getNow(null); final String selected; if (selectedAlpn != null) { selected = selectedAlpn.provider.getSelectedProtocol(selectedAlpn.engine); } else { selected = null; } if (selected != null) { DelegateOpenListener listener; if (selected.isEmpty()) { //alpn not in use if (fallbackProtocol == null) { UndertowLogger.REQUEST_IO_LOGGER.noALPNFallback(channel.getPeerAddress()); IoUtils.safeClose(channel); return; } listener = listeners.get(fallbackProtocol).listener; } else { listener = listeners.get(selected).listener; } source.getReadSetter().set(null); listener.handleEvent(channel, buffer); free = false; return; } else if (res > 0) { if (fallbackProtocol == null) { UndertowLogger.REQUEST_IO_LOGGER.noALPNFallback(channel.getPeerAddress()); IoUtils.safeClose(channel); return; } DelegateOpenListener listener = listeners.get(fallbackProtocol).listener; source.getReadSetter().set(null); listener.handleEvent(channel, buffer); free = false; return; } else if (res == 0) { channel.getSourceChannel().resumeReads(); return; } } } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); IoUtils.safeClose(channel); } catch (Throwable t) { UndertowLogger.REQUEST_IO_LOGGER.handleUnexpectedFailure(t); IoUtils.safeClose(channel); } finally { if (free) { buffer.close(); } } } } static final class SelectedAlpn { final SSLEngine engine; final ALPNProvider provider; SelectedAlpn(SSLEngine engine, ALPNProvider provider) { this.engine = engine; this.provider = provider; } } static final class SSLConduitUpdater implements Function { final SslConduit conduit; final Function underlying; SSLConduitUpdater(SslConduit conduit, Function underlying) { this.conduit = conduit; this.underlying = underlying; } @Override public SSLEngine apply(SSLEngine engine) { SSLEngine res = underlying.apply(engine); conduit.setSslEngine(res); return res; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http/CacheMap.java000066400000000000000000000027541420065311100307760ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http; import java.util.LinkedHashMap; import java.util.Map; /** * @author Stuart Douglas */ public class CacheMap extends LinkedHashMap { /** * The load factor used when none specified in constructor. */ static final float DEFAULT_LOAD_FACTOR = 0.75f; private static final long serialVersionUID = 1L; private int capacity; public CacheMap(int capacity) { super(capacity, DEFAULT_LOAD_FACTOR, true); this.capacity = capacity; } /** * removeEldestEntry() should be overridden by the user, otherwise it will not * remove the oldest object from the Map. */ @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > this.capacity; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http/HttpAttachments.java000066400000000000000000000062661420065311100324520ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http; import io.undertow.util.AttachmentKey; import io.undertow.util.HeaderMap; import java.util.function.Supplier; /** * Exchange attachments that have specific meaning when using the HTTP protocol * * @author Stuart Douglas */ public class HttpAttachments { /** * Attachment key for request trailers when using chunked encoding. When the request is parsed the trailers * will be attached under this key. */ public static final AttachmentKey REQUEST_TRAILERS = AttachmentKey.create(HeaderMap.class); /** * Attachment key for response trailers. If a header map is attached under this key then the contents will be written * out at the end of the chunked request or HTTP/2 response. * * Note that the results of {@link #RESPONSE_TRAILERS} and {@link #RESPONSE_TRAILER_SUPPLIER} will be merged if both exit * with the value supplied by the supplier taking precedence. * * Note that if pre chunked streams are being used then the trailers will not be appended to the response, however any * trailers parsed out of the chunked stream will be attached here instead. */ public static final AttachmentKey RESPONSE_TRAILERS = AttachmentKey.create(HeaderMap.class); /** * Attachment key for a supplier response trailers. If a header map is attached under this key then the contents will be written * out at the end of the chunked request or HTTP/2 response. * * Note that the results of {@link #RESPONSE_TRAILERS} and {@link #RESPONSE_TRAILER_SUPPLIER} will be merged if both exit * with the value supplied by the supplier taking precedence. * * Note that if pre chunked streams are being used then the trailers will not be appended to the response, however any * trailers parsed out of the chunked stream will be attached here instead. */ public static final AttachmentKey> RESPONSE_TRAILER_SUPPLIER = AttachmentKey.create(Supplier.class); /** * If the value {@code true} is attached to the exchange under this key then Undertow will assume that the underlying application * has already taken care of chunking, and will not attempt to add its own chunk markers. * * This will only take effect if the application has explicitly set the {@literal Transfer-Encoding: chunked} header. * */ public static final AttachmentKey PRE_CHUNKED_RESPONSE = AttachmentKey.create(Boolean.class); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http/HttpContinue.java000066400000000000000000000243771420065311100317660ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http; import java.io.IOException; import java.nio.channels.Channel; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import org.xnio.ChannelExceptionHandler; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.channels.StreamSinkChannel; import io.undertow.UndertowMessages; import io.undertow.io.IoCallback; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.AttachmentKey; import io.undertow.util.HeaderMap; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.Protocols; import io.undertow.util.StatusCodes; /** * Class that provides support for dealing with HTTP 100 (Continue) responses. *

* Note that if a client is pipelining some requests and sending continue for others this * could cause problems if the pipelining buffer is enabled. * * @author Stuart Douglas */ public class HttpContinue { private static final Set COMPATIBLE_PROTOCOLS; static { Set compat = new HashSet<>(); compat.add(Protocols.HTTP_1_1); compat.add(Protocols.HTTP_2_0); COMPATIBLE_PROTOCOLS = Collections.unmodifiableSet(compat); } public static final String CONTINUE = "100-continue"; private static final AttachmentKey ALREADY_SENT = AttachmentKey.create(Boolean.class); /** * Returns true if this exchange requires the server to send a 100 (Continue) response. * * @param exchange The exchange * @return true if the server needs to send a continue response */ public static boolean requiresContinueResponse(final HttpServerExchange exchange) { if (!COMPATIBLE_PROTOCOLS.contains(exchange.getProtocol()) || exchange.isResponseStarted() || !exchange.getConnection().isContinueResponseSupported() || exchange.getAttachment(ALREADY_SENT) != null) { return false; } HeaderMap requestHeaders = exchange.getRequestHeaders(); return requiresContinueResponse(requestHeaders); } public static boolean requiresContinueResponse(HeaderMap requestHeaders) { List expect = requestHeaders.get(Headers.EXPECT); if (expect != null) { for (String header : expect) { if (header.equalsIgnoreCase(CONTINUE)) { return true; } } } return false; } public static boolean isContinueResponseSent(HttpServerExchange exchange) { return exchange.getAttachment(ALREADY_SENT) != null; } /** * Sends a continuation using async IO, and calls back when it is complete. * * @param exchange The exchange * @param callback The completion callback */ public static void sendContinueResponse(final HttpServerExchange exchange, final IoCallback callback) { if (!exchange.isResponseChannelAvailable()) { callback.onException(exchange, null, UndertowMessages.MESSAGES.cannotSendContinueResponse()); return; } internalSendContinueResponse(exchange, callback); } /** * Creates a response sender that can be used to send a HTTP 100-continue response. * * @param exchange The exchange * @return The response sender */ public static ContinueResponseSender createResponseSender(final HttpServerExchange exchange) throws IOException { if (!exchange.isResponseChannelAvailable()) { throw UndertowMessages.MESSAGES.cannotSendContinueResponse(); } if(exchange.getAttachment(ALREADY_SENT) != null) { return new ContinueResponseSender() { @Override public boolean send() throws IOException { return true; } @Override public void awaitWritable() throws IOException { } @Override public void awaitWritable(long time, TimeUnit timeUnit) throws IOException { } }; } HttpServerExchange newExchange = exchange.getConnection().sendOutOfBandResponse(exchange); exchange.putAttachment(ALREADY_SENT, true); newExchange.setStatusCode(StatusCodes.CONTINUE); newExchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, 0); final StreamSinkChannel responseChannel = newExchange.getResponseChannel(); return new ContinueResponseSender() { boolean shutdown = false; @Override public boolean send() throws IOException { if (!shutdown) { shutdown = true; responseChannel.shutdownWrites(); } return responseChannel.flush(); } @Override public void awaitWritable() throws IOException { responseChannel.awaitWritable(); } @Override public void awaitWritable(final long time, final TimeUnit timeUnit) throws IOException { responseChannel.awaitWritable(time, timeUnit); } }; } /** * Marks a continue response as already having been sent. In general this should only be used * by low level handlers than need fine grained control over the continue response. * * @param exchange The exchange */ public static void markContinueResponseSent(HttpServerExchange exchange) { exchange.putAttachment(ALREADY_SENT, true); } /** * Sends a continue response using blocking IO * * @param exchange The exchange */ public static void sendContinueResponseBlocking(final HttpServerExchange exchange) throws IOException { if (!exchange.isResponseChannelAvailable()) { throw UndertowMessages.MESSAGES.cannotSendContinueResponse(); } if(exchange.getAttachment(ALREADY_SENT) != null) { return; } HttpServerExchange newExchange = exchange.getConnection().sendOutOfBandResponse(exchange); exchange.putAttachment(ALREADY_SENT, true); newExchange.setStatusCode(StatusCodes.CONTINUE); newExchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, 0); newExchange.startBlocking(); newExchange.getOutputStream().close(); newExchange.getInputStream().close(); } /** * Sets a 417 response code and ends the exchange. * * @param exchange The exchange to reject */ public static void rejectExchange(final HttpServerExchange exchange) { exchange.setStatusCode(StatusCodes.EXPECTATION_FAILED); exchange.setPersistent(false); exchange.endExchange(); } private static void internalSendContinueResponse(final HttpServerExchange exchange, final IoCallback callback) { if(exchange.getAttachment(ALREADY_SENT) != null) { callback.onComplete(exchange, null); return; } HttpServerExchange newExchange = exchange.getConnection().sendOutOfBandResponse(exchange); exchange.putAttachment(ALREADY_SENT, true); newExchange.setStatusCode(StatusCodes.CONTINUE); newExchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, 0); final StreamSinkChannel responseChannel = newExchange.getResponseChannel(); try { responseChannel.shutdownWrites(); if (!responseChannel.flush()) { responseChannel.getWriteSetter().set(ChannelListeners.flushingChannelListener( new ChannelListener() { @Override public void handleEvent(StreamSinkChannel channel) { channel.suspendWrites(); exchange.dispatch(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { callback.onComplete(exchange, null); } }); } }, new ChannelExceptionHandler() { @Override public void handleException(Channel channel, final IOException e) { exchange.dispatch(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { callback.onException(exchange, null, e); } }); } })); responseChannel.resumeWrites(); exchange.dispatch(); } else { callback.onComplete(exchange, null); } } catch (IOException e) { callback.onException(exchange, null, e); } } /** * A continue response that is in the process of being sent. */ public interface ContinueResponseSender { /** * Continue sending the response. * * @return true if the response is fully sent, false otherwise. */ boolean send() throws IOException; void awaitWritable() throws IOException; void awaitWritable(long time, final TimeUnit timeUnit) throws IOException; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http/HttpOpenListener.java000066400000000000000000000176131420065311100326040ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.xnio.ChannelListener; import org.xnio.IoUtils; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.Pool; import org.xnio.StreamConnection; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.UndertowOptions; import io.undertow.conduits.BytesReceivedStreamSourceConduit; import io.undertow.conduits.BytesSentStreamSinkConduit; import io.undertow.conduits.IdleTimeoutConduit; import io.undertow.conduits.ReadTimeoutStreamSourceConduit; import io.undertow.conduits.WriteTimeoutStreamSinkConduit; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.ConnectorStatistics; import io.undertow.server.ConnectorStatisticsImpl; import io.undertow.server.DelegateOpenListener; import io.undertow.server.HttpHandler; import io.undertow.server.ServerConnection; import io.undertow.server.XnioByteBufferPool; /** * Open listener for HTTP server. XNIO should be set up to chain the accept handler to post-accept open * listeners to this listener which actually initiates HTTP parsing. * * @author David M. Lloyd */ public final class HttpOpenListener implements ChannelListener, DelegateOpenListener { private final Set connections = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final ByteBufferPool bufferPool; private final int bufferSize; private volatile HttpHandler rootHandler; private volatile OptionMap undertowOptions; private volatile HttpRequestParser parser; private volatile boolean statisticsEnabled; private final ConnectorStatisticsImpl connectorStatistics; @Deprecated public HttpOpenListener(final Pool pool) { this(pool, OptionMap.EMPTY); } @Deprecated public HttpOpenListener(final Pool pool, final OptionMap undertowOptions) { this(new XnioByteBufferPool(pool), undertowOptions); } public HttpOpenListener(final ByteBufferPool pool) { this(pool, OptionMap.EMPTY); } public HttpOpenListener(final ByteBufferPool pool, final OptionMap undertowOptions) { this.undertowOptions = undertowOptions; this.bufferPool = pool; PooledByteBuffer buf = pool.allocate(); this.bufferSize = buf.getBuffer().remaining(); buf.close(); parser = HttpRequestParser.instance(undertowOptions); connectorStatistics = new ConnectorStatisticsImpl(); statisticsEnabled = undertowOptions.get(UndertowOptions.ENABLE_CONNECTOR_STATISTICS, false); } @Override public void handleEvent(StreamConnection channel) { handleEvent(channel, null); } @Override public void handleEvent(final StreamConnection channel, PooledByteBuffer buffer) { if (UndertowLogger.REQUEST_LOGGER.isTraceEnabled()) { UndertowLogger.REQUEST_LOGGER.tracef("Opened connection with %s", channel.getPeerAddress()); } //set read and write timeouts try { Integer readTimeout = channel.getOption(Options.READ_TIMEOUT); Integer idle = undertowOptions.get(UndertowOptions.IDLE_TIMEOUT); if (idle != null) { IdleTimeoutConduit conduit = new IdleTimeoutConduit(channel); channel.getSourceChannel().setConduit(conduit); channel.getSinkChannel().setConduit(conduit); } if (readTimeout != null && readTimeout > 0) { channel.getSourceChannel().setConduit(new ReadTimeoutStreamSourceConduit(channel.getSourceChannel().getConduit(), channel, this)); } Integer writeTimeout = channel.getOption(Options.WRITE_TIMEOUT); if (writeTimeout != null && writeTimeout > 0) { channel.getSinkChannel().setConduit(new WriteTimeoutStreamSinkConduit(channel.getSinkChannel().getConduit(), channel, this)); } } catch (IOException e) { IoUtils.safeClose(channel); UndertowLogger.REQUEST_IO_LOGGER.ioException(e); } catch (Throwable t) { IoUtils.safeClose(channel); UndertowLogger.REQUEST_IO_LOGGER.handleUnexpectedFailure(t); } if (statisticsEnabled) { channel.getSinkChannel().setConduit(new BytesSentStreamSinkConduit(channel.getSinkChannel().getConduit(), connectorStatistics.sentAccumulator())); channel.getSourceChannel().setConduit(new BytesReceivedStreamSourceConduit(channel.getSourceChannel().getConduit(), connectorStatistics.receivedAccumulator())); } HttpServerConnection connection = new HttpServerConnection(channel, bufferPool, rootHandler, undertowOptions, bufferSize, statisticsEnabled ? connectorStatistics : null); HttpReadListener readListener = new HttpReadListener(connection, parser, statisticsEnabled ? connectorStatistics : null); if (buffer != null) { if (buffer.getBuffer().hasRemaining()) { connection.setExtraBytes(buffer); } else { buffer.close(); } } if (connectorStatistics != null && statisticsEnabled) { connectorStatistics.incrementConnectionCount(); } connections.add(connection); connection.addCloseListener(new ServerConnection.CloseListener() { @Override public void closed(ServerConnection c) { connections.remove(connection); } }); connection.setReadListener(readListener); readListener.newRequest(); channel.getSourceChannel().setReadListener(readListener); readListener.handleEvent(channel.getSourceChannel()); } @Override public HttpHandler getRootHandler() { return rootHandler; } @Override public void setRootHandler(final HttpHandler rootHandler) { this.rootHandler = rootHandler; } @Override public OptionMap getUndertowOptions() { return undertowOptions; } @Override public void setUndertowOptions(final OptionMap undertowOptions) { if (undertowOptions == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("undertowOptions"); } this.undertowOptions = undertowOptions; this.parser = HttpRequestParser.instance(undertowOptions); statisticsEnabled = undertowOptions.get(UndertowOptions.ENABLE_CONNECTOR_STATISTICS, false); } @Override public ByteBufferPool getBufferPool() { return bufferPool; } @Override public ConnectorStatistics getConnectorStatistics() { if (statisticsEnabled) { return connectorStatistics; } return null; } @Override public void closeConnections() { for(HttpServerConnection i : connections) { i.getIoThread().execute(new Runnable() { @Override public void run() { IoUtils.safeClose(i); } }); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http/HttpReadListener.java000066400000000000000000000526621420065311100325610ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.UndertowOptions; import io.undertow.conduits.ReadDataStreamSourceConduit; import io.undertow.connector.PooledByteBuffer; import io.undertow.protocols.http2.Http2Channel; import io.undertow.server.ConnectorStatisticsImpl; import io.undertow.server.Connectors; import io.undertow.server.HttpServerExchange; import io.undertow.server.protocol.ParseTimeoutUpdater; import io.undertow.server.protocol.http2.Http2ReceiveListener; import io.undertow.util.ClosingChannelExceptionHandler; import io.undertow.util.ConnectionUtils; import io.undertow.util.HeaderValues; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.Methods; import io.undertow.util.Protocols; import io.undertow.util.StringWriteChannelListener; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.StreamConnection; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.ConduitStreamSinkChannel; import org.xnio.conduits.ConduitStreamSourceChannel; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; /** * Listener which reads requests and headers off of an HTTP stream. * * @author David M. Lloyd */ final class HttpReadListener implements ChannelListener, Runnable { /** * used for HTTP2 prior knowledge support */ private static final HttpString PRI = new HttpString("PRI"); private static final byte[] PRI_EXPECTED = new byte[] {'S', 'M', '\r', '\n', '\r', '\n'}; private static final String BAD_REQUEST = "HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"; private final HttpServerConnection connection; private final ParseState state; private final HttpRequestParser parser; private HttpServerExchange httpServerExchange; private int read = 0; private final int maxRequestSize; private final long maxEntitySize; private final boolean recordRequestStartTime; private final boolean allowUnknownProtocols; private final boolean requireHostHeader; //0 = new request ok, reads resumed //1 = request running, new request not ok //2 = suspending/resuming in progress @SuppressWarnings("unused") private volatile int requestState; private static final AtomicIntegerFieldUpdater requestStateUpdater = AtomicIntegerFieldUpdater.newUpdater(HttpReadListener.class, "requestState"); private final ConnectorStatisticsImpl connectorStatistics; private ParseTimeoutUpdater parseTimeoutUpdater; HttpReadListener(final HttpServerConnection connection, final HttpRequestParser parser, ConnectorStatisticsImpl connectorStatistics) { this.connection = connection; this.parser = parser; this.connectorStatistics = connectorStatistics; this.maxRequestSize = connection.getUndertowOptions().get(UndertowOptions.MAX_HEADER_SIZE, UndertowOptions.DEFAULT_MAX_HEADER_SIZE); this.maxEntitySize = connection.getUndertowOptions().get(UndertowOptions.MAX_ENTITY_SIZE, UndertowOptions.DEFAULT_MAX_ENTITY_SIZE); this.recordRequestStartTime = connection.getUndertowOptions().get(UndertowOptions.RECORD_REQUEST_START_TIME, false); this.requireHostHeader = connection.getUndertowOptions().get(UndertowOptions.REQUIRE_HOST_HTTP11, true); this.allowUnknownProtocols = connection.getUndertowOptions().get(UndertowOptions.ALLOW_UNKNOWN_PROTOCOLS, false); int requestParseTimeout = connection.getUndertowOptions().get(UndertowOptions.REQUEST_PARSE_TIMEOUT, -1); int requestIdleTimeout = connection.getUndertowOptions().get(UndertowOptions.NO_REQUEST_TIMEOUT, -1); if(requestIdleTimeout < 0 && requestParseTimeout < 0) { this.parseTimeoutUpdater = null; } else { this.parseTimeoutUpdater = new ParseTimeoutUpdater(connection, requestParseTimeout, requestIdleTimeout); connection.addCloseListener(parseTimeoutUpdater); } state = new ParseState(connection.getUndertowOptions().get(UndertowOptions.HTTP_HEADERS_CACHE_SIZE, UndertowOptions.DEFAULT_HTTP_HEADERS_CACHE_SIZE)); } public void newRequest() { state.reset(); read = 0; if(parseTimeoutUpdater != null) { parseTimeoutUpdater.connectionIdle(); } connection.setCurrentExchange(null); } public void handleEvent(final ConduitStreamSourceChannel channel) { while (requestStateUpdater.get(this) != 0) { //if the CAS fails it is because another thread is in the process of changing state //we just immediately retry if (requestStateUpdater.compareAndSet(this, 1, 2)) { try { channel.suspendReads(); } finally { requestStateUpdater.set(this, 1); } return; } } handleEventWithNoRunningRequest(channel); } public void handleEventWithNoRunningRequest(final ConduitStreamSourceChannel channel) { PooledByteBuffer existing = connection.getExtraBytes(); if ((existing == null && connection.getOriginalSourceConduit().isReadShutdown()) || connection.getOriginalSinkConduit().isWriteShutdown()) { IoUtils.safeClose(connection); channel.suspendReads(); return; } final PooledByteBuffer pooled = existing == null ? connection.getByteBufferPool().allocate() : existing; final ByteBuffer buffer = pooled.getBuffer(); boolean free = true; try { int res; boolean bytesRead = false; do { if (existing == null) { buffer.clear(); try { res = channel.read(buffer); } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.debug("Error reading request", e); IoUtils.safeClose(connection); return; } } else { res = buffer.remaining(); } if (res <= 0) { if(bytesRead && parseTimeoutUpdater != null) { parseTimeoutUpdater.failedParse(); } handleFailedRead(channel, res); return; } else { bytesRead = true; } if (existing != null) { existing = null; connection.setExtraBytes(null); } else { buffer.flip(); } int begin = buffer.remaining(); if(httpServerExchange == null) { httpServerExchange = new HttpServerExchange(connection, maxEntitySize); } parser.handle(buffer, state, httpServerExchange); if (buffer.hasRemaining()) { free = false; connection.setExtraBytes(pooled); } int total = read + (begin - buffer.remaining()); read = total; if (read > maxRequestSize) { UndertowLogger.REQUEST_LOGGER.requestHeaderWasTooLarge(connection.getPeerAddress(), maxRequestSize); sendBadRequestAndClose(connection.getChannel(), null); return; } } while (!state.isComplete()); if(parseTimeoutUpdater != null) { parseTimeoutUpdater.requestStarted(); } connection.getOriginalSourceConduit().suspendReads(); final HttpServerExchange httpServerExchange = this.httpServerExchange; httpServerExchange.setRequestScheme(connection.getSslSession() != null ? "https" : "http"); this.httpServerExchange = null; requestStateUpdater.set(this, 1); if (recordRequestStartTime) { Connectors.setRequestStartTime(httpServerExchange); } if(httpServerExchange.getProtocol() == Protocols.HTTP_2_0) { free = handleHttp2PriorKnowledge(pooled, httpServerExchange); return; } if(!allowUnknownProtocols) { HttpString protocol = httpServerExchange.getProtocol(); if(protocol != Protocols.HTTP_1_1 && protocol != Protocols.HTTP_1_0 && protocol != Protocols.HTTP_0_9) { UndertowLogger.REQUEST_IO_LOGGER.debugf("Closing connection from %s due to unknown protocol %s", connection.getChannel().getPeerAddress(), protocol); sendBadRequestAndClose(connection.getChannel(), new IOException()); return; } } HttpTransferEncoding.setupRequest(httpServerExchange); connection.setCurrentExchange(httpServerExchange); if(connectorStatistics != null) { connectorStatistics.setup(httpServerExchange); } if(connection.getSslSession() != null) { //TODO: figure out a better solution for this //in order to improve performance we do not generally suspend reads, instead we a CAS to detect when //data arrives while a request is running and suspend lazily, as suspend/resume is relatively expensive //however this approach does not work for SSL, as the underlying channel is not thread safe //so we just suspend every time (the overhead is likely much less than the general SSL overhead anyway) channel.suspendReads(); } HeaderValues host = httpServerExchange.getRequestHeaders().get(Headers.HOST); if(host != null && host.size() > 1) { sendBadRequestAndClose(connection.getChannel(), UndertowMessages.MESSAGES.moreThanOneHostHeader()); return; } if(requireHostHeader && httpServerExchange.getProtocol().equals(Protocols.HTTP_1_1)) { if(host == null || host.size() ==0 || host.getFirst().isEmpty()) { sendBadRequestAndClose(connection.getChannel(), UndertowMessages.MESSAGES.noHostInHttp11Request()); return; } } if(!Connectors.areRequestHeadersValid(httpServerExchange.getRequestHeaders())) { sendBadRequestAndClose(connection.getChannel(), UndertowMessages.MESSAGES.invalidHeaders()); return; } Connectors.executeRootHandler(connection.getRootHandler(), httpServerExchange); } catch (Throwable t) { sendBadRequestAndClose(connection.getChannel(), t); return; } finally { if (free) pooled.close(); } } private boolean handleHttp2PriorKnowledge(PooledByteBuffer pooled, HttpServerExchange httpServerExchange) throws IOException { if(httpServerExchange.getRequestMethod().equals(PRI) && connection.getUndertowOptions().get(UndertowOptions.ENABLE_HTTP2, false)) { handleHttp2PriorKnowledge(connection.getChannel(), connection, pooled); return false; } else { sendBadRequestAndClose(connection.getChannel(), new IOException()); return true; } } private void handleFailedRead(ConduitStreamSourceChannel channel, int res) { if (res == 0) { channel.setReadListener(this); channel.resumeReads(); } else if (res == -1) { IoUtils.safeClose(connection); } } private void sendBadRequestAndClose(final StreamConnection connection, final Throwable exception) { UndertowLogger.REQUEST_IO_LOGGER.failedToParseRequest(exception); connection.getSourceChannel().suspendReads(); new StringWriteChannelListener(BAD_REQUEST) { @Override protected void writeDone(final StreamSinkChannel c) { super.writeDone(c); c.suspendWrites(); IoUtils.safeClose(connection); } @Override protected void handleError(StreamSinkChannel channel, IOException e) { IoUtils.safeClose(connection); } }.setup(connection.getSinkChannel()); } public void exchangeComplete(final HttpServerExchange exchange) { connection.clearChannel(); connection.setCurrentExchange(null); final HttpServerConnection connection = this.connection; if (exchange.isPersistent() && !isUpgradeOrConnect(exchange)) { final StreamConnection channel = connection.getChannel(); if (connection.getExtraBytes() == null) { //if we are not pipelining we just register a listener //we have to resume from with the io thread if (exchange.isInIoThread()) { //no need for CAS, we are in the IO thread newRequest(); channel.getSourceChannel().setReadListener(HttpReadListener.this); channel.getSourceChannel().resumeReads(); requestStateUpdater.set(this, 0); } else { while (true) { if (connection.getOriginalSourceConduit().isReadShutdown() || connection.getOriginalSinkConduit().isWriteShutdown()) { channel.getSourceChannel().suspendReads(); channel.getSinkChannel().suspendWrites(); IoUtils.safeClose(connection); return; } else { if (requestStateUpdater.compareAndSet(this, 1, 2)) { try { newRequest(); channel.getSourceChannel().setReadListener(HttpReadListener.this); channel.getSourceChannel().resumeReads(); } finally { requestStateUpdater.set(this, 0); } break; } } } } } else { if (exchange.isInIoThread()) { requestStateUpdater.set(this, 0); //no need to CAS, as we don't actually resume newRequest(); //no need to suspend reads here, the task will always run before the read listener anyway channel.getIoThread().execute(this); } else { while (true) { if (connection.getOriginalSinkConduit().isWriteShutdown()) { channel.getSourceChannel().suspendReads(); channel.getSinkChannel().suspendWrites(); IoUtils.safeClose(connection); return; } else if (requestStateUpdater.compareAndSet(this, 1, 2)) { try { newRequest(); channel.getSourceChannel().suspendReads(); } finally { requestStateUpdater.set(this, 0); } break; } } Executor executor = exchange.getDispatchExecutor(); if (executor == null) { executor = exchange.getConnection().getWorker(); } executor.execute(this); } } } else if (!exchange.isPersistent()) { if (connection.getExtraBytes() != null) { connection.getExtraBytes().close(); connection.setExtraBytes(null); } ConnectionUtils.cleanClose(connection.getChannel(), connection); } else { //upgrade or connect handling if (connection.getExtraBytes() != null) { connection.getChannel().getSourceChannel().setConduit(new ReadDataStreamSourceConduit(connection.getChannel().getSourceChannel().getConduit(), connection)); } try { if (!connection.getChannel().getSinkChannel().flush()) { connection.getChannel().getSinkChannel().setWriteListener(ChannelListeners.flushingChannelListener(new ChannelListener() { @Override public void handleEvent(ConduitStreamSinkChannel conduitStreamSinkChannel) { connection.getUpgradeListener().handleUpgrade(connection.getChannel(), exchange); } }, new ClosingChannelExceptionHandler(connection))); connection.getChannel().getSinkChannel().resumeWrites(); return; } connection.getUpgradeListener().handleUpgrade(connection.getChannel(), exchange); } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); IoUtils.safeClose(connection); } catch (Throwable t) { UndertowLogger.REQUEST_IO_LOGGER.handleUnexpectedFailure(t); IoUtils.safeClose(connection); } } } private boolean isUpgradeOrConnect(HttpServerExchange exchange) { return exchange.isUpgrade() || (exchange.getRequestMethod().equals(Methods.CONNECT) && ((HttpServerConnection)exchange.getConnection()).isConnectHandled() ); } @Override public void run() { handleEvent(connection.getChannel().getSourceChannel()); } private void handleHttp2PriorKnowledge(final StreamConnection connection, final HttpServerConnection serverConnection, PooledByteBuffer readData) throws IOException { final ConduitStreamSourceChannel request = connection.getSourceChannel(); byte[] data = new byte[PRI_EXPECTED.length]; final ByteBuffer buffer = ByteBuffer.wrap(data); if(readData.getBuffer().hasRemaining()) { while (readData.getBuffer().hasRemaining() && buffer.hasRemaining()) { buffer.put(readData.getBuffer().get()); } } final PooledByteBuffer extraData; if(readData.getBuffer().hasRemaining()) { extraData = readData; } else { readData.close(); extraData = null; } if(!doHttp2PriRead(connection, buffer, serverConnection, extraData)) { request.getReadSetter().set(new ChannelListener() { @Override public void handleEvent(StreamSourceChannel channel) { try { doHttp2PriRead(connection, buffer, serverConnection, extraData); } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); IoUtils.safeClose(connection); } catch (Throwable t) { UndertowLogger.REQUEST_IO_LOGGER.handleUnexpectedFailure(t); IoUtils.safeClose(connection); } } }); request.resumeReads(); } } private boolean doHttp2PriRead(StreamConnection connection, ByteBuffer buffer, HttpServerConnection serverConnection, PooledByteBuffer extraData) throws IOException { if(buffer.hasRemaining()) { int res = connection.getSourceChannel().read(buffer); if (res == -1) { return true; //fail } if (buffer.hasRemaining()) { return false; } } buffer.flip(); for(int i = 0; i < PRI_EXPECTED.length; ++i) { if(buffer.get() != PRI_EXPECTED[i]) { throw UndertowMessages.MESSAGES.http2PriRequestFailed(); } } Http2Channel channel = new Http2Channel(connection, null, serverConnection.getByteBufferPool(), extraData, false, false, false, serverConnection.getUndertowOptions()); Http2ReceiveListener receiveListener = new Http2ReceiveListener(serverConnection.getRootHandler(), serverConnection.getUndertowOptions(), serverConnection.getBufferSize(), null); channel.getReceiveSetter().set(receiveListener); channel.resumeReceives(); return true; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http/HttpRequestParser.java000066400000000000000000001147161420065311100330040ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http; import java.io.UnsupportedEncodingException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import io.undertow.UndertowMessages; import io.undertow.UndertowOptions; import io.undertow.annotationprocessor.HttpParserConfig; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.Methods; import io.undertow.util.Protocols; import io.undertow.util.URLUtils; import io.undertow.util.BadRequestException; import org.xnio.OptionMap; import static io.undertow.util.Headers.ACCEPT_CHARSET_STRING; import static io.undertow.util.Headers.ACCEPT_ENCODING_STRING; import static io.undertow.util.Headers.ACCEPT_LANGUAGE_STRING; import static io.undertow.util.Headers.ACCEPT_RANGES_STRING; import static io.undertow.util.Headers.ACCEPT_STRING; import static io.undertow.util.Headers.AUTHORIZATION_STRING; import static io.undertow.util.Headers.CACHE_CONTROL_STRING; import static io.undertow.util.Headers.CONNECTION_STRING; import static io.undertow.util.Headers.CONTENT_LENGTH_STRING; import static io.undertow.util.Headers.CONTENT_TYPE_STRING; import static io.undertow.util.Headers.COOKIE_STRING; import static io.undertow.util.Headers.EXPECT_STRING; import static io.undertow.util.Headers.FROM_STRING; import static io.undertow.util.Headers.HOST_STRING; import static io.undertow.util.Headers.IF_MATCH_STRING; import static io.undertow.util.Headers.IF_MODIFIED_SINCE_STRING; import static io.undertow.util.Headers.IF_NONE_MATCH_STRING; import static io.undertow.util.Headers.IF_RANGE_STRING; import static io.undertow.util.Headers.IF_UNMODIFIED_SINCE_STRING; import static io.undertow.util.Headers.MAX_FORWARDS_STRING; import static io.undertow.util.Headers.ORIGIN_STRING; import static io.undertow.util.Headers.PRAGMA_STRING; import static io.undertow.util.Headers.PROXY_AUTHORIZATION_STRING; import static io.undertow.util.Headers.RANGE_STRING; import static io.undertow.util.Headers.REFERER_STRING; import static io.undertow.util.Headers.REFRESH_STRING; import static io.undertow.util.Headers.SEC_WEB_SOCKET_KEY_STRING; import static io.undertow.util.Headers.SEC_WEB_SOCKET_VERSION_STRING; import static io.undertow.util.Headers.SERVER_STRING; import static io.undertow.util.Headers.SSL_CIPHER_STRING; import static io.undertow.util.Headers.SSL_CIPHER_USEKEYSIZE_STRING; import static io.undertow.util.Headers.SSL_CLIENT_CERT_STRING; import static io.undertow.util.Headers.SSL_SESSION_ID_STRING; import static io.undertow.util.Headers.STRICT_TRANSPORT_SECURITY_STRING; import static io.undertow.util.Headers.TRAILER_STRING; import static io.undertow.util.Headers.TRANSFER_ENCODING_STRING; import static io.undertow.util.Headers.UPGRADE_STRING; import static io.undertow.util.Headers.USER_AGENT_STRING; import static io.undertow.util.Headers.VIA_STRING; import static io.undertow.util.Headers.WARNING_STRING; import static io.undertow.util.Methods.CONNECT_STRING; import static io.undertow.util.Methods.DELETE_STRING; import static io.undertow.util.Methods.GET_STRING; import static io.undertow.util.Methods.HEAD_STRING; import static io.undertow.util.Methods.OPTIONS_STRING; import static io.undertow.util.Methods.POST_STRING; import static io.undertow.util.Methods.PUT_STRING; import static io.undertow.util.Methods.TRACE_STRING; import static io.undertow.util.Protocols.HTTP_0_9_STRING; import static io.undertow.util.Protocols.HTTP_1_0_STRING; import static io.undertow.util.Protocols.HTTP_1_1_STRING; import static io.undertow.util.Protocols.HTTP_2_0_STRING; /** * The basic HTTP parser. The actual parser is a sub class of this class that is generated as part of * the build process by the {@link io.undertow.annotationprocessor.AbstractParserGenerator} annotation processor. *

* The actual processor is a state machine, that means that for common header, method, protocol values * it will return an interned string, rather than creating a new string for each one. *

* * @author Stuart Douglas */ @HttpParserConfig(methods = { OPTIONS_STRING, GET_STRING, HEAD_STRING, POST_STRING, PUT_STRING, DELETE_STRING, TRACE_STRING, CONNECT_STRING}, protocols = { HTTP_0_9_STRING, HTTP_1_0_STRING, HTTP_1_1_STRING, HTTP_2_0_STRING }, headers = { ACCEPT_STRING, ACCEPT_CHARSET_STRING, ACCEPT_ENCODING_STRING, ACCEPT_LANGUAGE_STRING, ACCEPT_RANGES_STRING, AUTHORIZATION_STRING, CACHE_CONTROL_STRING, COOKIE_STRING, CONNECTION_STRING, CONTENT_LENGTH_STRING, CONTENT_TYPE_STRING, EXPECT_STRING, FROM_STRING, HOST_STRING, IF_MATCH_STRING, IF_MODIFIED_SINCE_STRING, IF_NONE_MATCH_STRING, IF_RANGE_STRING, IF_UNMODIFIED_SINCE_STRING, MAX_FORWARDS_STRING, ORIGIN_STRING, PRAGMA_STRING, PROXY_AUTHORIZATION_STRING, RANGE_STRING, REFERER_STRING, REFRESH_STRING, SEC_WEB_SOCKET_KEY_STRING, SEC_WEB_SOCKET_VERSION_STRING, SERVER_STRING, SSL_CLIENT_CERT_STRING, SSL_CIPHER_STRING, SSL_SESSION_ID_STRING, SSL_CIPHER_USEKEYSIZE_STRING, STRICT_TRANSPORT_SECURITY_STRING, TRAILER_STRING, TRANSFER_ENCODING_STRING, UPGRADE_STRING, USER_AGENT_STRING, VIA_STRING, WARNING_STRING }) public abstract class HttpRequestParser { private static final byte[] HTTP; public static final int HTTP_LENGTH; private final int maxParameters; private final int maxHeaders; private final boolean allowEncodedSlash; private final boolean decode; private final String charset; private final int maxCachedHeaderSize; private final boolean allowUnescapedCharactersInUrl; private static final boolean[] ALLOWED_TARGET_CHARACTER = new boolean[256]; static { try { HTTP = "HTTP/1.".getBytes("ASCII"); HTTP_LENGTH = HTTP.length; } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } for(int i = 0; i < 256; ++i) { if(i < 32 || i > 126) { ALLOWED_TARGET_CHARACTER[i] = false; } else { switch ((char)i) { case '\"': case '#': case '<': case '>': case '\\': case '^': case '`': case '{': case '|': case '}': ALLOWED_TARGET_CHARACTER[i] = false; break; default: ALLOWED_TARGET_CHARACTER[i] = true; } } } } public static boolean isTargetCharacterAllowed(char c) { return ALLOWED_TARGET_CHARACTER[c]; } public HttpRequestParser(OptionMap options) { maxParameters = options.get(UndertowOptions.MAX_PARAMETERS, UndertowOptions.DEFAULT_MAX_PARAMETERS); maxHeaders = options.get(UndertowOptions.MAX_HEADERS, UndertowOptions.DEFAULT_MAX_HEADERS); allowEncodedSlash = options.get(UndertowOptions.ALLOW_ENCODED_SLASH, false); decode = options.get(UndertowOptions.DECODE_URL, true); charset = options.get(UndertowOptions.URL_CHARSET, StandardCharsets.UTF_8.name()); maxCachedHeaderSize = options.get(UndertowOptions.MAX_CACHED_HEADER_SIZE, UndertowOptions.DEFAULT_MAX_CACHED_HEADER_SIZE); this.allowUnescapedCharactersInUrl = options.get(UndertowOptions.ALLOW_UNESCAPED_CHARACTERS_IN_URL, false); } public static final HttpRequestParser instance(final OptionMap options) { try { final Class cls = Class.forName(HttpRequestParser.class.getName() + "$$generated", false, HttpRequestParser.class.getClassLoader()); Constructor ctor = cls.getConstructor(OptionMap.class); return (HttpRequestParser) ctor.newInstance(options); } catch (Exception e) { throw new RuntimeException(e); } } public void handle(ByteBuffer buffer, final ParseState currentState, final HttpServerExchange builder) throws BadRequestException { if (currentState.state == ParseState.VERB) { //fast path, we assume that it will parse fully so we avoid all the if statements //fast path HTTP GET requests, basically just assume all requests are get //and fall out to the state machine if it is not final int position = buffer.position(); if (buffer.remaining() > 3 && buffer.get(position) == 'G' && buffer.get(position + 1) == 'E' && buffer.get(position + 2) == 'T' && buffer.get(position + 3) == ' ') { buffer.position(position + 4); builder.setRequestMethod(Methods.GET); currentState.state = ParseState.PATH; } else { try { handleHttpVerb(buffer, currentState, builder); } catch (IllegalArgumentException e) { throw new BadRequestException(e); } } handlePath(buffer, currentState, builder); boolean failed = false; if (buffer.remaining() > HTTP_LENGTH + 3) { int pos = buffer.position(); for (int i = 0; i < HTTP_LENGTH; ++i) { if (HTTP[i] != buffer.get(pos + i)) { failed = true; break; } } if (!failed) { final byte b = buffer.get(pos + HTTP_LENGTH); final byte b2 = buffer.get(pos + HTTP_LENGTH + 1); final byte b3 = buffer.get(pos + HTTP_LENGTH + 2); if (b2 == '\r' && b3 == '\n') { if (b == '1') { builder.setProtocol(Protocols.HTTP_1_1); buffer.position(pos + HTTP_LENGTH + 3); currentState.state = ParseState.HEADER; } else if (b == '0') { builder.setProtocol(Protocols.HTTP_1_0); buffer.position(pos + HTTP_LENGTH + 3); currentState.state = ParseState.HEADER; } else { failed = true; } } else { failed = true; } } } else { failed = true; } if (failed) { handleHttpVersion(buffer, currentState, builder); handleAfterVersion(buffer, currentState); } while (currentState.state != ParseState.PARSE_COMPLETE && buffer.hasRemaining()) { handleHeader(buffer, currentState, builder); if (currentState.state == ParseState.HEADER_VALUE) { handleHeaderValue(buffer, currentState, builder); } } return; } handleStateful(buffer, currentState, builder); } private void handleStateful(ByteBuffer buffer, ParseState currentState, HttpServerExchange builder) throws BadRequestException { if (currentState.state == ParseState.PATH) { handlePath(buffer, currentState, builder); if (!buffer.hasRemaining()) { return; } } if (currentState.state == ParseState.QUERY_PARAMETERS) { handleQueryParameters(buffer, currentState, builder); if (!buffer.hasRemaining()) { return; } } if (currentState.state == ParseState.PATH_PARAMETERS) { handlePathParameters(buffer, currentState, builder); if (!buffer.hasRemaining()) { return; } // we could go back to PATH after PATH_PARAMETERS if (currentState.state == ParseState.PATH) { handlePath(buffer, currentState, builder); if (!buffer.hasRemaining()) { return; } } } if (currentState.state == ParseState.VERSION) { handleHttpVersion(buffer, currentState, builder); if (!buffer.hasRemaining()) { return; } } if (currentState.state == ParseState.AFTER_VERSION) { handleAfterVersion(buffer, currentState); if (!buffer.hasRemaining()) { return; } } while (currentState.state != ParseState.PARSE_COMPLETE) { if (currentState.state == ParseState.HEADER) { handleHeader(buffer, currentState, builder); if (!buffer.hasRemaining()) { return; } } if (currentState.state == ParseState.HEADER_VALUE) { handleHeaderValue(buffer, currentState, builder); if (!buffer.hasRemaining()) { return; } } } } abstract void handleHttpVerb(ByteBuffer buffer, final ParseState currentState, final HttpServerExchange builder) throws BadRequestException; abstract void handleHttpVersion(ByteBuffer buffer, final ParseState currentState, final HttpServerExchange builder) throws BadRequestException; abstract void handleHeader(ByteBuffer buffer, final ParseState currentState, final HttpServerExchange builder) throws BadRequestException; /** * The parse states for parsing the path. */ private static final int START = 0; private static final int FIRST_COLON = 1; private static final int FIRST_SLASH = 2; private static final int SECOND_SLASH = 3; private static final int IN_PATH = 4; private static final int HOST_DONE = 5; /** * Parses a path value * * @param buffer The buffer * @param state The current state * @param exchange The exchange builder * @return The number of bytes remaining */ @SuppressWarnings("unused") final void handlePath(ByteBuffer buffer, ParseState state, HttpServerExchange exchange) throws BadRequestException { StringBuilder stringBuilder = state.stringBuilder; int parseState = state.parseState; int canonicalPathStart = state.pos; boolean urlDecodeRequired = state.urlDecodeRequired; while (buffer.hasRemaining()) { char next = (char) (buffer.get() & 0xFF); if(!allowUnescapedCharactersInUrl && !ALLOWED_TARGET_CHARACTER[next]) { throw new BadRequestException(UndertowMessages.MESSAGES.invalidCharacterInRequestTarget(next)); } if (next == ' ' || next == '\t') { if (stringBuilder.length() != 0) { final String path = stringBuilder.toString(); parsePathComplete(state, exchange, canonicalPathStart, parseState, urlDecodeRequired, path); exchange.setQueryString(""); state.state = ParseState.VERSION; return; } } else if (next == '\r' || next == '\n') { throw UndertowMessages.MESSAGES.failedToParsePath(); } else if (next == '?' && (parseState == START || parseState == HOST_DONE || parseState == IN_PATH)) { beginQueryParameters(buffer, state, exchange, stringBuilder, parseState, canonicalPathStart, urlDecodeRequired); return; } else if (next == ';') { state.parseState = parseState; state.urlDecodeRequired = urlDecodeRequired; state.pos = canonicalPathStart; // store at canonical path the partial path parsed up until here state.canonicalPath.append(stringBuilder.substring(canonicalPathStart)); // handle the path parameters handlePathParameters(buffer, state, exchange); // if state is PATH, it means that handlePathParameters found a / after parsing path parameters // so, we expect a next chunk of path in the next bytes, mark this with canonicalPathStart // and append the '/' read by handlePathParameters if(state.state == ParseState.PATH) { canonicalPathStart = stringBuilder.length(); stringBuilder.append('/'); } else { // path has been entirely parsed, just return return; } } else { if (decode && (next == '%' || next > 127)) { urlDecodeRequired = true; } else if (next == ':' && parseState == START) { parseState = FIRST_COLON; } else if (next == '/' && parseState == FIRST_COLON) { parseState = FIRST_SLASH; } else if (next == '/' && parseState == FIRST_SLASH) { parseState = SECOND_SLASH; } else if (next == '/' && parseState == SECOND_SLASH) { parseState = HOST_DONE; canonicalPathStart = stringBuilder.length(); } else if (parseState == FIRST_COLON || parseState == FIRST_SLASH) { parseState = IN_PATH; } else if (next == '/' && parseState != HOST_DONE) { parseState = IN_PATH; } stringBuilder.append(next); } } state.parseState = parseState; state.pos = canonicalPathStart; state.urlDecodeRequired = urlDecodeRequired; } private void parsePathComplete(ParseState state, HttpServerExchange exchange, int canonicalPathStart, int parseState, boolean urlDecodeRequired, String path) { if (parseState == SECOND_SLASH) { exchange.setRequestPath("/"); exchange.setRelativePath("/"); exchange.setRequestURI(path, true); } else if (parseState < HOST_DONE && state.canonicalPath.length() == 0) { String decodedPath = decode(path, urlDecodeRequired, state, allowEncodedSlash, false); exchange.setRequestPath(decodedPath); exchange.setRelativePath(decodedPath); exchange.setRequestURI(path, false); } else { handleFullUrl(state, exchange, canonicalPathStart, urlDecodeRequired, path, parseState); } state.stringBuilder.setLength(0); state.canonicalPath.setLength(0); state.parseState = 0; state.pos = 0; state.urlDecodeRequired = false; } private void beginQueryParameters(ByteBuffer buffer, ParseState state, HttpServerExchange exchange, StringBuilder stringBuilder, int parseState, int canonicalPathStart, boolean urlDecodeRequired) throws BadRequestException { final String path = stringBuilder.toString(); parsePathComplete(state, exchange, canonicalPathStart, parseState, urlDecodeRequired, path); state.state = ParseState.QUERY_PARAMETERS; handleQueryParameters(buffer, state, exchange); } private void handleFullUrl(ParseState state, HttpServerExchange exchange, int canonicalPathStart, boolean urlDecodeRequired, String path, int parseState) { state.canonicalPath.append(path.substring(canonicalPathStart)); String thePath = decode(state.canonicalPath.toString(), urlDecodeRequired, state, allowEncodedSlash, false); exchange.setRequestPath(thePath); exchange.setRelativePath(thePath); exchange.setRequestURI(path, parseState == HOST_DONE); } /** * Parses query string parameters * * @param buffer The buffer * @param state The current state * @param exchange The exchange builder * @return The number of bytes remaining */ @SuppressWarnings("unused") final void handleQueryParameters(ByteBuffer buffer, ParseState state, HttpServerExchange exchange) throws BadRequestException { StringBuilder stringBuilder = state.stringBuilder; int queryParamPos = state.pos; int mapCount = state.mapCount; boolean urlDecodeRequired = state.urlDecodeRequired; String nextQueryParam = state.nextQueryParam; //so this is a bit funky, because it not only deals with parsing, but //also deals with URL decoding the query parameters as well, while also //maintaining a non-decoded version to use as the query string //In most cases these string will be the same, and as we do not want to //build up two separate strings we don't use encodedStringBuilder unless //we encounter an encoded character while (buffer.hasRemaining()) { char next = (char) (buffer.get() & 0xFF); if(!allowUnescapedCharactersInUrl && !ALLOWED_TARGET_CHARACTER[next]) { throw new BadRequestException(UndertowMessages.MESSAGES.invalidCharacterInRequestTarget(next)); } if (next == ' ' || next == '\t') { final String queryString = stringBuilder.toString(); exchange.setQueryString(queryString); if (nextQueryParam == null) { if (queryParamPos != stringBuilder.length()) { exchange.addQueryParam(decode(stringBuilder.substring(queryParamPos), urlDecodeRequired, state, true, true), ""); } } else { exchange.addQueryParam(nextQueryParam, decode(stringBuilder.substring(queryParamPos), urlDecodeRequired, state, true, true)); } state.state = ParseState.VERSION; state.stringBuilder.setLength(0); state.pos = 0; state.nextQueryParam = null; state.urlDecodeRequired = false; state.mapCount = 0; return; } else if (next == '\r' || next == '\n') { throw UndertowMessages.MESSAGES.failedToParsePath(); } else { if (decode && (next == '+' || next == '%' || next > 127)) { //+ is only a whitespace substitute in the query part of the URL urlDecodeRequired = true; } else if (next == '=' && nextQueryParam == null) { nextQueryParam = decode(stringBuilder.substring(queryParamPos), urlDecodeRequired, state, true, true); urlDecodeRequired = false; queryParamPos = stringBuilder.length() + 1; } else if (next == '&' && nextQueryParam == null) { if (++mapCount >= maxParameters) { throw UndertowMessages.MESSAGES.tooManyQueryParameters(maxParameters); } if (queryParamPos != stringBuilder.length()) { exchange.addQueryParam(decode(stringBuilder.substring(queryParamPos), urlDecodeRequired, state, true, true), ""); } urlDecodeRequired = false; queryParamPos = stringBuilder.length() + 1; } else if (next == '&') { if (++mapCount >= maxParameters) { throw UndertowMessages.MESSAGES.tooManyQueryParameters(maxParameters); } exchange.addQueryParam(nextQueryParam, decode(stringBuilder.substring(queryParamPos), urlDecodeRequired, state, true, true)); urlDecodeRequired = false; queryParamPos = stringBuilder.length() + 1; nextQueryParam = null; } stringBuilder.append(next); } } state.pos = queryParamPos; state.nextQueryParam = nextQueryParam; state.urlDecodeRequired = urlDecodeRequired; state.mapCount = mapCount; } private String decode(final String value, boolean urlDecodeRequired, ParseState state, final boolean allowEncodedSlash, final boolean formEncoded) { if (urlDecodeRequired) { return URLUtils.decode(value, charset, allowEncodedSlash, formEncoded, state.decodeBuffer); } else { return value; } } /** * This method should only ever be called if the path contains am non-decoded semi colon character ';' */ final void handlePathParameters(ByteBuffer buffer, ParseState state, HttpServerExchange exchange) throws BadRequestException { state.state = ParseState.PATH_PARAMETERS; boolean urlDecodeRequired = state.urlDecodeRequired; String param = state.nextQueryParam; final StringBuilder stringBuilder = state.stringBuilder; stringBuilder.append(";"); int pos = stringBuilder.length(); //so this is a bit funky, because it not only deals with parsing, but //also deals with URL decoding the query parameters as well, while also //maintaining a non-decoded version to use as the query string //In most cases these string will be the same, and as we do not want to //build up two separate strings we don't use encodedStringBuilder unless //we encounter an encoded character while (buffer.hasRemaining()) { char next = (char) (buffer.get() & 0xFF); if(!allowUnescapedCharactersInUrl && !ALLOWED_TARGET_CHARACTER[next]) { throw new BadRequestException(UndertowMessages.MESSAGES.invalidCharacterInRequestTarget(next)); } // end of HTTP URI if (next == ' ' || next == '\t' || next == '?') { handleParsedParam(param, stringBuilder.substring(pos), exchange, urlDecodeRequired, state); final String path = stringBuilder.toString(); // the canonicalPathStart should be the current length to not add anything to it parsePathComplete(state, exchange, path.length(), state.parseState, urlDecodeRequired, path); state.state = ParseState.VERSION; state.nextQueryParam = null; if (next == '?') { state.state = ParseState.QUERY_PARAMETERS; handleQueryParameters(buffer, state, exchange); } else exchange.setQueryString(""); return; } else if (next == '\r' || next == '\n') { throw UndertowMessages.MESSAGES.failedToParsePath(); } else if (next == '/') { handleParsedParam(param, stringBuilder.substring(pos), exchange, urlDecodeRequired, state); state.pos = stringBuilder.length(); state.state = ParseState.PATH; state.nextQueryParam = null; return; } else { if (decode && (next == '+' || next == '%' || next > 127)) { urlDecodeRequired = true; } if (next == '=' && param == null) { param = decode(stringBuilder.substring(pos), urlDecodeRequired, state, true, true); urlDecodeRequired = false; pos = stringBuilder.length() + 1; } else if (next == ';') { handleParsedParam(param, stringBuilder.substring(pos), exchange, urlDecodeRequired, state); param = null; pos = stringBuilder.length() + 1; } else if (next == ',') { if(param == null) { throw UndertowMessages.MESSAGES.failedToParsePath(); } else { handleParsedParam(param, stringBuilder.substring(pos), exchange, urlDecodeRequired, state); pos = stringBuilder.length() + 1; } } stringBuilder.append(next); } } state.urlDecodeRequired = urlDecodeRequired; state.pos = pos; state.urlDecodeRequired = urlDecodeRequired; state.nextQueryParam = param; } private void handleParsedParam(String previouslyParsedParam, String parsedParam, HttpServerExchange exchange, boolean urlDecodeRequired, ParseState state) throws BadRequestException { if (previouslyParsedParam == null) { exchange.addPathParam(decode(parsedParam, urlDecodeRequired, state, true, true), ""); } else { // path param already parsed so parse and add the value exchange.addPathParam(previouslyParsedParam, decode(parsedParam, urlDecodeRequired, state, true, true)); } } /** * The parse states for parsing heading values */ private static final int NORMAL = 0; private static final int WHITESPACE = 1; private static final int BEGIN_LINE_END = 2; private static final int LINE_END = 3; private static final int AWAIT_DATA_END = 4; /** * Parses a header value. This is called from the generated bytecode. * * @param buffer The buffer * @param state The current state * @param builder The exchange builder * @return The number of bytes remaining */ @SuppressWarnings("unused") final void handleHeaderValue(ByteBuffer buffer, ParseState state, HttpServerExchange builder) throws BadRequestException { HttpString headerName = state.nextHeader; StringBuilder stringBuilder = state.stringBuilder; CacheMap headerValuesCache = state.headerValuesCache; if (headerName != null && stringBuilder.length() == 0 && headerValuesCache != null) { String existing = headerValuesCache.get(headerName); if (existing != null) { if (handleCachedHeader(existing, buffer, state, builder)) { return; } } } handleHeaderValueCacheMiss(buffer, state, builder, headerName, headerValuesCache, stringBuilder); } private void handleHeaderValueCacheMiss(ByteBuffer buffer, ParseState state, HttpServerExchange builder, HttpString headerName, CacheMap headerValuesCache, StringBuilder stringBuilder) throws BadRequestException { int parseState = state.parseState; while (buffer.hasRemaining() && parseState == NORMAL) { final byte next = buffer.get(); if (next == '\r') { parseState = BEGIN_LINE_END; } else if (next == '\n') { parseState = LINE_END; } else if (next == ' ' || next == '\t') { parseState = WHITESPACE; } else { stringBuilder.append((char) (next & 0xFF)); } } while (buffer.hasRemaining()) { final byte next = buffer.get(); switch (parseState) { case NORMAL: { if (next == '\r') { parseState = BEGIN_LINE_END; } else if (next == '\n') { parseState = LINE_END; } else if (next == ' ' || next == '\t') { parseState = WHITESPACE; } else { stringBuilder.append((char) (next & 0xFF)); } break; } case WHITESPACE: { if (next == '\r') { parseState = BEGIN_LINE_END; } else if (next == '\n') { parseState = LINE_END; } else if (next == ' ' || next == '\t') { } else { if (stringBuilder.length() > 0) { stringBuilder.append(' '); } stringBuilder.append((char) (next & 0xFF)); parseState = NORMAL; } break; } case LINE_END: case BEGIN_LINE_END: { if (next == '\n' && parseState == BEGIN_LINE_END) { parseState = LINE_END; } else if (next == '\t' || next == ' ') { //this is a continuation parseState = WHITESPACE; } else { //we have a header String headerValue = stringBuilder.toString(); if (++state.mapCount > maxHeaders) { throw new BadRequestException(UndertowMessages.MESSAGES.tooManyHeaders(maxHeaders)); } //TODO: we need to decode this according to RFC-2047 if we have seen a =? symbol builder.getRequestHeaders().add(headerName, headerValue); if(headerValuesCache != null && headerName.length() + headerValue.length() < maxCachedHeaderSize) { headerValuesCache.put(headerName, headerValue); } state.nextHeader = null; state.leftOver = next; state.stringBuilder.setLength(0); if (next == '\r') { parseState = AWAIT_DATA_END; } else if (next == '\n') { state.state = ParseState.PARSE_COMPLETE; return; } else { state.state = ParseState.HEADER; state.parseState = 0; return; } } break; } case AWAIT_DATA_END: { state.state = ParseState.PARSE_COMPLETE; return; } } } //we only write to the state if we did not finish parsing state.parseState = parseState; } protected boolean handleCachedHeader(String existing, ByteBuffer buffer, ParseState state, HttpServerExchange builder) throws BadRequestException { int pos = buffer.position(); while (pos < buffer.limit() && buffer.get(pos) == ' ') { pos++; } if (existing.length() + 3 + pos > buffer.limit()) { return false; } int i = 0; while (i < existing.length()) { byte b = buffer.get(pos + i); if (b != existing.charAt(i)) { return false; } ++i; } if (buffer.get(pos + i++) != '\r') { return false; } if (buffer.get(pos + i++) != '\n') { return false; } int next = buffer.get(pos + i); if (next == '\t' || next == ' ') { //continuation return false; } buffer.position(pos + i); if (++state.mapCount > maxHeaders) { throw new BadRequestException(UndertowMessages.MESSAGES.tooManyHeaders(maxHeaders)); } //TODO: we need to decode this according to RFC-2047 if we have seen a =? symbol builder.getRequestHeaders().add(state.nextHeader, existing); state.nextHeader = null; state.state = ParseState.HEADER; state.parseState = 0; return true; } protected void handleAfterVersion(ByteBuffer buffer, ParseState state) throws BadRequestException { boolean newLine = state.leftOver == '\n'; while (buffer.hasRemaining()) { final byte next = buffer.get(); if (newLine) { if (next == '\n') { state.state = ParseState.PARSE_COMPLETE; return; } else { state.state = ParseState.HEADER; state.leftOver = next; return; } } else { if (next == '\n') { newLine = true; } else if (next != '\r' && next != ' ' && next != '\t') { state.state = ParseState.HEADER; state.leftOver = next; return; } else { throw UndertowMessages.MESSAGES.badRequest(); } } } if (newLine) { state.leftOver = '\n'; } } /** * This is a bit of hack to enable the parser to get access to the HttpString's that are sorted * in the static fields of the relevant classes. This means that in most cases a HttpString comparison * will take the fast path == route, as they will be the same object * * @return */ @SuppressWarnings("unused") protected static Map httpStrings() { final Map results = new HashMap<>(); final Class[] classs = {Headers.class, Methods.class, Protocols.class}; for (Class c : classs) { for (Field field : c.getDeclaredFields()) { if (field.getType().equals(HttpString.class)) { HttpString result = null; try { result = (HttpString) field.get(null); results.put(result.toString(), result); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } } } return results; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http/HttpResponseConduit.java000066400000000000000000000777621420065311100333340ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.allAreSet; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import org.xnio.Buffers; import org.xnio.IoUtils; import org.xnio.XnioWorker; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.AbstractStreamSinkConduit; import org.xnio.conduits.ConduitWritableByteChannel; import org.xnio.conduits.Conduits; import org.xnio.conduits.StreamSinkConduit; import io.undertow.UndertowMessages; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.Connectors; import io.undertow.server.HttpServerExchange; import io.undertow.util.HeaderMap; import io.undertow.util.HeaderValues; import io.undertow.util.HttpString; import io.undertow.util.Protocols; import io.undertow.util.StatusCodes; /** * @author David M. Lloyd */ final class HttpResponseConduit extends AbstractStreamSinkConduit { private final ByteBufferPool pool; private final HttpServerConnection connection; private int state = STATE_START; private long fiCookie = -1L; private String string; private HeaderValues headerValues; private int valueIdx; private int charIndex; private PooledByteBuffer pooledBuffer; private PooledByteBuffer pooledFileTransferBuffer; private HttpServerExchange exchange; private ByteBuffer[] writevBuffer; private boolean done = false; private static final int STATE_BODY = 0; // Message body, normal pass-through operation private static final int STATE_START = 1; // No headers written yet private static final int STATE_HDR_NAME = 2; // Header name indexed by charIndex private static final int STATE_HDR_D = 3; // Header delimiter ':' private static final int STATE_HDR_DS = 4; // Header delimiter ': ' private static final int STATE_HDR_VAL = 5; // Header value private static final int STATE_HDR_EOL_CR = 6; // Header line CR private static final int STATE_HDR_EOL_LF = 7; // Header line LF private static final int STATE_HDR_FINAL_CR = 8; // Final CR private static final int STATE_HDR_FINAL_LF = 9; // Final LF private static final int STATE_BUF_FLUSH = 10; // flush the buffer and go to writing body private static final int MASK_STATE = 0x0000000F; private static final int FLAG_SHUTDOWN = 0x00000010; HttpResponseConduit(final StreamSinkConduit next, final ByteBufferPool pool, HttpServerConnection connection) { super(next); this.pool = pool; this.connection = connection; } HttpResponseConduit(final StreamSinkConduit next, final ByteBufferPool pool, HttpServerConnection connection, HttpServerExchange exchange) { super(next); this.pool = pool; this.connection = connection; this.exchange = exchange; } void reset(HttpServerExchange exchange) { this.exchange = exchange; state = STATE_START; fiCookie = -1L; string = null; headerValues = null; valueIdx = 0; charIndex = 0; } /** * Handles writing out the header data. It can also take a byte buffer of user * data, to enable both user data and headers to be written out in a single operation, * which has a noticeable performance impact. *

* It is up to the caller to note the current position of this buffer before and after they * call this method, and use this to figure out how many bytes (if any) have been written. * * @param state * @param userData * @return * @throws IOException */ private int processWrite(int state, final Object userData, int pos, int length) throws IOException { if (done || exchange == null) { throw new ClosedChannelException(); } try { assert state != STATE_BODY; if (state == STATE_BUF_FLUSH) { final ByteBuffer byteBuffer = pooledBuffer.getBuffer(); do { long res = 0; ByteBuffer[] data; if (userData == null || length == 0) { res = next.write(byteBuffer); } else if (userData instanceof ByteBuffer) { data = writevBuffer; if (data == null) { data = writevBuffer = new ByteBuffer[2]; } data[0] = byteBuffer; data[1] = (ByteBuffer) userData; res = next.write(data, 0, 2); } else { data = writevBuffer; if (data == null || data.length < length + 1) { data = writevBuffer = new ByteBuffer[length + 1]; } data[0] = byteBuffer; System.arraycopy(userData, pos, data, 1, length); res = next.write(data, 0, length + 1); } if (res == 0) { return STATE_BUF_FLUSH; } } while (byteBuffer.hasRemaining()); bufferDone(); return STATE_BODY; } else if (state != STATE_START) { return processStatefulWrite(state, userData, pos, length); } //merge the cookies into the header map Connectors.flattenCookies(exchange); if (pooledBuffer == null) { pooledBuffer = pool.allocate(); } ByteBuffer buffer = pooledBuffer.getBuffer(); assert buffer.remaining() >= 50; Protocols.HTTP_1_1.appendTo(buffer); buffer.put((byte) ' '); int code = exchange.getStatusCode(); assert 999 >= code && code >= 100; buffer.put((byte) (code / 100 + '0')); buffer.put((byte) (code / 10 % 10 + '0')); buffer.put((byte) (code % 10 + '0')); buffer.put((byte) ' '); String string = exchange.getReasonPhrase(); if(string == null) { string = StatusCodes.getReason(code); } if(string.length() > buffer.remaining()) { pooledBuffer.close(); pooledBuffer = null; truncateWrites(); throw UndertowMessages.MESSAGES.reasonPhraseToLargeForBuffer(string); } writeString(buffer, string); buffer.put((byte) '\r').put((byte) '\n'); int remaining = buffer.remaining(); HeaderMap headers = exchange.getResponseHeaders(); long fiCookie = headers.fastIterateNonEmpty(); while (fiCookie != -1) { HeaderValues headerValues = headers.fiCurrent(fiCookie); HttpString header = headerValues.getHeaderName(); int headerSize = header.length(); int valueIdx = 0; while (valueIdx < headerValues.size()) { remaining -= (headerSize + 2); if (remaining < 0) { this.fiCookie = fiCookie; this.string = string; this.headerValues = headerValues; this.valueIdx = valueIdx; this.charIndex = 0; this.state = STATE_HDR_NAME; buffer.flip(); return processStatefulWrite(STATE_HDR_NAME, userData, pos, length); } header.appendTo(buffer); buffer.put((byte) ':').put((byte) ' '); string = headerValues.get(valueIdx++); remaining -= (string.length() + 2); if (remaining < 2) {//we use 2 here, to make sure we always have room for the final \r\n this.fiCookie = fiCookie; this.string = string; this.headerValues = headerValues; this.valueIdx = valueIdx; this.charIndex = 0; this.state = STATE_HDR_VAL; buffer.flip(); return processStatefulWrite(STATE_HDR_VAL, userData, pos, length); } writeString(buffer, string); buffer.put((byte) '\r').put((byte) '\n'); } fiCookie = headers.fiNextNonEmpty(fiCookie); } buffer.put((byte) '\r').put((byte) '\n'); buffer.flip(); do { long res = 0; ByteBuffer[] data; if (userData == null) { res = next.write(buffer); } else if (userData instanceof ByteBuffer) { data = writevBuffer; if (data == null) { data = writevBuffer = new ByteBuffer[2]; } data[0] = buffer; data[1] = (ByteBuffer) userData; res = next.write(data, 0, 2); } else { data = writevBuffer; if (data == null || data.length < length + 1) { data = writevBuffer = new ByteBuffer[length + 1]; } data[0] = buffer; System.arraycopy(userData, pos, data, 1, length); res = next.write(data, 0, length + 1); } if (res == 0) { return STATE_BUF_FLUSH; } } while (buffer.hasRemaining()); bufferDone(); return STATE_BODY; } catch (IOException | RuntimeException | Error e) { //WFLY-4696, just to be safe if (pooledBuffer != null) { pooledBuffer.close(); pooledBuffer = null; } throw e; } } private void bufferDone() { if(exchange == null) { return; } HttpServerConnection connection = (HttpServerConnection)exchange.getConnection(); if(connection.getExtraBytes() != null && connection.isOpen() && exchange.isRequestComplete()) { //if we are pipelining we hold onto the buffer pooledBuffer.getBuffer().clear(); } else { pooledBuffer.close(); pooledBuffer = null; this.exchange = null; } } public void freeContinueResponse() { if (pooledBuffer != null) { pooledBuffer.close(); pooledBuffer = null; } } private static void writeString(ByteBuffer buffer, String string) { int length = string.length(); for (int charIndex = 0; charIndex < length; charIndex++) { char c = string.charAt(charIndex); byte b = (byte) c; if(b != '\r' && b != '\n') { buffer.put(b); } else { buffer.put((byte) ' '); } } } /** * Handles writing out the header data in the case where is is too big to fit into a buffer. This is a much slower code path. */ private int processStatefulWrite(int state, final Object userData, int pos, int len) throws IOException { ByteBuffer buffer = pooledBuffer.getBuffer(); long fiCookie = this.fiCookie; int valueIdx = this.valueIdx; int charIndex = this.charIndex; int length; String string = this.string; HeaderValues headerValues = this.headerValues; int res; // BUFFER IS FLIPPED COMING IN if (buffer.hasRemaining()) { do { res = next.write(buffer); if (res == 0) { return state; } } while (buffer.hasRemaining()); } buffer.clear(); HeaderMap headers = exchange.getResponseHeaders(); // BUFFER IS NOW EMPTY FOR FILLING for (; ; ) { switch (state) { case STATE_HDR_NAME: { final HttpString headerName = headerValues.getHeaderName(); length = headerName.length(); while (charIndex < length) { if (buffer.hasRemaining()) { buffer.put(headerName.byteAt(charIndex++)); } else { buffer.flip(); do { res = next.write(buffer); if (res == 0) { this.string = string; this.headerValues = headerValues; this.charIndex = charIndex; this.fiCookie = fiCookie; this.valueIdx = valueIdx; return STATE_HDR_NAME; } } while (buffer.hasRemaining()); buffer.clear(); } } // fall thru } case STATE_HDR_D: { if (!buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { this.string = string; this.headerValues = headerValues; this.charIndex = charIndex; this.fiCookie = fiCookie; this.valueIdx = valueIdx; return STATE_HDR_D; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) ':'); // fall thru } case STATE_HDR_DS: { if (!buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { this.string = string; this.headerValues = headerValues; this.charIndex = charIndex; this.fiCookie = fiCookie; this.valueIdx = valueIdx; return STATE_HDR_DS; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) ' '); string = headerValues.get(valueIdx++); charIndex = 0; // fall thru } case STATE_HDR_VAL: { length = string.length(); while (charIndex < length) { if (buffer.hasRemaining()) { buffer.put((byte) string.charAt(charIndex++)); } else { buffer.flip(); do { res = next.write(buffer); if (res == 0) { this.string = string; this.headerValues = headerValues; this.charIndex = charIndex; this.fiCookie = fiCookie; this.valueIdx = valueIdx; return STATE_HDR_VAL; } } while (buffer.hasRemaining()); buffer.clear(); } } charIndex = 0; if (valueIdx == headerValues.size()) { if (!buffer.hasRemaining()) { if (flushHeaderBuffer(buffer, string, headerValues, charIndex, fiCookie, valueIdx)) return STATE_HDR_EOL_CR; } buffer.put((byte) 13); // CR if (!buffer.hasRemaining()) { if (flushHeaderBuffer(buffer, string, headerValues, charIndex, fiCookie, valueIdx)) return STATE_HDR_EOL_LF; } buffer.put((byte) 10); // LF if ((fiCookie = headers.fiNextNonEmpty(fiCookie)) != -1L) { headerValues = headers.fiCurrent(fiCookie); valueIdx = 0; state = STATE_HDR_NAME; break; } else { if (!buffer.hasRemaining()) { if (flushHeaderBuffer(buffer, string, headerValues, charIndex, fiCookie, valueIdx)) return STATE_HDR_FINAL_CR; } buffer.put((byte) 13); // CR if (!buffer.hasRemaining()) { if (flushHeaderBuffer(buffer, string, headerValues, charIndex, fiCookie, valueIdx)) return STATE_HDR_FINAL_LF; } buffer.put((byte) 10); // LF this.fiCookie = -1; this.valueIdx = 0; this.string = null; buffer.flip(); //for performance reasons we use a gather write if there is user data if (userData == null) { do { res = next.write(buffer); if (res == 0) { return STATE_BUF_FLUSH; } } while (buffer.hasRemaining()); } else if(userData instanceof ByteBuffer) { ByteBuffer[] b = {buffer, (ByteBuffer) userData}; do { long r = next.write(b, 0, b.length); if (r == 0 && buffer.hasRemaining()) { return STATE_BUF_FLUSH; } } while (buffer.hasRemaining()); } else { ByteBuffer[] b = new ByteBuffer[1 + len]; b[0] = buffer; System.arraycopy(userData, pos, b, 1, len); do { long r = next.write(b, 0, b.length); if (r == 0 && buffer.hasRemaining()) { return STATE_BUF_FLUSH; } } while (buffer.hasRemaining()); } bufferDone(); return STATE_BODY; } // not reached } // fall thru } // Clean-up states case STATE_HDR_EOL_CR: { if (!buffer.hasRemaining()) { if (flushHeaderBuffer(buffer, string, headerValues, charIndex, fiCookie, valueIdx)) return STATE_HDR_EOL_CR; } buffer.put((byte) 13); // CR } case STATE_HDR_EOL_LF: { if (!buffer.hasRemaining()) { if (flushHeaderBuffer(buffer, string, headerValues, charIndex, fiCookie, valueIdx)) return STATE_HDR_EOL_LF; } buffer.put((byte) 10); // LF if (valueIdx < headerValues.size()) { state = STATE_HDR_NAME; break; } else if ((fiCookie = headers.fiNextNonEmpty(fiCookie)) != -1L) { headerValues = headers.fiCurrent(fiCookie); valueIdx = 0; state = STATE_HDR_NAME; break; } // fall thru } case STATE_HDR_FINAL_CR: { if (!buffer.hasRemaining()) { if (flushHeaderBuffer(buffer, string, headerValues, charIndex, fiCookie, valueIdx)) return STATE_HDR_FINAL_CR; } buffer.put((byte) 13); // CR // fall thru } case STATE_HDR_FINAL_LF: { if (!buffer.hasRemaining()) { if (flushHeaderBuffer(buffer, string, headerValues, charIndex, fiCookie, valueIdx)) return STATE_HDR_FINAL_LF; } buffer.put((byte) 10); // LF this.fiCookie = -1L; this.valueIdx = 0; this.string = null; buffer.flip(); //for performance reasons we use a gather write if there is user data if (userData == null) { do { res = next.write(buffer); if (res == 0) { return STATE_BUF_FLUSH; } } while (buffer.hasRemaining()); } else if(userData instanceof ByteBuffer) { ByteBuffer[] b = {buffer, (ByteBuffer) userData}; do { long r = next.write(b, 0, b.length); if (r == 0 && buffer.hasRemaining()) { return STATE_BUF_FLUSH; } } while (buffer.hasRemaining()); } else { ByteBuffer[] b = new ByteBuffer[1 + len]; b[0] = buffer; System.arraycopy(userData, pos, b, 1, len); do { long r = next.write(b, 0, b.length); if (r == 0 && buffer.hasRemaining()) { return STATE_BUF_FLUSH; } } while (buffer.hasRemaining()); } // fall thru } case STATE_BUF_FLUSH: { // buffer was successfully flushed above bufferDone(); return STATE_BODY; } default: { throw new IllegalStateException(); } } } } private boolean flushHeaderBuffer(ByteBuffer buffer, String string, HeaderValues headerValues, int charIndex, long fiCookie, int valueIdx) throws IOException { int res; buffer.flip(); do { res = next.write(buffer); if (res == 0) { this.string = string; this.headerValues = headerValues; this.charIndex = charIndex; this.fiCookie = fiCookie; this.valueIdx = valueIdx; return true; } } while (buffer.hasRemaining()); buffer.clear(); return false; } public int write(final ByteBuffer src) throws IOException { try { int oldState = this.state; int state = oldState & MASK_STATE; int alreadyWritten = 0; int originalRemaining = -1; try { if (state != 0) { originalRemaining = src.remaining(); state = processWrite(state, src, -1, -1); if (state != 0) { return 0; } alreadyWritten = originalRemaining - src.remaining(); if (allAreSet(oldState, FLAG_SHUTDOWN)) { next.terminateWrites(); throw new ClosedChannelException(); } } if (alreadyWritten != originalRemaining) { return next.write(src) + alreadyWritten; } return alreadyWritten; } finally { this.state = oldState & ~MASK_STATE | state; } } catch(IOException|RuntimeException|Error e) { IoUtils.safeClose(connection); throw e; } } public long write(final ByteBuffer[] srcs) throws IOException { return write(srcs, 0, srcs.length); } public long write(final ByteBuffer[] srcs, final int offset, final int length) throws IOException { if (length == 0) { return 0L; } int oldVal = state; int state = oldVal & MASK_STATE; try { if (state != 0) { long rem = Buffers.remaining(srcs, offset, length); state = processWrite(state, srcs, offset, length); long ret = rem - Buffers.remaining(srcs, offset, length); if (state != 0) { return ret; } if (allAreSet(oldVal, FLAG_SHUTDOWN)) { next.terminateWrites(); throw new ClosedChannelException(); } //we don't attempt to write again return ret; } return length == 1 ? next.write(srcs[offset]) : next.write(srcs, offset, length); } catch (IOException | RuntimeException | Error e) { IoUtils.safeClose(connection); throw e; } finally { this.state = oldVal & ~MASK_STATE | state; } } public long transferFrom(final FileChannel src, final long position, final long count) throws IOException { try { if (pooledFileTransferBuffer != null) { try { return write(pooledFileTransferBuffer.getBuffer()); } catch (IOException | RuntimeException | Error e) { if (pooledFileTransferBuffer != null) { pooledFileTransferBuffer.close(); pooledFileTransferBuffer = null; } throw e; } finally { if (pooledFileTransferBuffer != null) { if (!pooledFileTransferBuffer.getBuffer().hasRemaining()) { pooledFileTransferBuffer.close(); pooledFileTransferBuffer = null; } } } } else if (state != 0) { final PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().allocate(); ByteBuffer buffer = pooled.getBuffer(); try { int res = src.read(buffer); buffer.flip(); if (res <= 0) { return res; } return write(buffer); } finally { if (buffer.hasRemaining()) { pooledFileTransferBuffer = pooled; } else { pooled.close(); } } } else { return next.transferFrom(src, position, count); } } catch (IOException | RuntimeException | Error e) { IoUtils.safeClose(connection); throw e; } } public long transferFrom(final StreamSourceChannel source, final long count, final ByteBuffer throughBuffer) throws IOException { try { if (state != 0) { return IoUtils.transfer(source, count, throughBuffer, new ConduitWritableByteChannel(this)); } else { return next.transferFrom(source, count, throughBuffer); } } catch (IOException| RuntimeException | Error e) { IoUtils.safeClose(connection); throw e; } } @Override public int writeFinal(ByteBuffer src) throws IOException { try { return Conduits.writeFinalBasic(this, src); } catch (IOException | RuntimeException | Error e) { IoUtils.safeClose(connection); throw e; } } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { try { return Conduits.writeFinalBasic(this, srcs, offset, length); } catch (IOException | RuntimeException | Error e) { IoUtils.safeClose(connection); throw e; } } public boolean flush() throws IOException { int oldVal = state; int state = oldVal & MASK_STATE; try { if (state != 0) { state = processWrite(state, null, -1, -1); if (state != 0) { return false; } if (allAreSet(oldVal, FLAG_SHUTDOWN)) { next.terminateWrites(); // fall out to the flush } } return next.flush(); } catch (IOException | RuntimeException | Error e) { IoUtils.safeClose(connection); throw e; } finally { this.state = oldVal & ~MASK_STATE | state; } } public void terminateWrites() throws IOException { try { int oldVal = this.state; if (allAreClear(oldVal, MASK_STATE)) { next.terminateWrites(); return; } this.state = oldVal | FLAG_SHUTDOWN; } catch (IOException | RuntimeException | Error e) { IoUtils.safeClose(connection); throw e; } } public void truncateWrites() throws IOException { try { next.truncateWrites(); } catch (IOException | RuntimeException | Error e) { IoUtils.safeClose(connection); throw e; } finally { if (pooledBuffer != null) { bufferDone(); } if(pooledFileTransferBuffer != null) { pooledFileTransferBuffer.close(); pooledFileTransferBuffer = null; } } } public XnioWorker getWorker() { return next.getWorker(); } void freeBuffers() { done = true; if(pooledBuffer != null) { bufferDone(); } if(pooledFileTransferBuffer != null) { pooledFileTransferBuffer.close(); pooledFileTransferBuffer = null; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http/HttpServerConnection.java000066400000000000000000000274461420065311100334700ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http; import io.undertow.UndertowMessages; import io.undertow.conduits.ReadDataStreamSourceConduit; import io.undertow.server.AbstractServerConnection; import io.undertow.server.ConduitWrapper; import io.undertow.server.ConnectionSSLSessionInfo; import io.undertow.server.ConnectorStatisticsImpl; import io.undertow.server.Connectors; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpUpgradeListener; import io.undertow.server.SSLSessionInfo; import io.undertow.server.ServerConnection; import io.undertow.util.ConduitFactory; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.ImmediatePooledByteBuffer; import io.undertow.util.Methods; import org.xnio.IoUtils; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import org.xnio.StreamConnection; import org.xnio.channels.SslChannel; import org.xnio.conduits.StreamSinkConduit; import javax.net.ssl.SSLSession; import java.nio.ByteBuffer; /** * A server-side HTTP connection. *

* Note that the lifecycle of the server connection is tied to the underlying TCP connection. Even if the channel * is upgraded the connection is not considered closed until the upgraded channel is closed. * * @author David M. Lloyd */ public final class HttpServerConnection extends AbstractServerConnection { private SSLSessionInfo sslSessionInfo; private HttpReadListener readListener; private PipeliningBufferingStreamSinkConduit pipelineBuffer; private HttpResponseConduit responseConduit; private ServerFixedLengthStreamSinkConduit fixedLengthStreamSinkConduit; private ReadDataStreamSourceConduit readDataStreamSourceConduit; private HttpUpgradeListener upgradeListener; private boolean connectHandled; public HttpServerConnection(StreamConnection channel, final ByteBufferPool bufferPool, final HttpHandler rootHandler, final OptionMap undertowOptions, final int bufferSize, final ConnectorStatisticsImpl connectorStatistics) { super(channel, bufferPool, rootHandler, undertowOptions, bufferSize); if (channel instanceof SslChannel) { sslSessionInfo = new ConnectionSSLSessionInfo(((SslChannel) channel), this); } this.responseConduit = new HttpResponseConduit(channel.getSinkChannel().getConduit(), bufferPool, this); fixedLengthStreamSinkConduit = new ServerFixedLengthStreamSinkConduit(responseConduit, false, false); readDataStreamSourceConduit = new ReadDataStreamSourceConduit(channel.getSourceChannel().getConduit(), this); //todo: do this without an allocation addCloseListener(new CloseListener() { @Override public void closed(ServerConnection connection) { if(connectorStatistics != null) { connectorStatistics.decrementConnectionCount(); } responseConduit.freeBuffers(); } }); } @Override public HttpServerExchange sendOutOfBandResponse(HttpServerExchange exchange) { if (exchange == null || !HttpContinue.requiresContinueResponse(exchange)) { throw UndertowMessages.MESSAGES.outOfBandResponseOnlyAllowedFor100Continue(); } final ConduitState state = resetChannel(); HttpServerExchange newExchange = new HttpServerExchange(this); for (HttpString header : exchange.getRequestHeaders().getHeaderNames()) { newExchange.getRequestHeaders().putAll(header, exchange.getRequestHeaders().get(header)); } newExchange.setProtocol(exchange.getProtocol()); newExchange.setRequestMethod(exchange.getRequestMethod()); exchange.setRequestURI(exchange.getRequestURI(), exchange.isHostIncludedInRequestURI()); exchange.setRequestPath(exchange.getRequestPath()); exchange.setRelativePath(exchange.getRelativePath()); newExchange.getRequestHeaders().put(Headers.CONNECTION, Headers.KEEP_ALIVE.toString()); newExchange.getRequestHeaders().put(Headers.CONTENT_LENGTH, 0); newExchange.setPersistent(true); Connectors.terminateRequest(newExchange); newExchange.addResponseWrapper(new ConduitWrapper() { @Override public StreamSinkConduit wrap(ConduitFactory factory, HttpServerExchange exchange) { HttpResponseConduit httpResponseConduit = new HttpResponseConduit(getSinkChannel().getConduit(), getByteBufferPool(), HttpServerConnection.this, exchange); exchange.addExchangeCompleteListener(new ExchangeCompletionListener() { @Override public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { httpResponseConduit.freeContinueResponse(); nextListener.proceed(); } }); ServerFixedLengthStreamSinkConduit fixed = new ServerFixedLengthStreamSinkConduit(httpResponseConduit, false, false); fixed.reset(0, exchange); return fixed; } }); //we restore the read channel immediately, as this out of band response has no read side channel.getSourceChannel().setConduit(source(state)); newExchange.addExchangeCompleteListener(new ExchangeCompletionListener() { @Override public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { restoreChannel(state); } }); return newExchange; } @Override public boolean isContinueResponseSupported() { return true; } @Override public void terminateRequestChannel(HttpServerExchange exchange) { if (!exchange.isPersistent()) { IoUtils.safeClose(getChannel().getSourceChannel()); } } /** * Pushes back the given data. This should only be used by transfer coding handlers that have read past * the end of the request when handling pipelined requests * * @param unget The buffer to push back */ public void ungetRequestBytes(final PooledByteBuffer unget) { if (getExtraBytes() == null) { setExtraBytes(unget); } else { PooledByteBuffer eb = getExtraBytes(); ByteBuffer buf = eb.getBuffer(); final ByteBuffer ugBuffer = unget.getBuffer(); if (ugBuffer.limit() - ugBuffer.remaining() > buf.remaining()) { //stuff the existing data after the data we are ungetting ugBuffer.compact(); ugBuffer.put(buf); ugBuffer.flip(); eb.close(); setExtraBytes(unget); } else { //TODO: this is horrible, but should not happen often final byte[] data = new byte[ugBuffer.remaining() + buf.remaining()]; int first = ugBuffer.remaining(); ugBuffer.get(data, 0, ugBuffer.remaining()); buf.get(data, first, buf.remaining()); eb.close(); unget.close(); final ByteBuffer newBuffer = ByteBuffer.wrap(data); setExtraBytes(new ImmediatePooledByteBuffer(newBuffer)); } } } @Override public SSLSessionInfo getSslSessionInfo() { return sslSessionInfo; } @Override public void setSslSessionInfo(SSLSessionInfo sessionInfo) { this.sslSessionInfo = sessionInfo; } public SSLSession getSslSession() { if (channel instanceof SslChannel) { return ((SslChannel) channel).getSslSession(); } return null; } @Override protected StreamConnection upgradeChannel() { clearChannel(); if (extraBytes != null) { channel.getSourceChannel().setConduit(new ReadDataStreamSourceConduit(channel.getSourceChannel().getConduit(), this)); } return channel; } @Override protected StreamSinkConduit getSinkConduit(HttpServerExchange exchange, StreamSinkConduit conduit) { if(exchange.getRequestMethod().equals(Methods.CONNECT) && !connectHandled) { //make sure that any unhandled CONNECT requests result in a connection close exchange.setPersistent(false); exchange.getResponseHeaders().put(Headers.CONNECTION, "close"); } return HttpTransferEncoding.createSinkConduit(exchange); } @Override protected boolean isUpgradeSupported() { return true; } @Override protected boolean isConnectSupported() { return true; } void setReadListener(HttpReadListener readListener) { this.readListener = readListener; } @Override protected void exchangeComplete(HttpServerExchange exchange) { if(fixedLengthStreamSinkConduit != null) { fixedLengthStreamSinkConduit.clearExchange(); } if (pipelineBuffer == null) { readListener.exchangeComplete(exchange); } else { pipelineBuffer.exchangeComplete(exchange); } } HttpReadListener getReadListener() { return readListener; } ReadDataStreamSourceConduit getReadDataStreamSourceConduit() { return readDataStreamSourceConduit; } public PipeliningBufferingStreamSinkConduit getPipelineBuffer() { return pipelineBuffer; } public HttpResponseConduit getResponseConduit() { return responseConduit; } ServerFixedLengthStreamSinkConduit getFixedLengthStreamSinkConduit() { return fixedLengthStreamSinkConduit; } protected HttpUpgradeListener getUpgradeListener() { return upgradeListener; } @Override protected void setUpgradeListener(HttpUpgradeListener upgradeListener) { this.upgradeListener = upgradeListener; } @Override protected void setConnectListener(HttpUpgradeListener connectListener) { this.upgradeListener = connectListener; connectHandled = true; } void setCurrentExchange(HttpServerExchange exchange) { this.current = exchange; } public void setPipelineBuffer(PipeliningBufferingStreamSinkConduit pipelineBuffer) { this.pipelineBuffer = pipelineBuffer; this.responseConduit = new HttpResponseConduit(pipelineBuffer, bufferPool, this); this.fixedLengthStreamSinkConduit = new ServerFixedLengthStreamSinkConduit(responseConduit, false, false); } @Override public String getTransportProtocol() { return "http/1.1"; } @Override public boolean isRequestTrailerFieldsSupported() { if(current == null) { return false; } String te = current.getRequestHeaders().getFirst(Headers.TRANSFER_ENCODING); if(te == null) { return false; } return te.equalsIgnoreCase(Headers.CHUNKED.toString()); } boolean isConnectHandled() { return connectHandled; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http/HttpTransferEncoding.java000066400000000000000000000425011420065311100334220ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http; import io.undertow.UndertowLogger; import io.undertow.UndertowOptions; import io.undertow.conduits.ChunkedStreamSinkConduit; import io.undertow.conduits.ChunkedStreamSourceConduit; import io.undertow.conduits.ConduitListener; import io.undertow.conduits.FinishableStreamSinkConduit; import io.undertow.conduits.FixedLengthStreamSourceConduit; import io.undertow.conduits.HeadStreamSinkConduit; import io.undertow.conduits.PreChunkedStreamSinkConduit; import io.undertow.server.Connectors; import io.undertow.server.HttpServerExchange; import io.undertow.util.DateUtils; import io.undertow.util.HeaderMap; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.Methods; import io.undertow.util.StatusCodes; import org.jboss.logging.Logger; import org.xnio.conduits.ConduitStreamSourceChannel; import org.xnio.conduits.StreamSinkConduit; import org.xnio.conduits.StreamSourceConduit; /** * Class that is responsible for HTTP transfer encoding, this could be part of the {@link HttpReadListener}, * but is separated out for clarity. *

* For more info see http://tools.ietf.org/html/rfc2616#section-4.4 * * @author Stuart Douglas * @author David M. Lloyd */ class HttpTransferEncoding { private static final Logger log = Logger.getLogger("io.undertow.server.handler.transfer-encoding"); /** * Construct a new instance. */ private HttpTransferEncoding() { } public static void setupRequest(final HttpServerExchange exchange) { final HeaderMap requestHeaders = exchange.getRequestHeaders(); final String connectionHeader = requestHeaders.getFirst(Headers.CONNECTION); final String transferEncodingHeader = requestHeaders.getLast(Headers.TRANSFER_ENCODING); final String contentLengthHeader = requestHeaders.getFirst(Headers.CONTENT_LENGTH); final HttpServerConnection connection = (HttpServerConnection) exchange.getConnection(); //if we are already using the pipelineing buffer add it to the exchange PipeliningBufferingStreamSinkConduit pipeliningBuffer = connection.getPipelineBuffer(); if (pipeliningBuffer != null) { pipeliningBuffer.setupPipelineBuffer(exchange); } ConduitStreamSourceChannel sourceChannel = connection.getChannel().getSourceChannel(); sourceChannel.setConduit(connection.getReadDataStreamSourceConduit()); boolean persistentConnection = persistentConnection(exchange, connectionHeader); if (transferEncodingHeader == null && contentLengthHeader == null) { if (persistentConnection && connection.getExtraBytes() != null && pipeliningBuffer == null && connection.getUndertowOptions().get(UndertowOptions.BUFFER_PIPELINED_DATA, false)) { pipeliningBuffer = new PipeliningBufferingStreamSinkConduit(connection.getOriginalSinkConduit(), connection.getByteBufferPool()); connection.setPipelineBuffer(pipeliningBuffer); pipeliningBuffer.setupPipelineBuffer(exchange); } // no content - immediately start the next request, returning an empty stream for this one Connectors.terminateRequest(exchange); } else { persistentConnection = handleRequestEncoding(exchange, transferEncodingHeader, contentLengthHeader, connection, pipeliningBuffer, persistentConnection); } exchange.setPersistent(persistentConnection); if (!exchange.isRequestComplete() || connection.getExtraBytes() != null) { //if there is more data we suspend reads sourceChannel.setReadListener(null); sourceChannel.suspendReads(); } } private static boolean handleRequestEncoding(final HttpServerExchange exchange, String transferEncodingHeader, String contentLengthHeader, HttpServerConnection connection, PipeliningBufferingStreamSinkConduit pipeliningBuffer, boolean persistentConnection) { HttpString transferEncoding = Headers.IDENTITY; if (transferEncodingHeader != null) { transferEncoding = new HttpString(transferEncodingHeader); } if (transferEncodingHeader != null && !transferEncoding.equals(Headers.IDENTITY)) { ConduitStreamSourceChannel sourceChannel = ((HttpServerConnection) exchange.getConnection()).getChannel().getSourceChannel(); sourceChannel.setConduit(new ChunkedStreamSourceConduit(sourceChannel.getConduit(), exchange, chunkedDrainListener(exchange))); } else if (contentLengthHeader != null) { final long contentLength; contentLength = parsePositiveLong(contentLengthHeader); if (contentLength == 0L) { log.trace("No content, starting next request"); // no content - immediately start the next request, returning an empty stream for this one Connectors.terminateRequest(exchange); } else { // fixed-length content - add a wrapper for a fixed-length stream ConduitStreamSourceChannel sourceChannel = ((HttpServerConnection) exchange.getConnection()).getChannel().getSourceChannel(); sourceChannel.setConduit(fixedLengthStreamSourceConduitWrapper(contentLength, sourceChannel.getConduit(), exchange)); } } else if (transferEncodingHeader != null) { //identity transfer encoding log.trace("Connection not persistent (no content length and identity transfer encoding)"); // make it not persistent persistentConnection = false; } else if (persistentConnection) { //we have no content and a persistent request. This may mean we need to use the pipelining buffer to improve //performance if (connection.getExtraBytes() != null && pipeliningBuffer == null && connection.getUndertowOptions().get(UndertowOptions.BUFFER_PIPELINED_DATA, false)) { pipeliningBuffer = new PipeliningBufferingStreamSinkConduit(connection.getOriginalSinkConduit(), connection.getByteBufferPool()); connection.setPipelineBuffer(pipeliningBuffer); pipeliningBuffer.setupPipelineBuffer(exchange); } // no content - immediately start the next request, returning an empty stream for this one Connectors.terminateRequest(exchange); } else { //assume there is no content //we still know there is no content Connectors.terminateRequest(exchange); } return persistentConnection; } private static boolean persistentConnection(HttpServerExchange exchange, String connectionHeader) { if (exchange.isHttp11()) { return !(connectionHeader != null && Headers.CLOSE.equalToString(connectionHeader)); } else if (exchange.isHttp10()) { if (connectionHeader != null) { if (Headers.KEEP_ALIVE.equals(new HttpString(connectionHeader))) { return true; } } } log.trace("Connection not persistent"); return false; } private static StreamSourceConduit fixedLengthStreamSourceConduitWrapper(final long contentLength, final StreamSourceConduit conduit, final HttpServerExchange exchange) { return new FixedLengthStreamSourceConduit(conduit, contentLength, fixedLengthDrainListener(exchange), exchange); } private static ConduitListener fixedLengthDrainListener(final HttpServerExchange exchange) { return new ConduitListener() { public void handleEvent(final FixedLengthStreamSourceConduit fixedLengthConduit) { long remaining = fixedLengthConduit.getRemaining(); if (remaining > 0L) { UndertowLogger.REQUEST_LOGGER.requestWasNotFullyConsumed(); exchange.setPersistent(false); } Connectors.terminateRequest(exchange); } }; } private static ConduitListener chunkedDrainListener(final HttpServerExchange exchange) { return new ConduitListener() { public void handleEvent(final ChunkedStreamSourceConduit chunkedStreamSourceConduit) { if (!chunkedStreamSourceConduit.isFinished()) { UndertowLogger.REQUEST_LOGGER.requestWasNotFullyConsumed(); exchange.setPersistent(false); } Connectors.terminateRequest(exchange); } }; } private static ConduitListener terminateResponseListener(final HttpServerExchange exchange) { return new ConduitListener() { public void handleEvent(final StreamSinkConduit channel) { Connectors.terminateResponse(exchange); } }; } static StreamSinkConduit createSinkConduit(final HttpServerExchange exchange) { DateUtils.addDateHeaderIfRequired(exchange); boolean headRequest = exchange.getRequestMethod().equals(Methods.HEAD); HttpServerConnection serverConnection = (HttpServerConnection) exchange.getConnection(); HttpResponseConduit responseConduit = serverConnection.getResponseConduit(); responseConduit.reset(exchange); StreamSinkConduit channel = responseConduit; if (headRequest) { //if this is a head request we add a head channel underneath the content encoding channel //this will just discard the data //we still go through with the rest of the logic, to make sure all headers are set correctly channel = new HeadStreamSinkConduit(channel, terminateResponseListener(exchange)); } else if(!Connectors.isEntityBodyAllowed(exchange)) { //we are not allowed to send an entity body for some requests exchange.getResponseHeaders().remove(Headers.CONTENT_LENGTH); exchange.getResponseHeaders().remove(Headers.TRANSFER_ENCODING); channel = new HeadStreamSinkConduit(channel, terminateResponseListener(exchange)); return channel; } final HeaderMap responseHeaders = exchange.getResponseHeaders(); // test to see if we're still persistent String connection = responseHeaders.getFirst(Headers.CONNECTION); if(exchange.getStatusCode() == StatusCodes.EXPECTATION_FAILED) { //417 responses are never persistent, as we have no idea if there is a response body //still coming on the wire. exchange.setPersistent(false); } if (!exchange.isPersistent()) { responseHeaders.put(Headers.CONNECTION, Headers.CLOSE.toString()); } else if (exchange.isPersistent() && connection != null) { if (HttpString.tryFromString(connection).equals(Headers.CLOSE)) { exchange.setPersistent(false); } } else if (exchange.getConnection().getUndertowOptions().get(UndertowOptions.ALWAYS_SET_KEEP_ALIVE, true)) { responseHeaders.put(Headers.CONNECTION, Headers.KEEP_ALIVE.toString()); } //according to the HTTP RFC we should ignore content length if a transfer coding is specified final String transferEncodingHeader = responseHeaders.getLast(Headers.TRANSFER_ENCODING); if(transferEncodingHeader == null) { final String contentLengthHeader = responseHeaders.getFirst(Headers.CONTENT_LENGTH); if (contentLengthHeader != null) { StreamSinkConduit res = handleFixedLength(exchange, headRequest, channel, responseHeaders, contentLengthHeader, serverConnection); if (res != null) { return res; } } } else { responseHeaders.remove(Headers.CONTENT_LENGTH); //if there is a transfer-encoding header we remove content length if present } return handleResponseConduit(exchange, headRequest, channel, responseHeaders, terminateResponseListener(exchange), transferEncodingHeader); } private static StreamSinkConduit handleFixedLength(HttpServerExchange exchange, boolean headRequest, StreamSinkConduit channel, HeaderMap responseHeaders, String contentLengthHeader, HttpServerConnection connection) { try { final long contentLength = parsePositiveLong(contentLengthHeader); if (headRequest) { return channel; } // fixed-length response ServerFixedLengthStreamSinkConduit fixed = connection.getFixedLengthStreamSinkConduit(); fixed.reset(contentLength, exchange); return fixed; } catch (NumberFormatException e) { //we just fix it for them responseHeaders.remove(Headers.CONTENT_LENGTH); } return null; } private static StreamSinkConduit handleResponseConduit(HttpServerExchange exchange, boolean headRequest, StreamSinkConduit channel, HeaderMap responseHeaders, ConduitListener finishListener, String transferEncodingHeader) { if (transferEncodingHeader == null) { if (exchange.isHttp11()) { if (exchange.isPersistent()) { responseHeaders.put(Headers.TRANSFER_ENCODING, Headers.CHUNKED.toString()); if (headRequest) { return channel; } return new ChunkedStreamSinkConduit(channel, exchange.getConnection().getByteBufferPool(), true, !exchange.isPersistent(), responseHeaders, finishListener, exchange); } else { if (headRequest) { return channel; } return new FinishableStreamSinkConduit(channel, finishListener); } } else { exchange.setPersistent(false); responseHeaders.put(Headers.CONNECTION, Headers.CLOSE.toString()); if (headRequest) { return channel; } return new FinishableStreamSinkConduit(channel, finishListener); } } else { //moved outside because this is rarely used //and makes the method small enough to be inlined return handleExplicitTransferEncoding(exchange, channel, finishListener, responseHeaders, transferEncodingHeader, headRequest); } } private static StreamSinkConduit handleExplicitTransferEncoding(HttpServerExchange exchange, StreamSinkConduit channel, ConduitListener finishListener, HeaderMap responseHeaders, String transferEncodingHeader, boolean headRequest) { HttpString transferEncoding = new HttpString(transferEncodingHeader); if (transferEncoding.equals(Headers.CHUNKED)) { if (headRequest) { return channel; } Boolean preChunked = exchange.getAttachment(HttpAttachments.PRE_CHUNKED_RESPONSE); if(preChunked != null && preChunked) { return new PreChunkedStreamSinkConduit(channel, finishListener, exchange); } else { return new ChunkedStreamSinkConduit(channel, exchange.getConnection().getByteBufferPool(), true, !exchange.isPersistent(), responseHeaders, finishListener, exchange); } } else { if (headRequest) { return channel; } log.trace("Cancelling persistence because response is identity with no content length"); // make it not persistent - very unfortunate for the next request handler really... exchange.setPersistent(false); responseHeaders.put(Headers.CONNECTION, Headers.CLOSE.toString()); return new FinishableStreamSinkConduit(channel, terminateResponseListener(exchange)); } } /** * fast long parsing algorithm * * @param str The string * @return The long */ public static long parsePositiveLong(String str) { long value = 0; final int length = str.length(); if (length == 0) { throw new NumberFormatException(str); } long multiplier = 1; for (int i = length - 1; i >= 0; --i) { char c = str.charAt(i); if (c < '0' || c > '9') { throw new NumberFormatException(str); } long digit = c - '0'; value += digit * multiplier; multiplier *= 10; } return value; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http/ParseState.java000066400000000000000000000103331420065311100314000ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http; import io.undertow.util.HttpString; /** * The current state of the tokenizer state machine. This class is mutable and not thread safe. *

* As the machine changes state this class is updated rather than allocating a new one each time. * * fields are not private to allow for efficient putfield / getfield access * * Fields can mean different things depending on the current state. This means that names may * not always reflect complete functionality. * * This class is re-used for requests on the same connection. * * @author Stuart Douglas */ class ParseState { //parsing states public static final int VERB = 0; public static final int PATH = 1; public static final int PATH_PARAMETERS = 2; public static final int QUERY_PARAMETERS = 3; public static final int VERSION = 4; public static final int AFTER_VERSION = 5; public static final int HEADER = 6; public static final int HEADER_VALUE = 7; public static final int PARSE_COMPLETE = 8; /** * The actual state of request parsing */ int state; /** * The current state in the tokenizer state machine. */ int parseState; /** * If this state is a prefix or terminal match state this is set to the string * that is a candidate to be matched */ HttpString current; /** * The bytes version of {@link #current} */ byte[] currentBytes; /** * If this state is a prefix match state then this holds the current position in the string. * */ int pos; boolean urlDecodeRequired = false; /** * If this is in {@link io.undertow.annotationprocessor.AbstractParserGenerator#NO_STATE} then this holds the current token that has been read so far. */ final StringBuilder stringBuilder = new StringBuilder(); /** * We need to keep track of the canonical path */ final StringBuilder canonicalPath = new StringBuilder(); /** * This has different meanings depending on the current state. * * In state {@link #HEADER} it is a the first character of the header, that was read by * {@link #HEADER_VALUE} to see if this was a continuation. * * In state {@link #HEADER_VALUE} if represents the last character that was seen. * */ byte leftOver; /** * This is used to store the next header value when parsing header key / value pairs, */ HttpString nextHeader; String nextQueryParam; int mapCount; final StringBuilder decodeBuffer = new StringBuilder(); /** * In general browsers will often send the same header with every request. This cache allows us to re-use the resulting * strings. */ final CacheMap headerValuesCache; ParseState(int cacheSize) { this.parseState = 0; this.pos = 0; if(cacheSize <= 0) { headerValuesCache = null; } else { headerValuesCache = new CacheMap<>(cacheSize); } } public boolean isComplete() { return state == PARSE_COMPLETE; } public final void parseComplete(){ state = PARSE_COMPLETE; } public void reset() { this.state = 0; this.parseState = 0; this.current = null; this.currentBytes = null; this.pos = 0; this.leftOver = 0; this.urlDecodeRequired = false; this.stringBuilder.setLength(0); this.nextHeader = null; this.nextQueryParam = null; this.mapCount = 0; } } PipeliningBufferingStreamSinkConduit.java000066400000000000000000000273731420065311100365370ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Channel; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.util.concurrent.TimeUnit; import io.undertow.UndertowLogger; import io.undertow.server.HttpServerExchange; import org.xnio.Buffers; import org.xnio.ChannelListener; import org.xnio.IoUtils; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import org.xnio.StreamConnection; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.AbstractStreamSinkConduit; import org.xnio.conduits.ConduitWritableByteChannel; import org.xnio.conduits.Conduits; import org.xnio.conduits.StreamSinkConduit; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.anyAreClear; import static org.xnio.Bits.anyAreSet; /** * A buffer that is used when processing pipelined requests, that allows the server to * buffer multiple responses into a single write() call. *

* This can improve performance when pipelining requests. * * @author Stuart Douglas */ public class PipeliningBufferingStreamSinkConduit extends AbstractStreamSinkConduit { /** * If this channel is shutdown */ private static final int SHUTDOWN = 1; private static final int DELEGATE_SHUTDOWN = 1 << 1; private static final int FLUSHING = 1 << 3; private int state; private final ByteBufferPool pool; private PooledByteBuffer buffer; public PipeliningBufferingStreamSinkConduit(StreamSinkConduit next, final ByteBufferPool pool) { super(next); this.pool = pool; } @Override public long transferFrom(FileChannel src, long position, long count) throws IOException { if (anyAreSet(state, SHUTDOWN)) { throw new ClosedChannelException(); } return src.transferTo(position, count, new ConduitWritableByteChannel(this)); } @Override public long transferFrom(StreamSourceChannel source, long count, ByteBuffer throughBuffer) throws IOException { return IoUtils.transfer(source, count, throughBuffer, new ConduitWritableByteChannel(this)); } @Override public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { if (anyAreSet(state, SHUTDOWN)) { throw new ClosedChannelException(); } if (anyAreSet(state, FLUSHING)) { boolean res = flushBuffer(); if (!res) { return 0; } } PooledByteBuffer pooled = this.buffer; if (pooled == null) { this.buffer = pooled = pool.allocate(); } final ByteBuffer buffer = pooled.getBuffer(); long total = Buffers.remaining(srcs, offset, length); if (buffer.remaining() > total) { long put = total; Buffers.copy(buffer, srcs, offset, length); return put; } else { return flushBufferWithUserData(srcs, offset, length); } } @Override public int write(ByteBuffer src) throws IOException { if (anyAreSet(state, SHUTDOWN)) { throw new ClosedChannelException(); } if (anyAreSet(state, FLUSHING)) { boolean res = flushBuffer(); if (!res) { return 0; } } PooledByteBuffer pooled = this.buffer; if (pooled == null) { this.buffer = pooled = pool.allocate(); } final ByteBuffer buffer = pooled.getBuffer(); if (buffer.remaining() > src.remaining()) { int put = src.remaining(); buffer.put(src); return put; } else { return (int) flushBufferWithUserData(new ByteBuffer[]{src}, 0, 1); } } @Override public int writeFinal(ByteBuffer src) throws IOException { return Conduits.writeFinalBasic(this, src); } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { return Conduits.writeFinalBasic(this, srcs, offset, length); } private long flushBufferWithUserData(final ByteBuffer[] byteBuffers, int offset, int length) throws IOException { final ByteBuffer byteBuffer = buffer.getBuffer(); if (byteBuffer.position() == 0) { try { return next.write(byteBuffers, offset, length); } finally { buffer.close(); buffer = null; } } if (!anyAreSet(state, FLUSHING)) { state |= FLUSHING; byteBuffer.flip(); } int originalBufferedRemaining = byteBuffer.remaining(); long toWrite = originalBufferedRemaining; ByteBuffer[] writeBufs = new ByteBuffer[length + 1]; writeBufs[0] = byteBuffer; for (int i = offset; i < offset + length; ++i) { writeBufs[i + 1 - offset] = byteBuffers[i]; toWrite += byteBuffers[i].remaining(); } long res = 0; long written = 0; do { res = next.write(writeBufs, 0, writeBufs.length); written += res; if (res == 0) { if (written > originalBufferedRemaining) { buffer.close(); this.buffer = null; state &= ~FLUSHING; return written - originalBufferedRemaining; } return 0; } } while (written < toWrite); buffer.close(); this.buffer = null; state &= ~FLUSHING; return written - originalBufferedRemaining; } /** * Flushes the cached data. *

* This should be called when a read thread fails to read any more request data, to make sure that any * buffered data is flushed after the last pipelined request. *

* If this returns false the read thread should suspend reads and resume writes * * @return true If the flush succeeded, false otherwise * @throws IOException */ public boolean flushPipelinedData() throws IOException { if (buffer == null || (buffer.getBuffer().position() == 0 && allAreClear(state, FLUSHING))) { return next.flush(); } return flushBuffer(); } /** * Gets the channel wrapper that implements the buffering */ public void setupPipelineBuffer(final HttpServerExchange exchange) { ((HttpServerConnection) exchange.getConnection()).getChannel().getSinkChannel().setConduit(this); } private boolean flushBuffer() throws IOException { if (buffer == null) { return next.flush(); } final ByteBuffer byteBuffer = buffer.getBuffer(); if (!anyAreSet(state, FLUSHING)) { state |= FLUSHING; byteBuffer.flip(); } while (byteBuffer.hasRemaining()) { if (next.write(byteBuffer) == 0) { return false; } } if (!next.flush()) { return false; } buffer.close(); this.buffer = null; state &= ~FLUSHING; return true; } @Override public void awaitWritable(long time, TimeUnit timeUnit) throws IOException { if (buffer != null) { if (buffer.getBuffer().hasRemaining()) { return; } } next.awaitWritable(time, timeUnit); } @Override public void awaitWritable() throws IOException { if (buffer != null) { if (buffer.getBuffer().hasRemaining()) { return; } next.awaitWritable(); } } @Override public boolean flush() throws IOException { if (anyAreSet(state, SHUTDOWN)) { if (!flushBuffer()) { return false; } if (anyAreSet(state, SHUTDOWN) && anyAreClear(state, DELEGATE_SHUTDOWN)) { state |= DELEGATE_SHUTDOWN; next.terminateWrites(); } return next.flush(); } return true; } @Override public void terminateWrites() throws IOException { state |= SHUTDOWN; if (buffer == null) { state |= DELEGATE_SHUTDOWN; next.terminateWrites(); } } public void truncateWrites() throws IOException { try { next.truncateWrites(); } finally { if (buffer != null) { buffer.close(); } } } public void exchangeComplete(final HttpServerExchange exchange) { //if we ever fail to read then we flush the pipeline buffer //this relies on us always doing an eager read when starting a request, //rather than waiting to be notified of data being available final HttpServerConnection connection = (HttpServerConnection) exchange.getConnection(); if (connection.getExtraBytes() == null || exchange.isUpgrade()) { performFlush(exchange, connection); } else { connection.getReadListener().exchangeComplete(exchange); } } void performFlush(final HttpServerExchange exchange, final HttpServerConnection connection) { try { final HttpServerConnection.ConduitState oldState = connection.resetChannel(); if (!flushPipelinedData()) { final StreamConnection channel = connection.getChannel(); channel.getSinkChannel().setWriteListener(new ChannelListener() { @Override public void handleEvent(Channel c) { try { if (flushPipelinedData()) { channel.getSinkChannel().setWriteListener(null); channel.getSinkChannel().suspendWrites(); connection.restoreChannel(oldState); connection.getReadListener().exchangeComplete(exchange); } } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); IoUtils.safeClose(channel); } catch (Throwable t) { UndertowLogger.REQUEST_IO_LOGGER.handleUnexpectedFailure(t); IoUtils.safeClose(channel); } } }); connection.getChannel().getSinkChannel().resumeWrites(); return; } else { connection.restoreChannel(oldState); connection.getReadListener().exchangeComplete(exchange); } } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); IoUtils.safeClose(connection.getChannel()); } catch (Throwable t) { UndertowLogger.REQUEST_IO_LOGGER.handleUnexpectedFailure(t); IoUtils.safeClose(connection.getChannel()); } } } ServerFixedLengthStreamSinkConduit.java000066400000000000000000000040201420065311100361610ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http; import io.undertow.conduits.AbstractFixedLengthStreamSinkConduit; import io.undertow.server.Connectors; import io.undertow.server.HttpServerExchange; import org.xnio.conduits.StreamSinkConduit; /** * @author Stuart Douglas */ public class ServerFixedLengthStreamSinkConduit extends AbstractFixedLengthStreamSinkConduit { private HttpServerExchange exchange; /** * Construct a new instance. * * @param next the next channel * @param configurable {@code true} if this instance should pass configuration to the next * @param propagateClose {@code true} if this instance should pass close to the next */ public ServerFixedLengthStreamSinkConduit(StreamSinkConduit next, boolean configurable, boolean propagateClose) { super(next, 1, configurable, propagateClose); } void reset(long contentLength, HttpServerExchange exchange) { this.exchange = exchange; super.reset(contentLength, !exchange.isPersistent()); } void clearExchange(){ channelFinished(); } @Override protected void channelFinished() { if(exchange != null) { HttpServerExchange exchange = this.exchange; this.exchange = null; Connectors.terminateResponse(exchange); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http2/000077500000000000000000000000001420065311100265445ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http2/Http2OpenListener.java000066400000000000000000000147401420065311100327460ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http2; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.UndertowOptions; import io.undertow.conduits.BytesReceivedStreamSourceConduit; import io.undertow.conduits.BytesSentStreamSinkConduit; import io.undertow.protocols.http2.Http2Channel; import io.undertow.server.ConnectorStatistics; import io.undertow.server.ConnectorStatisticsImpl; import io.undertow.server.DelegateOpenListener; import io.undertow.server.HttpHandler; import io.undertow.server.XnioByteBufferPool; import org.xnio.ChannelListener; import org.xnio.IoUtils; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import org.xnio.Pool; import org.xnio.StreamConnection; import java.nio.ByteBuffer; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * Open listener for HTTP2 server * * @author Stuart Douglas */ public final class Http2OpenListener implements ChannelListener, DelegateOpenListener { private final Set connections = Collections.newSetFromMap(new ConcurrentHashMap<>()); public static final String HTTP2 = "h2"; @Deprecated public static final String HTTP2_14 = "h2-14"; private final ByteBufferPool bufferPool; private final int bufferSize; private final ChannelListener closeTask = new ChannelListener() { @Override public void handleEvent(Http2Channel channel) { connectorStatistics.decrementConnectionCount(); } }; private volatile HttpHandler rootHandler; private volatile OptionMap undertowOptions; private volatile boolean statisticsEnabled; private final ConnectorStatisticsImpl connectorStatistics; private final String protocol; @Deprecated public Http2OpenListener(final Pool pool) { this(pool, OptionMap.EMPTY); } @Deprecated public Http2OpenListener(final Pool pool, final OptionMap undertowOptions) { this(pool, undertowOptions, HTTP2); } @Deprecated public Http2OpenListener(final Pool pool, final OptionMap undertowOptions, String protocol) { this(new XnioByteBufferPool(pool), undertowOptions, protocol); } public Http2OpenListener(final ByteBufferPool pool) { this(pool, OptionMap.EMPTY); } public Http2OpenListener(final ByteBufferPool pool, final OptionMap undertowOptions) { this(pool, undertowOptions, HTTP2); } public Http2OpenListener(final ByteBufferPool pool, final OptionMap undertowOptions, String protocol) { this.undertowOptions = undertowOptions; this.bufferPool = pool; PooledByteBuffer buf = pool.allocate(); this.bufferSize = buf.getBuffer().remaining(); buf.close(); connectorStatistics = new ConnectorStatisticsImpl(); statisticsEnabled = undertowOptions.get(UndertowOptions.ENABLE_STATISTICS, false); this.protocol = protocol; } public void handleEvent(final StreamConnection channel, PooledByteBuffer buffer) { if (UndertowLogger.REQUEST_LOGGER.isTraceEnabled()) { UndertowLogger.REQUEST_LOGGER.tracef("Opened HTTP/2 connection with %s", channel.getPeerAddress()); } //cool, we have a Http2 connection. Http2Channel http2Channel = new Http2Channel(channel, protocol, bufferPool, buffer, false, false, undertowOptions); Integer idleTimeout = undertowOptions.get(UndertowOptions.IDLE_TIMEOUT); if (idleTimeout != null && idleTimeout > 0) { http2Channel.setIdleTimeout(idleTimeout); } if(statisticsEnabled) { channel.getSinkChannel().setConduit(new BytesSentStreamSinkConduit(channel.getSinkChannel().getConduit(), connectorStatistics.sentAccumulator())); channel.getSourceChannel().setConduit(new BytesReceivedStreamSourceConduit(channel.getSourceChannel().getConduit(), connectorStatistics.receivedAccumulator())); connectorStatistics.incrementConnectionCount(); http2Channel.addCloseTask(closeTask); } connections.add(http2Channel); http2Channel.addCloseTask(new ChannelListener() { @Override public void handleEvent(Http2Channel channel) { connections.remove(channel); } }); http2Channel.getReceiveSetter().set(new Http2ReceiveListener(rootHandler, getUndertowOptions(), bufferSize, connectorStatistics)); http2Channel.resumeReceives(); } @Override public ConnectorStatistics getConnectorStatistics() { if(statisticsEnabled) { return connectorStatistics; } return null; } @Override public void closeConnections() { for(Http2Channel i : connections) { IoUtils.safeClose(i); } } @Override public HttpHandler getRootHandler() { return rootHandler; } @Override public void setRootHandler(final HttpHandler rootHandler) { this.rootHandler = rootHandler; } @Override public OptionMap getUndertowOptions() { return undertowOptions; } @Override public void setUndertowOptions(final OptionMap undertowOptions) { if (undertowOptions == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("undertowOptions"); } this.undertowOptions = undertowOptions; statisticsEnabled = undertowOptions.get(UndertowOptions.ENABLE_CONNECTOR_STATISTICS, false); } @Override public ByteBufferPool getBufferPool() { return bufferPool; } @Override public void handleEvent(StreamConnection channel) { handleEvent(channel, null); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http2/Http2ReceiveListener.java000066400000000000000000000363321420065311100334300ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http2; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.function.Supplier; import static java.nio.charset.StandardCharsets.ISO_8859_1; import javax.net.ssl.SSLSession; import io.undertow.UndertowLogger; import io.undertow.UndertowOptions; import io.undertow.conduits.HeadStreamSinkConduit; import io.undertow.protocols.http2.AbstractHttp2StreamSourceChannel; import io.undertow.protocols.http2.Http2Channel; import io.undertow.protocols.http2.Http2DataStreamSinkChannel; import io.undertow.protocols.http2.Http2HeadersStreamSinkChannel; import io.undertow.protocols.http2.Http2StreamSourceChannel; import io.undertow.server.ConduitWrapper; import io.undertow.server.ConnectorStatisticsImpl; import io.undertow.server.Connectors; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.protocol.http.HttpAttachments; import io.undertow.server.protocol.http.HttpContinue; import io.undertow.server.protocol.http.HttpRequestParser; import io.undertow.util.ConduitFactory; import io.undertow.util.HeaderMap; import io.undertow.util.HeaderValues; import io.undertow.util.Headers; import io.undertow.util.ImmediatePooledByteBuffer; import io.undertow.util.Methods; import io.undertow.util.ParameterLimitException; import io.undertow.util.Protocols; import io.undertow.util.StatusCodes; import org.xnio.ChannelListener; import org.xnio.IoUtils; import org.xnio.OptionMap; import org.xnio.channels.Channels; import org.xnio.conduits.StreamSinkConduit; import static io.undertow.protocols.http2.Http2Channel.AUTHORITY; import static io.undertow.protocols.http2.Http2Channel.METHOD; import static io.undertow.protocols.http2.Http2Channel.PATH; import static io.undertow.protocols.http2.Http2Channel.SCHEME; /** * The recieve listener for a Http2 connection. *

* A new instance is created per connection. * * @author Stuart Douglas */ public class Http2ReceiveListener implements ChannelListener { private final HttpHandler rootHandler; private final long maxEntitySize; private final OptionMap undertowOptions; private final String encoding; private final boolean decode; private final StringBuilder decodeBuffer = new StringBuilder(); private final boolean allowEncodingSlash; private final int bufferSize; private final int maxParameters; private final boolean recordRequestStartTime; private final boolean allowUnescapedCharactersInUrl; private final ConnectorStatisticsImpl connectorStatistics; public Http2ReceiveListener(HttpHandler rootHandler, OptionMap undertowOptions, int bufferSize, ConnectorStatisticsImpl connectorStatistics) { this.rootHandler = rootHandler; this.undertowOptions = undertowOptions; this.bufferSize = bufferSize; this.connectorStatistics = connectorStatistics; this.maxEntitySize = undertowOptions.get(UndertowOptions.MAX_ENTITY_SIZE, UndertowOptions.DEFAULT_MAX_ENTITY_SIZE); this.allowEncodingSlash = undertowOptions.get(UndertowOptions.ALLOW_ENCODED_SLASH, false); this.decode = undertowOptions.get(UndertowOptions.DECODE_URL, true); this.maxParameters = undertowOptions.get(UndertowOptions.MAX_PARAMETERS, UndertowOptions.DEFAULT_MAX_PARAMETERS); this.recordRequestStartTime = undertowOptions.get(UndertowOptions.RECORD_REQUEST_START_TIME, false); if (undertowOptions.get(UndertowOptions.DECODE_URL, true)) { this.encoding = undertowOptions.get(UndertowOptions.URL_CHARSET, StandardCharsets.UTF_8.name()); } else { this.encoding = null; } this.allowUnescapedCharactersInUrl = undertowOptions.get(UndertowOptions.ALLOW_UNESCAPED_CHARACTERS_IN_URL, false); } @Override public void handleEvent(Http2Channel channel) { try { final AbstractHttp2StreamSourceChannel frame = channel.receive(); if (frame == null) { return; } if (frame instanceof Http2StreamSourceChannel) { handleRequests(channel, (Http2StreamSourceChannel) frame); } } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); IoUtils.safeClose(channel); } catch (Throwable t) { UndertowLogger.REQUEST_IO_LOGGER.handleUnexpectedFailure(t); IoUtils.safeClose(channel); } } private void handleRequests(Http2Channel channel, Http2StreamSourceChannel frame) { //we have a request final Http2StreamSourceChannel dataChannel = frame; final Http2ServerConnection connection = new Http2ServerConnection(channel, dataChannel, undertowOptions, bufferSize, rootHandler); // Check request headers. if (!checkRequestHeaders(dataChannel.getHeaders())) { channel.sendRstStream(frame.getStreamId(), Http2Channel.ERROR_PROTOCOL_ERROR); try { Channels.drain(frame, Long.MAX_VALUE); } catch (IOException e) { // ignore, this is expected because of the RST } return; } final HttpServerExchange exchange = new HttpServerExchange(connection, dataChannel.getHeaders(), dataChannel.getResponseChannel().getHeaders(), maxEntitySize); dataChannel.setTrailersHandler(new Http2StreamSourceChannel.TrailersHandler() { @Override public void handleTrailers(HeaderMap headerMap) { exchange.putAttachment(HttpAttachments.REQUEST_TRAILERS, headerMap); } }); connection.setExchange(exchange); dataChannel.setMaxStreamSize(maxEntitySize); exchange.setRequestScheme(exchange.getRequestHeaders().getFirst(SCHEME)); exchange.setRequestMethod(Methods.fromString(exchange.getRequestHeaders().getFirst(METHOD))); exchange.getRequestHeaders().put(Headers.HOST, exchange.getRequestHeaders().getFirst(AUTHORITY)); if(!Connectors.areRequestHeadersValid(exchange.getRequestHeaders())) { UndertowLogger.REQUEST_IO_LOGGER.debugf("Invalid headers in HTTP/2 request, closing connection. Remote peer %s", connection.getPeerAddress()); channel.sendGoAway(Http2Channel.ERROR_PROTOCOL_ERROR); return; } final String path = exchange.getRequestHeaders().getFirst(PATH); if(path == null || path.isEmpty()) { UndertowLogger.REQUEST_IO_LOGGER.debugf("No :path header sent in HTTP/2 request, closing connection. Remote peer %s", connection.getPeerAddress()); channel.sendGoAway(Http2Channel.ERROR_PROTOCOL_ERROR); return; } if (recordRequestStartTime) { Connectors.setRequestStartTime(exchange); } handleCommonSetup(dataChannel.getResponseChannel(), exchange, connection); if(!dataChannel.isOpen()) { Connectors.terminateRequest(exchange); } else { dataChannel.setCompletionListener(new ChannelListener() { @Override public void handleEvent(Http2StreamSourceChannel channel) { Connectors.terminateRequest(exchange); } }); } if(connectorStatistics != null) { connectorStatistics.setup(exchange); } try { Connectors.setExchangeRequestPath(exchange, path, encoding, decode, allowEncodingSlash, decodeBuffer, maxParameters); } catch (ParameterLimitException e) { //this can happen if max parameters is exceeded UndertowLogger.REQUEST_IO_LOGGER.debug("Failed to set request path", e); exchange.setStatusCode(StatusCodes.BAD_REQUEST); exchange.endExchange(); return; } //TODO: we should never actually put these into the map in the first place exchange.getRequestHeaders().remove(AUTHORITY); exchange.getRequestHeaders().remove(PATH); exchange.getRequestHeaders().remove(SCHEME); exchange.getRequestHeaders().remove(METHOD); Connectors.executeRootHandler(rootHandler, exchange); } /** * Handles the initial request when the exchange was started by a HTTP upgrade. * * @param initial The initial upgrade request that started the HTTP2 connection */ void handleInitialRequest(HttpServerExchange initial, Http2Channel channel, byte[] data) { //we have a request Http2HeadersStreamSinkChannel sink = channel.createInitialUpgradeResponseStream(); final Http2ServerConnection connection = new Http2ServerConnection(channel, sink, undertowOptions, bufferSize, rootHandler); HeaderMap requestHeaders = new HeaderMap(); for(HeaderValues hv : initial.getRequestHeaders()) { requestHeaders.putAll(hv.getHeaderName(), hv); } final HttpServerExchange exchange = new HttpServerExchange(connection, requestHeaders, sink.getHeaders(), maxEntitySize); if(initial.getRequestHeaders().contains(Headers.EXPECT)) { HttpContinue.markContinueResponseSent(exchange); } if(initial.getAttachment(HttpAttachments.REQUEST_TRAILERS) != null) { exchange.putAttachment(HttpAttachments.REQUEST_TRAILERS, initial.getAttachment(HttpAttachments.REQUEST_TRAILERS)); } Connectors.setRequestStartTime(initial, exchange); connection.setExchange(exchange); exchange.setRequestScheme(initial.getRequestScheme()); exchange.setRequestMethod(initial.getRequestMethod()); exchange.setQueryString(initial.getQueryString()); if (data != null) { Connectors.ungetRequestBytes(exchange, new ImmediatePooledByteBuffer(ByteBuffer.wrap(data))); } Connectors.terminateRequest(exchange); String uri = exchange.getQueryString().isEmpty() ? initial.getRequestURI() : initial.getRequestURI() + '?' + exchange.getQueryString(); try { Connectors.setExchangeRequestPath(exchange, uri, encoding, decode, allowEncodingSlash, decodeBuffer, maxParameters); } catch (ParameterLimitException e) { exchange.setStatusCode(StatusCodes.BAD_REQUEST); exchange.endExchange(); return; } handleCommonSetup(sink, exchange, connection); Connectors.executeRootHandler(rootHandler, exchange); } private void handleCommonSetup(Http2HeadersStreamSinkChannel sink, HttpServerExchange exchange, Http2ServerConnection connection) { Http2Channel channel = sink.getChannel(); SSLSession session = channel.getSslSession(); if(session != null) { connection.setSslSessionInfo(new Http2SslSessionInfo(channel)); } sink.setTrailersProducer(new Http2DataStreamSinkChannel.TrailersProducer() { @Override public HeaderMap getTrailers() { Supplier supplier = exchange.getAttachment(HttpAttachments.RESPONSE_TRAILER_SUPPLIER); if(supplier != null) { return supplier.get(); } return exchange.getAttachment(HttpAttachments.RESPONSE_TRAILERS); } }); sink.setCompletionListener(new ChannelListener() { @Override public void handleEvent(Http2DataStreamSinkChannel channel) { Connectors.terminateResponse(exchange); } }); exchange.setProtocol(Protocols.HTTP_2_0); if(exchange.getRequestMethod().equals(Methods.HEAD)) { exchange.addResponseWrapper(new ConduitWrapper() { @Override public StreamSinkConduit wrap(ConduitFactory factory, HttpServerExchange exchange) { return new HeadStreamSinkConduit(factory.create(), null, true); } }); } } /** * Performs HTTP2 specification compliance check for headers and pseudo-headers of a current request. * * @param headers map of the request headers * @return true if check was successful, false otherwise */ private boolean checkRequestHeaders(HeaderMap headers) { // :method pseudo-header must be present always exactly one time; // HTTP2 request MUST NOT contain 'connection' header if (headers.count(METHOD) != 1 || headers.contains(Headers.CONNECTION)) { return false; } // if CONNECT type is used, then we expect :method and :authority to be present only; // :scheme and :path must not be present if (headers.get(METHOD).contains(Methods.CONNECT_STRING)) { if (headers.contains(SCHEME) || headers.contains(PATH) || headers.count(AUTHORITY) != 1) { return false; } // For other HTTP methods we expect that :scheme, :method, and :path pseudo-headers are // present exactly one time. } else if (headers.count(SCHEME) != 1 || headers.count(PATH) != 1) { return false; } // HTTP2 request MAY contain TE header but if so, then only with 'trailers' value. if (headers.contains(Headers.TE)) { for (String value : headers.get(Headers.TE)) { if (!value.equals("trailers")) { return false; } } } // verify content of request pseudo-headers. Each header should only have a single value. if (headers.contains(PATH)) { for (byte b: headers.get(PATH).getFirst().getBytes(ISO_8859_1)) { if (!allowUnescapedCharactersInUrl && !HttpRequestParser.isTargetCharacterAllowed((char)b)){ return false; } } } if (headers.contains(SCHEME)) { for (byte b: headers.get(SCHEME).getFirst().getBytes(ISO_8859_1)) { if (!Connectors.isValidSchemeCharacter(b)){ return false; } } } if (headers.contains(AUTHORITY)) { for (byte b: headers.get(AUTHORITY).getFirst().getBytes(ISO_8859_1)) { if (!HttpRequestParser.isTargetCharacterAllowed((char)b)){ return false; } } } if (headers.contains(METHOD)) { for (byte b: headers.get(METHOD).getFirst().getBytes(ISO_8859_1)) { if (!Connectors.isValidTokenCharacter(b)){ return false; } } } return true; } } Http2ServerConnection.java000066400000000000000000000422021420065311100335400ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http2/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http2; import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.List; import javax.net.ssl.SSLSession; import io.undertow.UndertowLogger; import io.undertow.UndertowOptions; import io.undertow.protocols.http2.Http2HeadersStreamSinkChannel; import io.undertow.server.ConduitWrapper; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HttpHandler; import io.undertow.server.XnioBufferPoolAdaptor; import io.undertow.server.protocol.http.HttpContinue; import io.undertow.util.ConduitFactory; import io.undertow.util.DateUtils; import io.undertow.util.Headers; import io.undertow.util.ParameterLimitException; import io.undertow.util.Protocols; import org.xnio.ChannelListener; import org.xnio.Option; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import org.xnio.Pool; import org.xnio.StreamConnection; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.Configurable; import org.xnio.channels.ConnectedChannel; import org.xnio.conduits.ConduitStreamSinkChannel; import org.xnio.conduits.ConduitStreamSourceChannel; import org.xnio.conduits.EmptyStreamSourceConduit; import org.xnio.conduits.StreamSinkChannelWrappingConduit; import org.xnio.conduits.StreamSinkConduit; import org.xnio.conduits.StreamSourceChannelWrappingConduit; import org.xnio.conduits.StreamSourceConduit; import org.xnio.conduits.WriteReadyHandler; import io.undertow.UndertowMessages; import io.undertow.protocols.http2.Http2Channel; import io.undertow.protocols.http2.Http2DataStreamSinkChannel; import io.undertow.protocols.http2.Http2StreamSourceChannel; import io.undertow.server.Connectors; import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpUpgradeListener; import io.undertow.server.SSLSessionInfo; import io.undertow.server.ServerConnection; import io.undertow.util.AttachmentKey; import io.undertow.util.AttachmentList; import io.undertow.util.HeaderMap; import io.undertow.util.HttpString; import io.undertow.util.StatusCodes; import static io.undertow.protocols.http2.Http2Channel.AUTHORITY; import static io.undertow.protocols.http2.Http2Channel.METHOD; import static io.undertow.protocols.http2.Http2Channel.PATH; import static io.undertow.protocols.http2.Http2Channel.SCHEME; /** * A server connection. There is one connection per request * * * TODO: how are we going to deal with attachments? * @author Stuart Douglas */ public class Http2ServerConnection extends ServerConnection { private static final HttpString STATUS = new HttpString(":status"); private final Http2Channel channel; private final Http2StreamSourceChannel requestChannel; private final Http2DataStreamSinkChannel responseChannel; private final ConduitStreamSinkChannel conduitStreamSinkChannel; private final ConduitStreamSourceChannel conduitStreamSourceChannel; private final StreamSinkConduit originalSinkConduit; private final StreamSourceConduit originalSourceConduit; private final OptionMap undertowOptions; private final int bufferSize; private SSLSessionInfo sessionInfo; private final HttpHandler rootHandler; private HttpServerExchange exchange; private boolean continueSent = false; private XnioBufferPoolAdaptor poolAdaptor; public Http2ServerConnection(Http2Channel channel, Http2StreamSourceChannel requestChannel, OptionMap undertowOptions, int bufferSize, HttpHandler rootHandler) { this.channel = channel; this.requestChannel = requestChannel; this.undertowOptions = undertowOptions; this.bufferSize = bufferSize; this.rootHandler = rootHandler; responseChannel = requestChannel.getResponseChannel(); originalSinkConduit = new StreamSinkChannelWrappingConduit(responseChannel); originalSourceConduit = new StreamSourceChannelWrappingConduit(requestChannel); this.conduitStreamSinkChannel = new ConduitStreamSinkChannel(responseChannel, originalSinkConduit); this.conduitStreamSourceChannel = new ConduitStreamSourceChannel(channel, originalSourceConduit); } void setExchange(HttpServerExchange exchange) { this.exchange = exchange; } /** * Channel that is used when the request is already half closed * @param channel * @param undertowOptions * @param bufferSize * @param rootHandler */ public Http2ServerConnection(Http2Channel channel, Http2DataStreamSinkChannel sinkChannel, OptionMap undertowOptions, int bufferSize, HttpHandler rootHandler) { this.channel = channel; this.rootHandler = rootHandler; this.requestChannel = null; this.undertowOptions = undertowOptions; this.bufferSize = bufferSize; responseChannel = sinkChannel; originalSinkConduit = new StreamSinkChannelWrappingConduit(responseChannel); originalSourceConduit = new StreamSourceChannelWrappingConduit(requestChannel); this.conduitStreamSinkChannel = new ConduitStreamSinkChannel(responseChannel, originalSinkConduit); this.conduitStreamSourceChannel = new ConduitStreamSourceChannel(Configurable.EMPTY, new EmptyStreamSourceConduit(getIoThread())); } @Override public Pool getBufferPool() { if(poolAdaptor == null) { poolAdaptor = new XnioBufferPoolAdaptor(getByteBufferPool()); } return poolAdaptor; } public SSLSession getSslSession() { return channel.getSslSession(); } @Override public ByteBufferPool getByteBufferPool() { return channel.getBufferPool(); } @Override public XnioWorker getWorker() { return channel.getWorker(); } @Override public XnioIoThread getIoThread() { return channel.getIoThread(); } @Override public HttpServerExchange sendOutOfBandResponse(HttpServerExchange exchange) { if (exchange == null || !HttpContinue.requiresContinueResponse(exchange)) { throw UndertowMessages.MESSAGES.outOfBandResponseOnlyAllowedFor100Continue(); } final HttpServerExchange newExchange = new HttpServerExchange(this); for (HttpString header : exchange.getRequestHeaders().getHeaderNames()) { newExchange.getRequestHeaders().putAll(header, exchange.getRequestHeaders().get(header)); } newExchange.setProtocol(exchange.getProtocol()); newExchange.setRequestMethod(exchange.getRequestMethod()); exchange.setRequestURI(exchange.getRequestURI(), exchange.isHostIncludedInRequestURI()); exchange.setRequestPath(exchange.getRequestPath()); exchange.setRelativePath(exchange.getRelativePath()); newExchange.setPersistent(true); Connectors.terminateRequest(newExchange); newExchange.addResponseWrapper(new ConduitWrapper() { @Override public StreamSinkConduit wrap(ConduitFactory factory, HttpServerExchange exchange) { HeaderMap headers = newExchange.getResponseHeaders(); DateUtils.addDateHeaderIfRequired(exchange); headers.add(STATUS, exchange.getStatusCode()); Connectors.flattenCookies(exchange); Http2HeadersStreamSinkChannel sink = new Http2HeadersStreamSinkChannel(channel, requestChannel.getStreamId(), headers); StreamSinkChannelWrappingConduit ret = new StreamSinkChannelWrappingConduit(sink); ret.setWriteReadyHandler(new WriteReadyHandler.ChannelListenerHandler(Connectors.getConduitSinkChannel(exchange))); return ret; } }); continueSent = true; return newExchange; } @Override public boolean isContinueResponseSupported() { return true; } @Override public void terminateRequestChannel(HttpServerExchange exchange) { if(HttpContinue.requiresContinueResponse(exchange.getRequestHeaders()) && !continueSent) { if(requestChannel != null) { //can happen on upgrade requestChannel.setIgnoreForceClose(true); requestChannel.close(); //if this request requires a 100-continue and it was not sent we have to reset the stream //we do it in a completion listener though, to make sure the response is sent first exchange.addExchangeCompleteListener(new ExchangeCompletionListener() { @Override public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { try { channel.sendRstStream(responseChannel.getStreamId(), Http2Channel.ERROR_CANCEL); } finally { nextListener.proceed(); } } }); } } } @Override public boolean isOpen() { return channel.isOpen(); } @Override public boolean supportsOption(Option option) { return false; } @Override public T getOption(Option option) throws IOException { return null; } @Override public T setOption(Option option, T value) throws IllegalArgumentException, IOException { return null; } @Override public void close() throws IOException { channel.sendRstStream(requestChannel.getStreamId(), Http2Channel.ERROR_CANCEL); } @Override public SocketAddress getPeerAddress() { return channel.getPeerAddress(); } @Override public A getPeerAddress(Class type) { return channel.getPeerAddress(type); } @Override public ChannelListener.Setter getCloseSetter() { return channel.getCloseSetter(); } @Override public SocketAddress getLocalAddress() { return channel.getLocalAddress(); } @Override public A getLocalAddress(Class type) { return channel.getLocalAddress(type); } @Override public OptionMap getUndertowOptions() { return undertowOptions; } @Override public int getBufferSize() { return bufferSize; } @Override public SSLSessionInfo getSslSessionInfo() { return sessionInfo; } @Override public void setSslSessionInfo(SSLSessionInfo sessionInfo) { this.sessionInfo = sessionInfo; } @Override public void addCloseListener(final CloseListener listener) { channel.addCloseTask(new ChannelListener() { @Override public void handleEvent(Http2Channel channel) { listener.closed(Http2ServerConnection.this); } }); } @Override protected StreamConnection upgradeChannel() { throw UndertowMessages.MESSAGES.upgradeNotSupported(); } @Override protected ConduitStreamSinkChannel getSinkChannel() { return conduitStreamSinkChannel; } @Override protected ConduitStreamSourceChannel getSourceChannel() { return conduitStreamSourceChannel; } @Override protected StreamSinkConduit getSinkConduit(HttpServerExchange exchange, StreamSinkConduit conduit) { HeaderMap headers = responseChannel.getHeaders(); DateUtils.addDateHeaderIfRequired(exchange); headers.add(STATUS, exchange.getStatusCode()); Connectors.flattenCookies(exchange); if(!Connectors.isEntityBodyAllowed(exchange)) { //we are not allowed to send an entity body for some requests exchange.getResponseHeaders().remove(Headers.CONTENT_LENGTH); exchange.getResponseHeaders().remove(Headers.TRANSFER_ENCODING); } return originalSinkConduit; } @Override protected boolean isUpgradeSupported() { return false; } @Override protected boolean isConnectSupported() { return false; } @Override protected void exchangeComplete(HttpServerExchange exchange) { } @Override protected void setUpgradeListener(HttpUpgradeListener upgradeListener) { throw UndertowMessages.MESSAGES.upgradeNotSupported(); } @Override protected void setConnectListener(HttpUpgradeListener connectListener) { } @Override protected void maxEntitySizeUpdated(HttpServerExchange exchange) { if(requestChannel != null) { requestChannel.setMaxStreamSize(exchange.getMaxEntitySize()); } } @Override public void addToAttachmentList(AttachmentKey> key, T value) { channel.addToAttachmentList(key, value); } @Override public T removeAttachment(AttachmentKey key) { return channel.removeAttachment(key); } @Override public T putAttachment(AttachmentKey key, T value) { return channel.putAttachment(key, value); } @Override public List getAttachmentList(AttachmentKey> key) { return channel.getAttachmentList(key); } @Override public T getAttachment(AttachmentKey key) { return channel.getAttachment(key); } @Override public boolean isPushSupported() { return channel.isPushEnabled() && !exchange.getRequestHeaders().contains(Headers.X_DISABLE_PUSH) // push is not supported for already pushed streams, just for peer-initiated (odd) ids && responseChannel.getStreamId() % 2 != 0; } @Override public boolean isRequestTrailerFieldsSupported() { return true; } @Override public boolean pushResource(String path, HttpString method, HeaderMap requestHeaders) { return pushResource(path, method, requestHeaders, rootHandler); } @Override public boolean pushResource(String path, HttpString method, HeaderMap requestHeaders, final HttpHandler handler) { HeaderMap responseHeaders = new HeaderMap(); try { requestHeaders.put(METHOD, method.toString()); requestHeaders.put(PATH, path.toString()); requestHeaders.put(AUTHORITY, exchange.getHostAndPort()); requestHeaders.put(SCHEME, exchange.getRequestScheme()); Http2HeadersStreamSinkChannel sink = channel.sendPushPromise(responseChannel.getStreamId(), requestHeaders, responseHeaders); Http2ServerConnection newConnection = new Http2ServerConnection(channel, sink, getUndertowOptions(), getBufferSize(), rootHandler); final HttpServerExchange exchange = new HttpServerExchange(newConnection, requestHeaders, responseHeaders, getUndertowOptions().get(UndertowOptions.MAX_ENTITY_SIZE, UndertowOptions.DEFAULT_MAX_ENTITY_SIZE)); newConnection.setExchange(exchange); exchange.setRequestMethod(method); exchange.setProtocol(Protocols.HTTP_1_1); exchange.setRequestScheme(this.exchange.getRequestScheme()); try { Connectors.setExchangeRequestPath(exchange, path, getUndertowOptions().get(UndertowOptions.URL_CHARSET, StandardCharsets.UTF_8.name()), getUndertowOptions().get(UndertowOptions.DECODE_URL, true), getUndertowOptions().get(UndertowOptions.ALLOW_ENCODED_SLASH, false), new StringBuilder(), getUndertowOptions().get(UndertowOptions.MAX_PARAMETERS, UndertowOptions.DEFAULT_MAX_HEADERS)); } catch (ParameterLimitException e) { UndertowLogger.REQUEST_IO_LOGGER.debug("Too many parameters in HTTP/2 request", e); exchange.setStatusCode(StatusCodes.BAD_REQUEST); exchange.endExchange(); return false; } sink.setCompletionListener(new ChannelListener() { @Override public void handleEvent(Http2DataStreamSinkChannel channel) { Connectors.terminateResponse(exchange); } }); Connectors.terminateRequest(exchange); getIoThread().execute(new Runnable() { @Override public void run() { Connectors.executeRootHandler(handler, exchange); } }); return true; } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); return false; } } @Override public String getTransportProtocol() { return channel.getProtocol(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http2/Http2SslSessionInfo.java000066400000000000000000000064341420065311100332610ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http2; import java.io.IOException; import java.security.cert.Certificate; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.security.cert.X509Certificate; import org.xnio.Options; import org.xnio.SslClientAuthMode; import io.undertow.UndertowMessages; import io.undertow.protocols.http2.Http2Channel; import io.undertow.server.HttpServerExchange; import io.undertow.server.RenegotiationRequiredException; import io.undertow.server.SSLSessionInfo; /** * @author Stuart Douglas */ class Http2SslSessionInfo implements SSLSessionInfo { private final Http2Channel channel; Http2SslSessionInfo(Http2Channel channel) { this.channel = channel; } @Override public byte[] getSessionId() { return channel.getSslSession().getId(); } @Override public String getCipherSuite() { return channel.getSslSession().getCipherSuite(); } @Override public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException, RenegotiationRequiredException { try { return channel.getSslSession().getPeerCertificates(); } catch (SSLPeerUnverifiedException e) { try { SslClientAuthMode sslClientAuthMode = channel.getOption(Options.SSL_CLIENT_AUTH_MODE); if (sslClientAuthMode == SslClientAuthMode.NOT_REQUESTED) { throw new RenegotiationRequiredException(); } } catch (IOException e1) { //ignore, will not actually happen } throw e; } } @Override public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException, RenegotiationRequiredException { try { return channel.getSslSession().getPeerCertificateChain(); } catch (SSLPeerUnverifiedException e) { try { SslClientAuthMode sslClientAuthMode = channel.getOption(Options.SSL_CLIENT_AUTH_MODE); if (sslClientAuthMode == SslClientAuthMode.NOT_REQUESTED) { throw new RenegotiationRequiredException(); } } catch (IOException e1) { //ignore, will not actually happen } throw e; } } @Override public void renegotiate(HttpServerExchange exchange, SslClientAuthMode sslClientAuthMode) throws IOException { throw UndertowMessages.MESSAGES.renegotiationNotSupported(); } @Override public SSLSession getSSLSession() { return channel.getSslSession(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/http2/Http2UpgradeHandler.java000066400000000000000000000205221420065311100332170ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http2; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.xnio.OptionMap; import org.xnio.StreamConnection; import io.undertow.UndertowLogger; import io.undertow.UndertowOptions; import io.undertow.io.IoCallback; import io.undertow.io.Receiver; import io.undertow.io.Sender; import io.undertow.protocols.http2.Http2Channel; import io.undertow.server.Connectors; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpUpgradeListener; import io.undertow.server.protocol.http.HttpContinue; import io.undertow.util.FlexBase64; import io.undertow.util.Headers; import io.undertow.util.ImmediatePooledByteBuffer; import io.undertow.util.Protocols; import io.undertow.util.StatusCodes; /** * Upgrade listener for HTTP2, this allows connections to be established using the upgrade * mechanism as detailed in Section 3.2. This should always be the first handler in a handler * chain. * * * @author Stuart Douglas */ public class Http2UpgradeHandler implements HttpHandler { private final HttpHandler next; private final Set upgradeStrings; public Http2UpgradeHandler(HttpHandler next) { this.next = next; this.upgradeStrings = Collections.singleton(Http2Channel.CLEARTEXT_UPGRADE_STRING); } public Http2UpgradeHandler(HttpHandler next, String... upgradeStrings) { this.next = next; this.upgradeStrings = new HashSet<>(Arrays.asList(upgradeStrings)); } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { final String upgrade = exchange.getRequestHeaders().getFirst(Headers.UPGRADE); final String settings = exchange.getRequestHeaders().getFirst("HTTP2-Settings"); if(settings != null && upgrade != null && upgradeStrings.contains(upgrade)) { if(HttpContinue.requiresContinueResponse(exchange) && false) { HttpContinue.sendContinueResponse(exchange, new IoCallback() { @Override public void onComplete(HttpServerExchange exchange, Sender sender) { try { handleUpgradeBody(exchange, upgrade, settings); } catch (Exception e) { throw new RuntimeException(e); } } @Override public void onException(HttpServerExchange exchange, Sender sender, IOException exception) { exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); exchange.endExchange(); } }); } else { handleUpgradeBody(exchange, upgrade, settings); } return; } next.handleRequest(exchange); } private void handleUpgradeBody(HttpServerExchange exchange, String upgrade, String settings) throws Exception { if(exchange.isRequestComplete()) { handleHttp2Upgrade(exchange, upgrade, settings, null); } else { final int maxBufferedSize = exchange.getConnection().getUndertowOptions().get(UndertowOptions.MAX_BUFFERED_REQUEST_SIZE, UndertowOptions.DEFAULT_MAX_BUFFERED_REQUEST_SIZE); if(exchange.getRequestContentLength() > maxBufferedSize) { //request is too big to buffer //we don't upgrade to HTTP/2 next.handleRequest(exchange); } else if(exchange.getRequestContentLength() > 0 && exchange.getRequestContentLength() < maxBufferedSize) { //we know it is fine to buffer exchange.getRequestReceiver().receiveFullBytes(new Receiver.FullBytesCallback() { @Override public void handle(HttpServerExchange exchange, byte[] message) { try { handleHttp2Upgrade(exchange, upgrade, settings, message); } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); exchange.endExchange(); } } }); } else { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); exchange.getRequestReceiver().receivePartialBytes(new Receiver.PartialBytesCallback() { @Override public void handle(HttpServerExchange exchange, byte[] message, boolean last) { try { outputStream.write(message); if(last) { handleHttp2Upgrade(exchange, upgrade, settings, outputStream.toByteArray()); } else if(outputStream.size() >= maxBufferedSize) { exchange.getRequestReceiver().pause(); Connectors.ungetRequestBytes(exchange, new ImmediatePooledByteBuffer(ByteBuffer.wrap(outputStream.toByteArray()))); Connectors.resetRequestChannel(exchange); next.handleRequest(exchange); } } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); exchange.endExchange(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } } }); } } } private void handleHttp2Upgrade(HttpServerExchange exchange, final String upgrade, String settings, final byte[] data) throws IOException { //required by spec final ByteBuffer settingsFrame = FlexBase64.decodeURL(settings); exchange.getResponseHeaders().put(Headers.UPGRADE, upgrade); exchange.upgradeChannel(new HttpUpgradeListener() { @Override public void handleUpgrade(StreamConnection streamConnection, HttpServerExchange exchange) { OptionMap undertowOptions = exchange.getConnection().getUndertowOptions(); Http2Channel channel = new Http2Channel(streamConnection, upgrade, exchange.getConnection().getByteBufferPool(), null, false, true, true, settingsFrame, undertowOptions); Http2ReceiveListener receiveListener = new Http2ReceiveListener(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { //if this header is present we don't actually process the rest of the handler chain //as the request was only to create the initial request if(exchange.getRequestHeaders().contains("X-HTTP2-connect-only")) { exchange.endExchange(); return; } exchange.setProtocol(Protocols.HTTP_2_0); next.handleRequest(exchange); } }, undertowOptions, exchange.getConnection().getBufferSize(), null); channel.getReceiveSetter().set(receiveListener); receiveListener.handleInitialRequest(exchange, channel, data); channel.resumeReceives(); } }); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/proxy/000077500000000000000000000000001420065311100266645ustar00rootroot00000000000000ProxyProtocolOpenListener.java000066400000000000000000000022621420065311100346450ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/proxypackage io.undertow.server.protocol.proxy; import io.undertow.connector.ByteBufferPool; import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.server.OpenListener; import org.xnio.ChannelListener; import org.xnio.OptionMap; import org.xnio.StreamConnection; /** * Open listener for proxied connections * * @author Stuart Douglas */ public class ProxyProtocolOpenListener implements ChannelListener { private final OpenListener openListener; private final UndertowXnioSsl ssl; private final ByteBufferPool bufferPool; private final OptionMap sslOptionMap; public ProxyProtocolOpenListener(OpenListener openListener, UndertowXnioSsl ssl, ByteBufferPool bufferPool, OptionMap sslOptionMap) { this.openListener = openListener; this.ssl = ssl; this.bufferPool = bufferPool; this.sslOptionMap = sslOptionMap; } @Override public void handleEvent(StreamConnection streamConnection) { streamConnection.getSourceChannel().setReadListener(new ProxyProtocolReadListener(streamConnection, openListener, ssl, bufferPool, sslOptionMap)); streamConnection.getSourceChannel().wakeupReads(); } } ProxyProtocolReadListener.java000066400000000000000000000406341420065311100346240ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/protocol/proxypackage io.undertow.server.protocol.proxy; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.server.DelegateOpenListener; import io.undertow.server.OpenListener; import io.undertow.util.NetworkUtils; import io.undertow.util.PooledAdaptor; import org.xnio.ChannelListener; import org.xnio.IoUtils; import org.xnio.OptionMap; import org.xnio.StreamConnection; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.PushBackStreamSourceConduit; import org.xnio.ssl.SslConnection; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; import java.util.concurrent.atomic.AtomicBoolean; /** * Implementation of version 1 and 2 of the proxy protocol (https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) *

* Even though it is not required by the spec this implementation provides a stateful parser, that can handle * fragmentation of * * @author Stuart Douglas * @author Ulrich Herberg */ class ProxyProtocolReadListener implements ChannelListener { private static final int MAX_HEADER_LENGTH = 107; private static final byte[] NAME = "PROXY ".getBytes(StandardCharsets.US_ASCII); private static final String UNKNOWN = "UNKNOWN"; private static final String TCP4 = "TCP4"; private static final String TCP6 = "TCP6"; private static final byte[] SIG = new byte[] {0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A}; private final StreamConnection streamConnection; private final OpenListener openListener; private final UndertowXnioSsl ssl; private final ByteBufferPool bufferPool; private final OptionMap sslOptionMap; private int byteCount; private String protocol; private InetAddress sourceAddress; private InetAddress destAddress; private int sourcePort = -1; private int destPort = -1; private StringBuilder stringBuilder = new StringBuilder(); private boolean carriageReturnSeen = false; private boolean parsingUnknown = false; ProxyProtocolReadListener(StreamConnection streamConnection, OpenListener openListener, UndertowXnioSsl ssl, ByteBufferPool bufferPool, OptionMap sslOptionMap) { this.streamConnection = streamConnection; this.openListener = openListener; this.ssl = ssl; this.bufferPool = bufferPool; this.sslOptionMap = sslOptionMap; if (bufferPool.getBufferSize() < MAX_HEADER_LENGTH) { throw UndertowMessages.MESSAGES.bufferPoolTooSmall(MAX_HEADER_LENGTH); } } @Override public void handleEvent(StreamSourceChannel streamSourceChannel) { PooledByteBuffer buffer = bufferPool.allocate(); AtomicBoolean freeBuffer = new AtomicBoolean(true); try { int res = streamSourceChannel.read(buffer.getBuffer()); if (res == -1) { IoUtils.safeClose(streamConnection); return; } else if (res == 0) { return; } else { buffer.getBuffer().flip(); if (buffer.getBuffer().hasRemaining()) { byte firstByte = buffer.getBuffer().get(); // get first byte to determine whether Proxy Protocol V1 or V2 is used byteCount++; if (firstByte == SIG[0]) { // Could be Proxy Protocol V2 parseProxyProtocolV2(buffer, freeBuffer); } else if ((char) firstByte == NAME[0]){ // Could be Proxy Protocol V1 parseProxyProtocolV1(buffer, freeBuffer); } else { throw UndertowMessages.MESSAGES.invalidProxyHeader(); } } return; } } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); IoUtils.safeClose(streamConnection); } catch (Exception e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(new IOException(e)); IoUtils.safeClose(streamConnection); } finally { if (freeBuffer.get()) { buffer.close(); } } } private void parseProxyProtocolV2(PooledByteBuffer buffer, AtomicBoolean freeBuffer) throws Exception { while (byteCount < SIG.length) { byte c = buffer.getBuffer().get(); //first we verify that we have the correct protocol if (c != SIG[byteCount]) { throw UndertowMessages.MESSAGES.invalidProxyHeader(); } byteCount++; } byte ver_cmd = buffer.getBuffer().get(); byte fam = buffer.getBuffer().get(); int len = (buffer.getBuffer().getShort() & 0xffff); if ((ver_cmd & 0xF0) != 0x20) { // expect version 2 throw UndertowMessages.MESSAGES.invalidProxyHeader(); } switch (ver_cmd & 0x0F) { case 0x01: // PROXY command switch (fam) { case 0x11: { // TCP over IPv4 if (len < 12) { throw UndertowMessages.MESSAGES.invalidProxyHeader(); } byte[] sourceAddressBytes = new byte[4]; buffer.getBuffer().get(sourceAddressBytes); sourceAddress = InetAddress.getByAddress(sourceAddressBytes); byte[] dstAddressBytes = new byte[4]; buffer.getBuffer().get(dstAddressBytes); destAddress = InetAddress.getByAddress(dstAddressBytes); sourcePort = buffer.getBuffer().getShort() & 0xffff; destPort = buffer.getBuffer().getShort() & 0xffff; if (len > 12) { int skipAhead = len - 12; int currentPosition = buffer.getBuffer().position(); buffer.getBuffer().position(currentPosition + skipAhead); } break; } case 0x21: { // TCP over IPv6 if (len < 36) { throw UndertowMessages.MESSAGES.invalidProxyHeader(); } byte[] sourceAddressBytes = new byte[16]; buffer.getBuffer().get(sourceAddressBytes); sourceAddress = InetAddress.getByAddress(sourceAddressBytes); byte[] dstAddressBytes = new byte[16]; buffer.getBuffer().get(dstAddressBytes); destAddress = InetAddress.getByAddress(dstAddressBytes); sourcePort = buffer.getBuffer().getShort() & 0xffff; destPort = buffer.getBuffer().getShort() & 0xffff; if (len > 36) { int skipAhead = len - 36; int currentPosition = buffer.getBuffer().position(); buffer.getBuffer().position(currentPosition + skipAhead); } break; } default: // AF_UNIX sockets not supported throw UndertowMessages.MESSAGES.invalidProxyHeader(); } break; case 0x00: // LOCAL command if (len > 0) { int skipAhead = len; int currentPosition = buffer.getBuffer().position(); buffer.getBuffer().position(currentPosition + skipAhead); } if (buffer.getBuffer().hasRemaining()) { freeBuffer.set(false); proxyAccept(null, null, buffer); } else { proxyAccept(null, null, null); } return; default: throw UndertowMessages.MESSAGES.invalidProxyHeader(); } SocketAddress s = new InetSocketAddress(sourceAddress, sourcePort); SocketAddress d = new InetSocketAddress(destAddress, destPort); if (buffer.getBuffer().hasRemaining()) { freeBuffer.set(false); proxyAccept(s, d, buffer); } else { proxyAccept(s, d, null); } return; } private void parseProxyProtocolV1(PooledByteBuffer buffer, AtomicBoolean freeBuffer) throws Exception { while (buffer.getBuffer().hasRemaining()) { char c = (char) buffer.getBuffer().get(); if (byteCount < NAME.length) { //first we verify that we have the correct protocol if (c != NAME[byteCount]) { throw UndertowMessages.MESSAGES.invalidProxyHeader(); } } else { if (parsingUnknown) { //we are parsing the UNKNOWN protocol //we just ignore everything till \r\n if (c == '\r') { carriageReturnSeen = true; } else if (c == '\n') { if (!carriageReturnSeen) { throw UndertowMessages.MESSAGES.invalidProxyHeader(); } //we are done if (buffer.getBuffer().hasRemaining()) { freeBuffer.set(false); proxyAccept(null, null, buffer); } else { proxyAccept(null, null, null); } return; } else if (carriageReturnSeen) { throw UndertowMessages.MESSAGES.invalidProxyHeader(); } } else if (carriageReturnSeen) { if (c == '\n') { //we are done SocketAddress s = new InetSocketAddress(sourceAddress, sourcePort); SocketAddress d = new InetSocketAddress(destAddress, destPort); if (buffer.getBuffer().hasRemaining()) { freeBuffer.set(false); proxyAccept(s, d, buffer); } else { proxyAccept(s, d, null); } return; } else { throw UndertowMessages.MESSAGES.invalidProxyHeader(); } } else switch (c) { case ' ': //we have a space if (sourcePort != -1 || stringBuilder.length() == 0) { //header was invalid, either we are expecting a \r or a \n, or the previous character was a space throw UndertowMessages.MESSAGES.invalidProxyHeader(); } else if (protocol == null) { protocol = stringBuilder.toString(); stringBuilder.setLength(0); if (protocol.equals(UNKNOWN)) { parsingUnknown = true; } else if (!protocol.equals(TCP4) && !protocol.equals(TCP6)) { throw UndertowMessages.MESSAGES.invalidProxyHeader(); } } else if (sourceAddress == null) { sourceAddress = parseAddress(stringBuilder.toString(), protocol); stringBuilder.setLength(0); } else if (destAddress == null) { destAddress = parseAddress(stringBuilder.toString(), protocol); stringBuilder.setLength(0); } else { sourcePort = Integer.parseInt(stringBuilder.toString()); stringBuilder.setLength(0); } break; case '\r': if (destPort == -1 && sourcePort != -1 && !carriageReturnSeen && stringBuilder.length() > 0) { destPort = Integer.parseInt(stringBuilder.toString()); stringBuilder.setLength(0); carriageReturnSeen = true; } else if (protocol == null) { if (UNKNOWN.equals(stringBuilder.toString())) { parsingUnknown = true; carriageReturnSeen = true; } } else { throw UndertowMessages.MESSAGES.invalidProxyHeader(); } break; case '\n': throw UndertowMessages.MESSAGES.invalidProxyHeader(); default: stringBuilder.append(c); } } byteCount++; if (byteCount == MAX_HEADER_LENGTH) { throw UndertowMessages.MESSAGES.headerSizeToLarge(); } } } private void proxyAccept(SocketAddress source, SocketAddress dest, PooledByteBuffer additionalData) { StreamConnection streamConnection = this.streamConnection; if (source != null) { streamConnection = new AddressWrappedConnection(streamConnection, source, dest); } if (ssl != null) { //we need to apply the additional data before the SSL wrapping if (additionalData != null) { PushBackStreamSourceConduit conduit = new PushBackStreamSourceConduit(streamConnection.getSourceChannel().getConduit()); conduit.pushBack(new PooledAdaptor(additionalData)); streamConnection.getSourceChannel().setConduit(conduit); } SslConnection sslConnection = ssl.wrapExistingConnection(streamConnection, sslOptionMap == null ? OptionMap.EMPTY : sslOptionMap, false); streamConnection = sslConnection; callOpenListener(streamConnection, null); } else { callOpenListener(streamConnection, additionalData); } } private void callOpenListener(StreamConnection streamConnection, final PooledByteBuffer buffer) { if (openListener instanceof DelegateOpenListener) { ((DelegateOpenListener) openListener).handleEvent(streamConnection, buffer); } else { if (buffer != null) { PushBackStreamSourceConduit conduit = new PushBackStreamSourceConduit(streamConnection.getSourceChannel().getConduit()); conduit.pushBack(new PooledAdaptor(buffer)); streamConnection.getSourceChannel().setConduit(conduit); } openListener.handleEvent(streamConnection); } } static InetAddress parseAddress(String addressString, String protocol) throws IOException { if (protocol.equals(TCP4)) { return NetworkUtils.parseIpv4Address(addressString); } else { return NetworkUtils.parseIpv6Address(addressString); } } private static final class AddressWrappedConnection extends StreamConnection { private final StreamConnection delegate; private final SocketAddress source; private final SocketAddress dest; AddressWrappedConnection(StreamConnection delegate, SocketAddress source, SocketAddress dest) { super(delegate.getIoThread()); this.delegate = delegate; this.source = source; this.dest = dest; setSinkConduit(delegate.getSinkChannel().getConduit()); setSourceConduit(delegate.getSourceChannel().getConduit()); } @Override protected void notifyWriteClosed() { IoUtils.safeClose(delegate.getSinkChannel()); } @Override protected void notifyReadClosed() { IoUtils.safeClose(delegate.getSourceChannel()); } @Override public SocketAddress getPeerAddress() { return source; } @Override public SocketAddress getLocalAddress() { return dest; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/session/000077500000000000000000000000001420065311100253255ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/session/InMemorySessionManager.java000066400000000000000000000603321420065311100325720ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.session; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.server.HttpServerExchange; import io.undertow.util.AttachmentKey; import io.undertow.util.ConcurrentDirectDeque; import io.undertow.util.WorkerUtils; import java.math.BigDecimal; import java.math.BigInteger; import java.math.MathContext; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import org.xnio.XnioExecutor; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; /** * The default in memory session manager. This basically just stores sessions in an in memory hash map. *

* * @author Stuart Douglas */ public class InMemorySessionManager implements SessionManager, SessionManagerStatistics { private final AttachmentKey NEW_SESSION = AttachmentKey.create(SessionImpl.class); private final SessionIdGenerator sessionIdGenerator; private final ConcurrentMap sessions; private final SessionListeners sessionListeners = new SessionListeners(); /** * 30 minute default */ private volatile int defaultSessionTimeout = 30 * 60; private final int maxSize; private final ConcurrentDirectDeque evictionQueue; private final String deploymentName; private final AtomicLong createdSessionCount = new AtomicLong(); private final AtomicLong rejectedSessionCount = new AtomicLong(); private volatile long longestSessionLifetime = 0; private volatile long expiredSessionCount = 0; private volatile BigInteger totalSessionLifetime = BigInteger.ZERO; private final AtomicInteger highestSessionCount = new AtomicInteger(); private final boolean statisticsEnabled; private volatile long startTime; private final boolean expireOldestUnusedSessionOnMax; public InMemorySessionManager(String deploymentName, int maxSessions, boolean expireOldestUnusedSessionOnMax) { this(new SecureRandomSessionIdGenerator(), deploymentName, maxSessions, expireOldestUnusedSessionOnMax); } public InMemorySessionManager(SessionIdGenerator sessionIdGenerator, String deploymentName, int maxSessions, boolean expireOldestUnusedSessionOnMax) { this(sessionIdGenerator, deploymentName, maxSessions, expireOldestUnusedSessionOnMax, true); } public InMemorySessionManager(SessionIdGenerator sessionIdGenerator, String deploymentName, int maxSessions, boolean expireOldestUnusedSessionOnMax, boolean statisticsEnabled) { this.sessionIdGenerator = sessionIdGenerator; this.deploymentName = deploymentName; this.statisticsEnabled = statisticsEnabled; this.expireOldestUnusedSessionOnMax = expireOldestUnusedSessionOnMax; this.sessions = new ConcurrentHashMap<>(); this.maxSize = maxSessions; ConcurrentDirectDeque evictionQueue = null; if (maxSessions > 0 && expireOldestUnusedSessionOnMax) { evictionQueue = ConcurrentDirectDeque.newInstance(); } this.evictionQueue = evictionQueue; } public InMemorySessionManager(String deploymentName, int maxSessions) { this(deploymentName, maxSessions, false); } public InMemorySessionManager(String id) { this(id, -1); } @Override public String getDeploymentName() { return this.deploymentName; } @Override public void start() { createdSessionCount.set(0); expiredSessionCount = 0; rejectedSessionCount.set(0); totalSessionLifetime = BigInteger.ZERO; startTime = System.currentTimeMillis(); } @Override public void stop() { for (Map.Entry session : sessions.entrySet()) { final SessionImpl sessionValue = session.getValue(); sessionValue.destroy(); if (sessionValue.getId() == null) { // this means we are creating the session right now in a different thread, // do not send the session to listener with a null id, // just set it again, setting the same session id twice is harmless sessionValue.setId(session.getKey()); } sessionListeners.sessionDestroyed(session.getValue(), null, SessionListener.SessionDestroyedReason.UNDEPLOY); } sessions.clear(); } @Override public Session createSession(final HttpServerExchange serverExchange, final SessionConfig config) { if (maxSize > 0) { if(expireOldestUnusedSessionOnMax) { while (sessions.size() >= maxSize && !evictionQueue.isEmpty()) { String key = evictionQueue.poll(); if(key == null) { break; } UndertowLogger.REQUEST_LOGGER.debugf("Removing session %s as max size has been hit", key); SessionImpl toRemove = sessions.get(key); if (toRemove != null) { toRemove.invalidate(null, SessionListener.SessionDestroyedReason.TIMEOUT); //todo: better reason } } } else if (sessions.size() >= maxSize) { if(statisticsEnabled) { rejectedSessionCount.incrementAndGet(); } throw UndertowMessages.MESSAGES.tooManySessions(maxSize); } } if (config == null) { throw UndertowMessages.MESSAGES.couldNotFindSessionCookieConfig(); } String sessionID = config.findSessionId(serverExchange); final SessionImpl session = new SessionImpl(this, config, serverExchange.getIoThread(), serverExchange.getConnection().getWorker(), defaultSessionTimeout); if (sessionID != null) { if (!saveSessionID(sessionID, session)) throw UndertowMessages.MESSAGES.sessionWithIdAlreadyExists(sessionID); // else: succeeded to use requested session id } else { sessionID = createAndSaveNewID(session); } session.setId(sessionID); if (evictionQueue != null) { session.setEvictionToken(evictionQueue.offerLastAndReturnToken(sessionID)); } UndertowLogger.SESSION_LOGGER.debugf("Created session with id %s for exchange %s", sessionID, serverExchange); config.setSessionId(serverExchange, session.getId()); session.bumpTimeout(); sessionListeners.sessionCreated(session, serverExchange); serverExchange.putAttachment(NEW_SESSION, session); if(statisticsEnabled) { createdSessionCount.incrementAndGet(); int highest; int sessionSize; do { highest = highestSessionCount.get(); sessionSize = sessions.size(); if(sessionSize <= highest) { break; } } while (!highestSessionCount.compareAndSet(highest, sessionSize)); } return session; } private boolean saveSessionID(String sessionID, SessionImpl session) { return this.sessions.putIfAbsent(sessionID, session) == null; } private String createAndSaveNewID(SessionImpl session) { for (int i = 0; i < 100; i++) { final String sessionID = sessionIdGenerator.createSessionId(); if (saveSessionID(sessionID, session)) return sessionID; } //this should 'never' happen //but we guard against pathological session id generators to prevent an infinite loop throw UndertowMessages.MESSAGES.couldNotGenerateUniqueSessionId(); } @Override public Session getSession(final HttpServerExchange serverExchange, final SessionConfig config) { if (serverExchange != null) { SessionImpl newSession = serverExchange.getAttachment(NEW_SESSION); if(newSession != null) { return newSession; } } String sessionId = config.findSessionId(serverExchange); InMemorySessionManager.SessionImpl session = (SessionImpl) getSession(sessionId); if(session != null && serverExchange != null) { session.requestStarted(serverExchange); } return session; } @Override public Session getSession(String sessionId) { if (sessionId == null) { return null; } final SessionImpl sess = sessions.get(sessionId); if (sess == null) { return null; } if (sess.getId() == null) { // this means we are creating the session right now in a different thread, // do not return the session with a null id to the outer world, // just set it again, setting the same session id twice is harmless sess.setId(sessionId); } return sess; } @Override public synchronized void registerSessionListener(final SessionListener listener) { UndertowLogger.SESSION_LOGGER.debugf("Registered session listener %s", listener); sessionListeners.addSessionListener(listener); } @Override public synchronized void removeSessionListener(final SessionListener listener) { UndertowLogger.SESSION_LOGGER.debugf("Removed session listener %s", listener); sessionListeners.removeSessionListener(listener); } @Override public void setDefaultSessionTimeout(final int timeout) { UndertowLogger.SESSION_LOGGER.debugf("Setting default session timeout to %s", timeout); defaultSessionTimeout = timeout; } @Override public Set getTransientSessions() { return getAllSessions(); } @Override public Set getActiveSessions() { return getAllSessions(); } @Override public Set getAllSessions() { return new HashSet<>(sessions.keySet()); } @Override public boolean equals(Object object) { if (!(object instanceof SessionManager)) return false; SessionManager manager = (SessionManager) object; return this.deploymentName.equals(manager.getDeploymentName()); } @Override public int hashCode() { return this.deploymentName.hashCode(); } @Override public String toString() { return this.deploymentName; } @Override public SessionManagerStatistics getStatistics() { return this; } public long getCreatedSessionCount() { return createdSessionCount.get(); } @Override public long getMaxActiveSessions() { return maxSize; } @Override public long getHighestSessionCount() { return highestSessionCount.get(); } @Override public long getActiveSessionCount() { return sessions.size(); } @Override public long getExpiredSessionCount() { return expiredSessionCount; } @Override public long getRejectedSessions() { return rejectedSessionCount.get(); } @Override public long getMaxSessionAliveTime() { return longestSessionLifetime; } @Override public synchronized long getAverageSessionAliveTime() { //this method needs to be synchronised to make sure the session count and the total are in sync if(expiredSessionCount == 0) { return 0; } return new BigDecimal(totalSessionLifetime).divide(BigDecimal.valueOf(expiredSessionCount), MathContext.DECIMAL128).longValue(); } @Override public long getStartTime() { return startTime; } /** * session implementation for the in memory session manager */ private static class SessionImpl implements Session { final AttachmentKey FIRST_REQUEST_ACCESS = AttachmentKey.create(Long.class); final InMemorySessionManager sessionManager; final ConcurrentMap attributes = new ConcurrentHashMap<>(); volatile long lastAccessed; final long creationTime; volatile int maxInactiveInterval; static volatile AtomicReferenceFieldUpdater evictionTokenUpdater; static { //this is needed in case there is unprivileged code on the stack //it needs to delegate to the createTokenUpdater() method otherwise the creation will fail //as the inner class cannot access the member evictionTokenUpdater = AccessController.doPrivileged(new PrivilegedAction>() { @Override public AtomicReferenceFieldUpdater run() { return createTokenUpdater(); } }); } private static AtomicReferenceFieldUpdater createTokenUpdater() { return AtomicReferenceFieldUpdater.newUpdater(SessionImpl.class, Object.class, "evictionToken"); } private volatile String sessionId; private volatile Object evictionToken; private final SessionConfig sessionCookieConfig; private volatile long expireTime = -1; private volatile boolean invalid = false; private volatile boolean invalidationStarted = false; final XnioIoThread executor; final XnioWorker worker; XnioExecutor.Key timerCancelKey; Runnable cancelTask = new Runnable() { @Override public void run() { worker.execute(new Runnable() { @Override public void run() { long currentTime = System.currentTimeMillis(); if(currentTime >= expireTime) { invalidate(null, SessionListener.SessionDestroyedReason.TIMEOUT); } else { timerCancelKey = WorkerUtils.executeAfter(executor, cancelTask, expireTime - currentTime, TimeUnit.MILLISECONDS); } } }); } }; private SessionImpl(final InMemorySessionManager sessionManager, final SessionConfig sessionCookieConfig, final XnioIoThread executor, final XnioWorker worker, final int maxInactiveInterval) { this.sessionManager = sessionManager; this.sessionCookieConfig = sessionCookieConfig; this.executor = executor; this.worker = worker; creationTime = lastAccessed = System.currentTimeMillis(); this.setMaxInactiveInterval(maxInactiveInterval); } synchronized void bumpTimeout() { if(invalidationStarted) { return; } final long maxInactiveInterval = getMaxInactiveIntervalMilis(); if (maxInactiveInterval > 0) { long newExpireTime = System.currentTimeMillis() + maxInactiveInterval; if(timerCancelKey != null && (newExpireTime < expireTime)) { // We have to re-schedule as the new maxInactiveInterval is lower than the old one if (!timerCancelKey.remove()) { return; } timerCancelKey = null; } expireTime = newExpireTime; UndertowLogger.SESSION_LOGGER.tracef("Bumping timeout for session %s to %s", sessionId, expireTime); if(timerCancelKey == null) { //+1, to make sure that the time has actually expired //we don't re-schedule every time, as it is expensive //instead when it expires we check if the timeout has been bumped, and if so we re-schedule timerCancelKey = executor.executeAfter(cancelTask, maxInactiveInterval + 1L, TimeUnit.MILLISECONDS); } } else { expireTime = -1; if(timerCancelKey != null) { timerCancelKey.remove(); timerCancelKey = null; } } } private void setEvictionToken(Object evictionToken) { this.evictionToken = evictionToken; if (evictionToken != null) { Object token = evictionToken; if (evictionTokenUpdater.compareAndSet(this, token, null)) { sessionManager.evictionQueue.removeToken(token); this.evictionToken = sessionManager.evictionQueue.offerLastAndReturnToken(sessionId); } } } private void setId(final String sessionId) { this.sessionId = sessionId; } @Override public String getId() { return sessionId; } void requestStarted(HttpServerExchange serverExchange) { Long existing = serverExchange.getAttachment(FIRST_REQUEST_ACCESS); if(existing == null) { if (!invalid) { serverExchange.putAttachment(FIRST_REQUEST_ACCESS, System.currentTimeMillis()); } } bumpTimeout(); } @Override public void requestDone(final HttpServerExchange serverExchange) { Long existing = serverExchange.getAttachment(FIRST_REQUEST_ACCESS); if(existing != null) { lastAccessed = existing; } bumpTimeout(); } @Override public long getCreationTime() { if (invalid) { throw UndertowMessages.MESSAGES.sessionIsInvalid(sessionId); } return creationTime; } @Override public long getLastAccessedTime() { if (invalid) { throw UndertowMessages.MESSAGES.sessionIsInvalid(sessionId); } return lastAccessed; } @Override public void setMaxInactiveInterval(final int interval) { if (invalid) { throw UndertowMessages.MESSAGES.sessionIsInvalid(sessionId); } UndertowLogger.SESSION_LOGGER.debugf("Setting max inactive interval for %s to %s", sessionId, interval); this.maxInactiveInterval = interval; this.bumpTimeout(); } @Override public int getMaxInactiveInterval() { if (invalid) { throw UndertowMessages.MESSAGES.sessionIsInvalid(sessionId); } return maxInactiveInterval; } private long getMaxInactiveIntervalMilis() { if (invalid) { throw UndertowMessages.MESSAGES.sessionIsInvalid(sessionId); } return this.maxInactiveInterval*1000L; } @Override public Object getAttribute(final String name) { if (invalid) { throw UndertowMessages.MESSAGES.sessionIsInvalid(sessionId); } return attributes.get(name); } @Override public Set getAttributeNames() { if (invalid) { throw UndertowMessages.MESSAGES.sessionIsInvalid(sessionId); } return attributes.keySet(); } @Override public Object setAttribute(final String name, final Object value) { if (value == null) { return removeAttribute(name); } if (invalid) { throw UndertowMessages.MESSAGES.sessionIsInvalid(sessionId); } final Object existing = attributes.put(name, value); if (existing == null) { sessionManager.sessionListeners.attributeAdded(this, name, value); } else { sessionManager.sessionListeners.attributeUpdated(this, name, value, existing); } UndertowLogger.SESSION_LOGGER.tracef("Setting session attribute %s to %s for session %s", name, value, sessionId); return existing; } @Override public Object removeAttribute(final String name) { if (invalid) { throw UndertowMessages.MESSAGES.sessionIsInvalid(sessionId); } final Object existing = attributes.remove(name); sessionManager.sessionListeners.attributeRemoved(this, name, existing); UndertowLogger.SESSION_LOGGER.tracef("Removing session attribute %s for session %s", name, sessionId); return existing; } @Override public void invalidate(final HttpServerExchange exchange) { invalidate(exchange, SessionListener.SessionDestroyedReason.INVALIDATED); if (exchange != null) { exchange.removeAttachment(sessionManager.NEW_SESSION); } Object evictionToken = this.evictionToken; if(evictionToken != null) { sessionManager.evictionQueue.removeToken(evictionToken); } } void invalidate(final HttpServerExchange exchange, SessionListener.SessionDestroyedReason reason) { synchronized(SessionImpl.this) { if (timerCancelKey != null) { timerCancelKey.remove(); } SessionImpl sess = sessionManager.sessions.remove(sessionId); if (sess == null) { if (reason == SessionListener.SessionDestroyedReason.INVALIDATED) { throw UndertowMessages.MESSAGES.sessionAlreadyInvalidated(); } return; } invalidationStarted = true; } UndertowLogger.SESSION_LOGGER.debugf("Invalidating session %s for exchange %s", sessionId, exchange); sessionManager.sessionListeners.sessionDestroyed(this, exchange, reason); invalid = true; if(sessionManager.statisticsEnabled) { long life = System.currentTimeMillis() - creationTime; synchronized (sessionManager) { sessionManager.expiredSessionCount++; sessionManager.totalSessionLifetime = sessionManager.totalSessionLifetime.add(BigInteger.valueOf(life)); if(sessionManager.longestSessionLifetime < life) { sessionManager.longestSessionLifetime = life; } } } if (exchange != null) { sessionCookieConfig.clearSession(exchange, this.getId()); } } @Override public SessionManager getSessionManager() { return sessionManager; } @Override public String changeSessionId(final HttpServerExchange exchange, final SessionConfig config) { final String oldId = sessionId; String newId = sessionManager.createAndSaveNewID(this); this.sessionId = newId; if(!invalid) { config.setSessionId(exchange, this.getId()); } sessionManager.sessions.remove(oldId); sessionManager.sessionListeners.sessionIdChanged(this, oldId); UndertowLogger.SESSION_LOGGER.debugf("Changing session id %s to %s", oldId, newId); return newId; } private synchronized void destroy() { if (timerCancelKey != null) { timerCancelKey.remove(); } cancelTask = null; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/session/PathParameterSessionConfig.java000066400000000000000000000112701420065311100334200ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.session; import java.util.Deque; import java.util.Locale; import io.undertow.UndertowLogger; import io.undertow.server.HttpServerExchange; /** * Session config that is based on a path parameter and URL rewriting * * @author Stuart Douglas */ public class PathParameterSessionConfig implements SessionConfig { private final String name; public PathParameterSessionConfig(final String name) { this.name = name; } public PathParameterSessionConfig() { this(SessionCookieConfig.DEFAULT_SESSION_ID.toLowerCase(Locale.ENGLISH)); } @Override public void setSessionId(final HttpServerExchange exchange, final String sessionId) { exchange.getPathParameters().remove(name); exchange.addPathParam(name, sessionId); UndertowLogger.SESSION_LOGGER.tracef("Setting path parameter session id %s on %s", sessionId, exchange); } @Override public void clearSession(final HttpServerExchange exchange, final String sessionId) { UndertowLogger.SESSION_LOGGER.tracef("Clearing path parameter session id %s on %s", sessionId, exchange); exchange.getPathParameters().remove(name); } @Override public String findSessionId(final HttpServerExchange exchange) { Deque stringDeque = exchange.getPathParameters().get(name); if (stringDeque == null) { return null; } UndertowLogger.SESSION_LOGGER.tracef("Found path parameter session id %s on %s", stringDeque.getFirst(), exchange); return stringDeque.getFirst(); } @Override public SessionCookieSource sessionCookieSource(HttpServerExchange exchange) { return findSessionId(exchange) != null ? SessionCookieSource.URL : SessionCookieSource.NONE; } /** * Return the specified URL with the specified session identifier * suitably encoded. * * @param url URL to be encoded with the session id * @param sessionId Session id to be included in the encoded URL */ @Override public String rewriteUrl(final String url, final String sessionId) { if ((url == null) || (sessionId == null)) return (url); String path = url; String query = ""; String anchor = ""; final int question = url.indexOf('?'); if (question >= 0) { path = url.substring(0, question); query = url.substring(question); } final int pound = path.indexOf('#'); if (pound >= 0) { anchor = path.substring(pound); path = path.substring(0, pound); } final StringBuilder sb = new StringBuilder(); // look for param final int paramIndex = path.indexOf(";" + name); // found param, strip it off from path if (paramIndex >= 0) { sb.append(path.substring(0, paramIndex)); final String remainder = path.substring(paramIndex + name.length() + 1); final int endIndex1 = remainder.indexOf(";"); final int endIndex2 = remainder.indexOf("/"); if (endIndex1 != -1) { if (endIndex2 != -1 && endIndex2 < endIndex1) { sb.append(remainder.substring(endIndex2)); } else { sb.append(remainder.substring(endIndex1)); } } else if (endIndex2 != -1) { sb.append(remainder.substring(endIndex2)); } // else the rest of the path will be discarded, as it contains just the parameter we want to exclude } else { // name param was not found, we can use the path as is sb.append(path); } // append ;name=sessionId sb.append(';'); sb.append(name); sb.append('='); sb.append(sessionId); // apend anchor and query sb.append(anchor); sb.append(query); UndertowLogger.SESSION_LOGGER.tracef("Rewrote URL from %s to %s", url, sb); return sb.toString(); } } SecureRandomSessionIdGenerator.java000066400000000000000000000067011420065311100341740ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/server/session/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.session; import java.security.SecureRandom; /** * A {@link SessionIdGenerator} that uses a secure random to generate a * session ID. * * On some systems this may perform poorly if not enough entropy is available, * depending on the algorithm in use. * * * @author Stuart Douglas */ public class SecureRandomSessionIdGenerator implements SessionIdGenerator { private final SecureRandom random = new SecureRandom(); private volatile int length = 30; private static final char[] SESSION_ID_ALPHABET; private static final String ALPHABET_PROPERTY = "io.undertow.server.session.SecureRandomSessionIdGenerator.ALPHABET"; static { String alphabet = System.getProperty(ALPHABET_PROPERTY, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"); if(alphabet.length() != 64) { throw new RuntimeException("io.undertow.server.session.SecureRandomSessionIdGenerator must be exactly 64 characters long"); } SESSION_ID_ALPHABET = alphabet.toCharArray(); } @Override public String createSessionId() { final byte[] bytes = new byte[length]; random.nextBytes(bytes); return new String(encode(bytes)); } public int getLength() { return length; } public void setLength(final int length) { this.length = length; } /** * Encode the bytes into a String with a slightly modified Base64-algorithm * This code was written by Kevin Kelley * and adapted by Thomas Peuss * * @param data The bytes you want to encode * @return the encoded String */ private char[] encode(byte[] data) { char[] out = new char[((data.length + 2) / 3) * 4]; char[] alphabet = SESSION_ID_ALPHABET; // // 3 bytes encode to 4 chars. Output is always an even // multiple of 4 characters. // for (int i = 0, index = 0; i < data.length; i += 3, index += 4) { boolean quad = false; boolean trip = false; int val = (0xFF & (int) data[i]); val <<= 8; if ((i + 1) < data.length) { val |= (0xFF & (int) data[i + 1]); trip = true; } val <<= 8; if ((i + 2) < data.length) { val |= (0xFF & (int) data[i + 2]); quad = true; } out[index + 3] = alphabet[(quad ? (val & 0x3F) : 63)]; val >>= 6; out[index + 2] = alphabet[(trip ? (val & 0x3F) : 63)]; val >>= 6; out[index + 1] = alphabet[val & 0x3F]; val >>= 6; out[index] = alphabet[val & 0x3F]; } return out; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/session/Session.java000066400000000000000000000151051420065311100276150ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.session; import java.util.Set; import io.undertow.server.HttpServerExchange; /** * Represents a HTTP session. *

* Many operations provide both a blocking and an asynchronous version. *

* When using the async versions of operations no guarantee is made as to which threads will * run listeners registered with this session manger. When using the blocking version the listeners are guaranteed * to run in the calling thread. * * @author Stuart Douglas */ public interface Session { /** * Returns a string containing the unique identifier assigned * to this session. The identifier is assigned * by the servlet container and is implementation dependent. * * @return a string specifying the identifier * assigned to this session */ String getId(); /** * Called when a request is done with the session. * * @param serverExchange The http server exchange for this request */ void requestDone(final HttpServerExchange serverExchange); /** * Returns the time when this session was created, measured * in milliseconds since midnight January 1, 1970 GMT. * * @return a long specifying * when this session was created, * expressed in * milliseconds since 1/1/1970 GMT * @throws IllegalStateException if this method is called on an * invalidated session */ long getCreationTime(); /** * Returns the last time the client sent a request associated with * this session, as the number of milliseconds since midnight * January 1, 1970 GMT, and marked by the time the container received the request. *

*

Actions that your application takes, such as getting or setting * a value associated with the session, do not affect the access * time. * * @return a long * representing the last time * the client sent a request associated * with this session, expressed in * milliseconds since 1/1/1970 GMT * @throws IllegalStateException if this method is called on an * invalidated session */ long getLastAccessedTime(); /** * Specifies the time, in seconds, between client requests before the * servlet container will invalidate this session. A negative time * indicates the session should never timeout. * * @param interval An integer specifying the number * of seconds */ void setMaxInactiveInterval(int interval); /** * Returns the maximum time interval, in seconds, that * the servlet container will keep this session open between * client accesses. After this interval, the servlet container * will invalidate the session. The maximum time interval can be set * with the setMaxInactiveInterval method. * A negative time indicates the session should never timeout. * * @return an integer specifying the number of * seconds this session remains open * between client requests * @see #setMaxInactiveInterval */ int getMaxInactiveInterval(); /** * Returns the object bound with the specified name in this session, or * null if no object is bound under the name. * * @param name a string specifying the name of the object * @return the object with the specified name * @throws IllegalStateException if this method is called on an * invalidated session */ Object getAttribute(String name); /** * Returns an Set of String objects * containing the names of all the objects bound to this session. * * @return an Set of * String objects specifying the * names of all the objects bound to * this session * @throws IllegalStateException if this method is called on an * invalidated session */ Set getAttributeNames(); /** * Binds an object to this session, using the name specified. * If an object of the same name is already bound to the session, * the object is replaced. *

*

*

*

If the value passed in is null, this has the same effect as calling * removeAttribute(). * * @param name the name to which the object is bound; * cannot be null * @param value the object to be bound * @return An IOFuture containing the previous value * @throws IllegalStateException if this method is called on an invalidated session */ Object setAttribute(final String name, Object value); /** * Removes the object bound with the specified name from * this session. If the session does not have an object * bound with the specified name, this method does nothing. * * @param name the name of the object to remove from this session * @throws IllegalStateException if this method is called on an * invalidated session */ Object removeAttribute(final String name); /** * Invalidates this session then unbinds any objects bound * to it. * * @throws IllegalStateException if this method is called on an * already invalidated session */ void invalidate(final HttpServerExchange exchange); /** * @return The session manager that is associated with this session */ SessionManager getSessionManager(); /** * Generate a new session id for this session, and return the new id. * * @return The new session ID */ String changeSessionId(final HttpServerExchange exchange, final SessionConfig config); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/session/SessionAttachmentHandler.java000066400000000000000000000101071420065311100331210ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.session; import io.undertow.Handlers; import io.undertow.UndertowMessages; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.ResponseCodeHandler; /** * Handler that attaches the session to the request. *

* This handler is also the place where session cookie configuration properties are configured. *

* note: this approach is not used by Servlet, which has its own session handlers * * @author Stuart Douglas */ public class SessionAttachmentHandler implements HttpHandler { private volatile HttpHandler next = ResponseCodeHandler.HANDLE_404; private volatile SessionManager sessionManager; private final SessionConfig sessionConfig; public SessionAttachmentHandler(final SessionManager sessionManager, final SessionConfig sessionConfig) { this.sessionConfig = sessionConfig; if (sessionManager == null) { throw UndertowMessages.MESSAGES.sessionManagerMustNotBeNull(); } this.sessionManager = sessionManager; } public SessionAttachmentHandler(final HttpHandler next, final SessionManager sessionManager, final SessionConfig sessionConfig) { this.sessionConfig = sessionConfig; if (sessionManager == null) { throw UndertowMessages.MESSAGES.sessionManagerMustNotBeNull(); } this.next = next; this.sessionManager = sessionManager; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.putAttachment(SessionManager.ATTACHMENT_KEY, sessionManager); exchange.putAttachment(SessionConfig.ATTACHMENT_KEY, sessionConfig); final UpdateLastAccessTimeListener handler = new UpdateLastAccessTimeListener(sessionConfig, sessionManager); exchange.addExchangeCompleteListener(handler); next.handleRequest(exchange); } public HttpHandler getNext() { return next; } public SessionAttachmentHandler setNext(final HttpHandler next) { Handlers.handlerNotNull(next); this.next = next; return this; } public SessionManager getSessionManager() { return sessionManager; } public SessionAttachmentHandler setSessionManager(final SessionManager sessionManager) { if (sessionManager == null) { throw UndertowMessages.MESSAGES.sessionManagerMustNotBeNull(); } this.sessionManager = sessionManager; return this; } private static class UpdateLastAccessTimeListener implements ExchangeCompletionListener { private final SessionConfig sessionConfig; private final SessionManager sessionManager; private UpdateLastAccessTimeListener(final SessionConfig sessionConfig, final SessionManager sessionManager) { this.sessionConfig = sessionConfig; this.sessionManager = sessionManager; } @Override public void exchangeEvent(final HttpServerExchange exchange, final NextListener next) { try { final Session session = sessionManager.getSession(exchange, sessionConfig); if (session != null) { session.requestDone(exchange); } } finally { next.proceed(); } } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/session/SessionConfig.java000066400000000000000000000052721420065311100307470ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.session; import io.undertow.server.HttpServerExchange; import io.undertow.util.AttachmentKey; /** * Interface that abstracts the process of attaching a session to an exchange. This includes both the HTTP side of * attachment such as setting a cookie, as well as actually attaching the session to the exchange for use by later * handlers. * *

* Generally this will just set a cookie. * * @author Stuart Douglas */ public interface SessionConfig { AttachmentKey ATTACHMENT_KEY = AttachmentKey.create(SessionConfig.class); /** * Attaches the session to the exchange. The method should attach the exchange under an attachment key, * and should also modify the exchange to allow the session to be re-attached on the next request. *

* Generally this will involve setting a cookie *

* Once a session has been attached it must be possible to retrieve it via * {@link #findSessionId(io.undertow.server.HttpServerExchange)} * * * @param exchange The exchange * @param sessionId The session */ void setSessionId(final HttpServerExchange exchange, final String sessionId); /** * Clears this session from the exchange, removing the attachment and making any changes to the response necessary, * such as clearing cookies. * * @param exchange The exchange * @param sessionId The session id */ void clearSession(final HttpServerExchange exchange, final String sessionId); /** * Retrieves a session id of an existing session from an exchange. * * @param exchange The exchange * @return The session id, or null */ String findSessionId(final HttpServerExchange exchange); SessionCookieSource sessionCookieSource(final HttpServerExchange exchange); String rewriteUrl(final String originalUrl, final String sessionId); enum SessionCookieSource { URL, COOKIE, SSL, OTHER, NONE } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/session/SessionCookieConfig.java000066400000000000000000000114631420065311100321000ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.session; import io.undertow.UndertowLogger; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.Cookie; import io.undertow.server.handlers.CookieImpl; /** * Encapsulation of session cookie configuration. This removes the need for the session manager to * know about cookie configuration. * * @author Stuart Douglas * @author Richard Opalka */ public class SessionCookieConfig implements SessionConfig { public static final String DEFAULT_SESSION_ID = "JSESSIONID"; private String cookieName = DEFAULT_SESSION_ID; private String path = "/"; private String domain; private boolean discard; private boolean secure; private boolean httpOnly; private int maxAge = -1; private String comment; @Override public String rewriteUrl(final String originalUrl, final String sessionId) { return originalUrl; } @Override public void setSessionId(final HttpServerExchange exchange, final String sessionId) { Cookie cookie = new CookieImpl(cookieName, sessionId) .setPath(path) .setDomain(domain) .setDiscard(discard) .setSecure(secure) .setHttpOnly(httpOnly) .setComment(comment); if (maxAge > 0) { cookie.setMaxAge(maxAge); } exchange.setResponseCookie(cookie); UndertowLogger.SESSION_LOGGER.tracef("Setting session cookie session id %s on %s", sessionId, exchange); } @Override public void clearSession(final HttpServerExchange exchange, final String sessionId) { Cookie cookie = new CookieImpl(cookieName, sessionId) .setPath(path) .setDomain(domain) .setDiscard(discard) .setSecure(secure) .setHttpOnly(httpOnly) .setMaxAge(0); exchange.setResponseCookie(cookie); UndertowLogger.SESSION_LOGGER.tracef("Clearing session cookie session id %s on %s", sessionId, exchange); } @Override public String findSessionId(final HttpServerExchange exchange) { final Cookie cookie = exchange.getRequestCookie(cookieName); if (cookie != null) { UndertowLogger.SESSION_LOGGER.tracef("Found session cookie session id %s on %s", cookie, exchange); return cookie.getValue(); } return null; } @Override public SessionCookieSource sessionCookieSource(HttpServerExchange exchange) { return findSessionId(exchange) != null ? SessionCookieSource.COOKIE : SessionCookieSource.NONE; } public String getCookieName() { return cookieName; } public SessionCookieConfig setCookieName(final String cookieName) { this.cookieName = cookieName; return this; } public String getPath() { return path; } public SessionCookieConfig setPath(final String path) { this.path = path; return this; } public String getDomain() { return domain; } public SessionCookieConfig setDomain(final String domain) { this.domain = domain; return this; } public boolean isDiscard() { return discard; } public SessionCookieConfig setDiscard(final boolean discard) { this.discard = discard; return this; } public boolean isSecure() { return secure; } public SessionCookieConfig setSecure(final boolean secure) { this.secure = secure; return this; } public boolean isHttpOnly() { return httpOnly; } public SessionCookieConfig setHttpOnly(final boolean httpOnly) { this.httpOnly = httpOnly; return this; } public int getMaxAge() { return maxAge; } public SessionCookieConfig setMaxAge(final int maxAge) { this.maxAge = maxAge; return this; } public String getComment() { return comment; } public SessionCookieConfig setComment(final String comment) { this.comment = comment; return this; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/session/SessionIdGenerator.java000066400000000000000000000017731420065311100317470ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.session; /** * * Strategy for generating session ID's. * * The session manager is not required to support pluggable session * id generation, it is an optional feature. * * @author Stuart Douglas */ public interface SessionIdGenerator { String createSessionId(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/session/SessionListener.java000066400000000000000000000041141420065311100313210ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.session; import io.undertow.server.HttpServerExchange; /** * * A listener for session events. * * * @author Stuart Douglas */ public interface SessionListener { /** * Called when a session is created * @param session The new session * @param exchange The {@link HttpServerExchange} that created the session */ default void sessionCreated(final Session session, final HttpServerExchange exchange) { } /** * Called when a session is destroyed * @param session The new session * @param exchange The {@link HttpServerExchange} that destroyed the session, or null if the session timed out * @param reason The reason why the session was expired */ default void sessionDestroyed(final Session session, final HttpServerExchange exchange, SessionDestroyedReason reason) { } default void attributeAdded(final Session session, final String name, final Object value) { } default void attributeUpdated(final Session session, final String name, final Object newValue, final Object oldValue) { } default void attributeRemoved(final Session session, final String name,final Object oldValue) { } default void sessionIdChanged(final Session session, final String oldSessionId) { } enum SessionDestroyedReason { INVALIDATED, TIMEOUT, UNDEPLOY, } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/session/SessionListeners.java000066400000000000000000000062211420065311100315050ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.session; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import java.util.concurrent.CopyOnWriteArrayList; import io.undertow.server.HttpServerExchange; /** * Utility class that maintains the session listeners. * * * @author Stuart Douglas */ public class SessionListeners { private final List sessionListeners = new CopyOnWriteArrayList<>(); public void addSessionListener(final SessionListener listener) { this.sessionListeners.add(listener); } public boolean removeSessionListener(final SessionListener listener) { return this.sessionListeners.remove(listener); } public void clear() { this.sessionListeners.clear(); } public void sessionCreated(final Session session, final HttpServerExchange exchange) { for (SessionListener listener : sessionListeners) { listener.sessionCreated(session, exchange); } } public void sessionDestroyed(final Session session, final HttpServerExchange exchange, SessionListener.SessionDestroyedReason reason) { // We need to create our own snapshot to safely iterate over a concurrent list in reverse List listeners = new ArrayList<>(sessionListeners); ListIterator iterator = listeners.listIterator(listeners.size()); while (iterator.hasPrevious()) { iterator.previous().sessionDestroyed(session, exchange, reason); } } public void attributeAdded(final Session session, final String name, final Object value) { for (SessionListener listener : sessionListeners) { listener.attributeAdded(session, name, value); } } public void attributeUpdated(final Session session, final String name, final Object newValue, final Object oldValue) { for (SessionListener listener : sessionListeners) { listener.attributeUpdated(session, name, newValue, oldValue); } } public void attributeRemoved(final Session session, final String name, final Object oldValue) { for (SessionListener listener : sessionListeners) { listener.attributeRemoved(session, name, oldValue); } } public void sessionIdChanged(final Session session, final String oldSessionId) { for (SessionListener listener : sessionListeners) { listener.sessionIdChanged(session, oldSessionId); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/session/SessionManager.java000066400000000000000000000111351420065311100311070ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.session; import io.undertow.server.HttpServerExchange; import io.undertow.util.AttachmentKey; import java.util.Set; /** * Interface that manages sessions. *

* The session manager is responsible for maintaining session state. *

* As part of session creation the session manager MUST attempt to retrieve the {@link SessionCookieConfig} from * the {@link HttpServerExchange} and use it to set the session cookie. The frees up the session manager from * needing to know details of the cookie configuration. When invalidating a session the session manager MUST * also use this to clear the session cookie. * * @author Stuart Douglas */ public interface SessionManager { AttachmentKey ATTACHMENT_KEY = AttachmentKey.create(SessionManager.class); /** * Uniquely identifies this session manager * @return a unique identifier */ String getDeploymentName(); /** * Starts the session manager */ void start(); /** * stops the session manager */ void stop(); /** * Creates a new session. Any {@link SessionListener}s registered with this manager will be notified * of the session creation. *

* This method *MUST* call {@link SessionConfig#findSessionId(io.undertow.server.HttpServerExchange)} (io.undertow.server.HttpServerExchange)} first to * determine if an existing session ID is present in the exchange. If this id is present then it must be used * as the new session ID. If a session with this ID already exists then an {@link IllegalStateException} must be * thrown. *

*

* This requirement exists to allow forwards across servlet contexts to work correctly. * * The session manager is responsible for making sure that a newly created session is accessible to later calls to * {@link #getSession(io.undertow.server.HttpServerExchange, SessionConfig)} from the same request. It is recommended * that a non static attachment key be used to store the newly created session as an attachment. The attachment key * must be static to prevent different session managers from interfering with each other. * * @return The created session */ Session createSession(final HttpServerExchange serverExchange, final SessionConfig sessionCookieConfig); /** * @return An IoFuture that can be used to retrieve the session, or an IoFuture that will return null if not found */ Session getSession(final HttpServerExchange serverExchange, final SessionConfig sessionCookieConfig); /** * Retrieves a session with the given session id * * @param sessionId The session ID * @return The session, or null if it does not exist */ Session getSession(final String sessionId); /** * Registers a session listener for the session manager * * @param listener The listener */ void registerSessionListener(final SessionListener listener); /** * Removes a session listener from the session manager * * @param listener the listener */ void removeSessionListener(final SessionListener listener); /** * Sets the default session timeout * * @param timeout the timeout */ void setDefaultSessionTimeout(final int timeout); /** * Returns the identifiers of those sessions that would be lost upon * shutdown of this node */ Set getTransientSessions(); /** * Returns the identifiers of those sessions that are active on this * node, excluding passivated sessions */ Set getActiveSessions(); /** * Returns the identifiers of all sessions, including both active and * passive */ Set getAllSessions(); /** * Returns the statistics for this session manager, or null, if statistics are not supported. */ SessionManagerStatistics getStatistics(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/session/SessionManagerStatistics.java000066400000000000000000000041611420065311100331630ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.session; /** * Optional interface that can be implemented by {@link io.undertow.server.session.SessionManager} * implementations that provides session manager statistics. * * @author Stuart Douglas */ public interface SessionManagerStatistics { /** * * @return The number of sessions that this session manager has created */ long getCreatedSessionCount(); /** * * @return the maximum number of sessions this session manager supports */ long getMaxActiveSessions(); /** * * @return the highest number of sessions that have been active at a single time, or -1 if this statistic is not supported */ default long getHighestSessionCount() { return -1; } /** * * @return The number of active sessions */ long getActiveSessionCount(); /** * * @return The number of expired sessions */ long getExpiredSessionCount(); /** * * @return The number of rejected sessions */ long getRejectedSessions(); /** * * @return The longest a session has been alive for in milliseconds */ long getMaxSessionAliveTime(); /** * * @return The average session lifetime in milliseconds */ long getAverageSessionAliveTime(); /** * * @return The timestamp at which the session manager started */ long getStartTime(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/server/session/SslSessionConfig.java000066400000000000000000000135661420065311100314360ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.session; import io.undertow.UndertowLogger; import io.undertow.server.HttpServerExchange; import io.undertow.server.SSLSessionInfo; import java.util.Arrays; import java.util.HashMap; import java.util.Map; /** * Session config that stores the session ID in the current SSL session. *

* It allows for a fallback to be provided for non-ssl connections * * @author Stuart Douglas */ public class SslSessionConfig implements SessionConfig { private final SessionConfig fallbackSessionConfig; private final Map sessions = new HashMap<>(); private final Map reverse = new HashMap<>(); public SslSessionConfig(final SessionConfig fallbackSessionConfig, SessionManager sessionManager) { this.fallbackSessionConfig = fallbackSessionConfig; sessionManager.registerSessionListener(new SessionListener() { @Override public void sessionCreated(Session session, HttpServerExchange exchange) { } @Override public void sessionDestroyed(Session session, HttpServerExchange exchange, SessionDestroyedReason reason) { synchronized (SslSessionConfig.this) { Key sid = reverse.remove(session.getId()); if (sid != null) { sessions.remove(sid); } } } @Override public void attributeAdded(Session session, String name, Object value) { } @Override public void attributeUpdated(Session session, String name, Object newValue, Object oldValue) { } @Override public void attributeRemoved(Session session, String name, Object oldValue) { } @Override public void sessionIdChanged(Session session, String oldSessionId) { synchronized (SslSessionConfig.this) { Key sid = reverse.remove(session.getId()); if (sid != null) { sessions.remove(sid); } } } }); } public SslSessionConfig(SessionManager sessionManager) { this(null, sessionManager); } @Override public void setSessionId(final HttpServerExchange exchange, final String sessionId) { UndertowLogger.SESSION_LOGGER.tracef("Setting SSL session id %s on %s", sessionId, exchange); SSLSessionInfo sslSession = exchange.getConnection().getSslSessionInfo(); if (sslSession == null) { if (fallbackSessionConfig != null) { fallbackSessionConfig.setSessionId(exchange, sessionId); } } else { Key key = new Key(sslSession.getSessionId()); synchronized (this) { sessions.put(key, sessionId); reverse.put(sessionId, key); } } } @Override public void clearSession(final HttpServerExchange exchange, final String sessionId) { UndertowLogger.SESSION_LOGGER.tracef("Clearing SSL session id %s on %s", sessionId, exchange); SSLSessionInfo sslSession = exchange.getConnection().getSslSessionInfo(); if (sslSession == null) { if (fallbackSessionConfig != null) { fallbackSessionConfig.clearSession(exchange, sessionId); } } else { synchronized (this) { Key sid = reverse.remove(sessionId); if (sid != null) { sessions.remove(sid); } } } } @Override public String findSessionId(final HttpServerExchange exchange) { SSLSessionInfo sslSession = exchange.getConnection().getSslSessionInfo(); if (sslSession == null) { if (fallbackSessionConfig != null) { return fallbackSessionConfig.findSessionId(exchange); } } else { synchronized (this) { String sessionId = sessions.get(new Key(sslSession.getSessionId())); if(sessionId != null) { UndertowLogger.SESSION_LOGGER.tracef("Found SSL session id %s on %s", sessionId, exchange); } return sessionId; } } return null; } @Override public SessionCookieSource sessionCookieSource(HttpServerExchange exchange) { return findSessionId(exchange) != null ? SessionCookieSource.SSL : SessionCookieSource.NONE; } @Override public String rewriteUrl(final String originalUrl, final String sessionId) { return originalUrl; } private static final class Key { private final byte[] id; private Key(byte[] id) { this.id = id; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Key key = (Key) o; if (!Arrays.equals(id, key.id)) return false; return true; } @Override public int hashCode() { return id != null ? Arrays.hashCode(id) : 0; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/000077500000000000000000000000001420065311100233115ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/util/AbstractAttachable.java000066400000000000000000000063471420065311100277020ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.util.Collections; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import io.undertow.UndertowMessages; /** * A thing which can have named attachments. * * @author David M. Lloyd */ public abstract class AbstractAttachable implements Attachable { private Map, Object> attachments; /** * {@inheritDoc} */ @Override public T getAttachment(final AttachmentKey key) { if (key == null || attachments == null) { return null; } return (T) attachments.get(key); } /** * {@inheritDoc} */ @Override public List getAttachmentList(AttachmentKey> key) { if (key == null || attachments == null) { return Collections.emptyList(); } List list = (List) attachments.get(key); if (list == null) { return Collections.emptyList(); } return list; } /** * {@inheritDoc} */ @Override public T putAttachment(final AttachmentKey key, final T value) { if (key == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("key"); } if(attachments == null) { attachments = createAttachmentMap(); } return (T) attachments.put(key, value); } protected Map, Object> createAttachmentMap() { return new IdentityHashMap<>(5); } /** * {@inheritDoc} */ @Override public T removeAttachment(final AttachmentKey key) { if (key == null || attachments == null) { return null; } return (T) attachments.remove(key); } /** * {@inheritDoc} */ @Override public void addToAttachmentList(final AttachmentKey> key, final T value) { if (key != null) { if(attachments == null) { attachments = createAttachmentMap(); } final Map, Object> attachments = this.attachments; final AttachmentList list = (AttachmentList) attachments.get(key); if (list == null) { final AttachmentList newList = new AttachmentList<>(((ListAttachmentKey) key).getValueClass()); attachments.put(key, newList); newList.add(value); } else { list.add(value); } } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/Attachable.java000066400000000000000000000050001420065311100261770ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.util.List; /** * A thing which can have named attachments. * * @author David M. Lloyd */ public interface Attachable { /** * Get an attachment value. If no attachment exists for this key, {@code null} is returned. * * @param key the attachment key * @param the value type * @return the value, or {@code null} if there is none */ T getAttachment(AttachmentKey key); /** * Gets a list attachment value. If not attachment exists for this key an empty list is returned * * @param the value type * @param key the attachment key * @return the value, or an empty list if there is none */ List getAttachmentList(AttachmentKey> key); /** * Set an attachment value. If an attachment for this key was already set, return the original value. If the value being set * is {@code null}, the attachment key is removed. * * @param key the attachment key * @param value the new value * @param the value type * @return the old value, or {@code null} if there was none */ T putAttachment(AttachmentKey key, T value); /** * Remove an attachment, returning its previous value. * * @param key the attachment key * @param the value type * @return the old value, or {@code null} if there was none */ T removeAttachment(AttachmentKey key); /** * Add a value to a list-typed attachment key. If the key is not mapped, add such a mapping. * * @param key the attachment key * @param value the value to add * @param the list value type */ void addToAttachmentList(AttachmentKey> key, T value); } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/AttachmentKey.java000066400000000000000000000050441420065311100267200ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; /** * @author Stuart Douglas */ /** * An immutable, type-safe object attachment key. Such a key has no value outside of its object identity. * * @param the attachment type */ public abstract class AttachmentKey { AttachmentKey() { } /** * Cast the value to the type of this attachment key. * * @param value the value * @return the cast value */ public abstract T cast(Object value); /** * Construct a new simple attachment key. * * @param valueClass the value class * @param the attachment type * @return the new instance */ public static AttachmentKey create(final Class valueClass) { return new SimpleAttachmentKey(valueClass); } /** * Construct a new list attachment key. * * @param valueClass the list value class * @param the list value type * @return the new instance */ @SuppressWarnings("unchecked") public static AttachmentKey> createList(final Class valueClass) { return new ListAttachmentKey(valueClass); } } class ListAttachmentKey extends AttachmentKey> { private final Class valueClass; ListAttachmentKey(final Class valueClass) { this.valueClass = valueClass; } @SuppressWarnings({"unchecked"}) public AttachmentList cast(final Object value) { if (value == null) { return null; } AttachmentList list = (AttachmentList) value; final Class listValueClass = list.getValueClass(); if (listValueClass != valueClass) { throw new ClassCastException(); } return (AttachmentList) list; } Class getValueClass() { return valueClass; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/AttachmentList.java000066400000000000000000000100401420065311100270730ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; /** * @author David M. Lloyd */ public final class AttachmentList implements List, RandomAccess { private final Class valueClass; private final List delegate; public AttachmentList(final int initialCapacity, final Class valueClass) { delegate = Collections.checkedList(new ArrayList(initialCapacity), valueClass); this.valueClass = valueClass; } public AttachmentList(final Class valueClass) { delegate = Collections.checkedList(new ArrayList(), valueClass); this.valueClass = valueClass; } public AttachmentList(final Collection c, final Class valueClass) { delegate = Collections.checkedList(new ArrayList(c.size()), valueClass); delegate.addAll(c); this.valueClass = valueClass; } public Class getValueClass() { return valueClass; } public int size() { return delegate.size(); } public boolean isEmpty() { return delegate.isEmpty(); } public boolean contains(final Object o) { return delegate.contains(o); } public Iterator iterator() { return delegate.iterator(); } public Object[] toArray() { return delegate.toArray(); } public T[] toArray(final T[] a) { return delegate.toArray(a); } public boolean add(final T t) { return delegate.add(t); } public boolean remove(final Object o) { return delegate.remove(o); } public boolean containsAll(final Collection c) { return delegate.containsAll(c); } public boolean addAll(final Collection c) { return delegate.addAll(c); } public boolean addAll(final int index, final Collection c) { return delegate.addAll(index, c); } public boolean removeAll(final Collection c) { return delegate.removeAll(c); } public boolean retainAll(final Collection c) { return delegate.retainAll(c); } public void clear() { delegate.clear(); } public boolean equals(final Object o) { return delegate.equals(o); } public int hashCode() { return delegate.hashCode(); } public T get(final int index) { return delegate.get(index); } public T set(final int index, final T element) { return delegate.set(index, element); } public void add(final int index, final T element) { delegate.add(index, element); } public T remove(final int index) { return delegate.remove(index); } public int indexOf(final Object o) { return delegate.indexOf(o); } public int lastIndexOf(final Object o) { return delegate.lastIndexOf(o); } public ListIterator listIterator() { return delegate.listIterator(); } public ListIterator listIterator(final int index) { return delegate.listIterator(index); } public List subList(final int fromIndex, final int toIndex) { return delegate.subList(fromIndex, toIndex); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/BadRequestException.java000066400000000000000000000022531420065311100300740ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; /** * Exception that is thrown when bad request is detected * * @author Stuart Douglas */ public class BadRequestException extends Exception { public BadRequestException() { } public BadRequestException(String message) { super(message); } public BadRequestException(Throwable cause) { super(cause); } public BadRequestException(String message, Throwable cause) { super(message, cause); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/ByteRange.java000066400000000000000000000170331420065311100260400ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.UndertowLogger; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * Represents a byte range for a range request * * * @author Stuart Douglas */ public class ByteRange { private final List ranges; public ByteRange(List ranges) { this.ranges = ranges; } public int getRanges() { return ranges.size(); } /** * Gets the start of the specified range segment, of -1 if this is a suffix range segment * @param range The range segment to get * @return The range start */ public long getStart(int range) { return ranges.get(range).getStart(); } /** * Gets the end of the specified range segment, or the number of bytes if this is a suffix range segment * @param range The range segment to get * @return The range end */ public long getEnd(int range) { return ranges.get(range).getEnd(); } /** * Attempts to parse a range request. If the range request is invalid it will just return null so that * it may be ignored. * * * @param rangeHeader The range spec * @return A range spec, or null if the range header could not be parsed */ public static ByteRange parse(String rangeHeader) { if(rangeHeader == null || rangeHeader.length() < 7) { return null; } if(!rangeHeader.startsWith("bytes=")) { return null; } List ranges = new ArrayList<>(); String[] parts = rangeHeader.substring(6).split(","); for(String part : parts) { try { int index = part.indexOf('-'); if (index == 0) { //suffix range spec //represents the last N bytes //internally we represent this using a -1 as the start position long val = Long.parseLong(part.substring(1)); if(val < 0) { UndertowLogger.REQUEST_LOGGER.debugf("Invalid range spec %s", rangeHeader); return null; } ranges.add(new Range(-1, val)); } else { if(index == -1) { UndertowLogger.REQUEST_LOGGER.debugf("Invalid range spec %s", rangeHeader); return null; } long start = Long.parseLong(part.substring(0, index)); if(start < 0) { UndertowLogger.REQUEST_LOGGER.debugf("Invalid range spec %s", rangeHeader); return null; } long end; if (index + 1 < part.length()) { end = Long.parseLong(part.substring(index + 1)); } else { end = -1; } ranges.add(new Range(start, end)); } } catch (NumberFormatException e) { UndertowLogger.REQUEST_LOGGER.debugf("Invalid range spec %s", rangeHeader); return null; } } if(ranges.isEmpty()) { return null; } return new ByteRange(ranges); } /** * Returns a representation of the range result. If this returns null then a 200 response should be sent instead * @param resourceContentLength * @return */ public RangeResponseResult getResponseResult(final long resourceContentLength, String ifRange, Date lastModified, String eTag) { if(ranges.isEmpty()) { return null; } long start = getStart(0); long end = getEnd(0); long rangeLength; if(ifRange != null && !ifRange.isEmpty()) { if(ifRange.charAt(0) == '"') { //entity tag if(eTag != null && !eTag.equals(ifRange)) { return null; } } else { Date ifDate = DateUtils.parseDate(ifRange); if(ifDate != null && lastModified != null && ifDate.getTime() < lastModified.getTime()) { return null; } } } if(start == -1 ) { //suffix range if(end < 0){ //ignore the range request return new RangeResponseResult(0, 0, 0, "bytes */" + resourceContentLength, StatusCodes.REQUEST_RANGE_NOT_SATISFIABLE); } start = Math.max(resourceContentLength - end, 0); end = resourceContentLength - 1; rangeLength = resourceContentLength - start; } else if(end == -1) { //prefix range long toWrite = resourceContentLength - start; if (toWrite > 0) { rangeLength = toWrite; } else { //ignore the range request return new RangeResponseResult(0, 0, 0, "bytes */" + resourceContentLength, StatusCodes.REQUEST_RANGE_NOT_SATISFIABLE); } end = resourceContentLength - 1; } else { end = Math.min(end, resourceContentLength - 1); if(start >= resourceContentLength || start > end) { return new RangeResponseResult(0, 0, 0, "bytes */" + resourceContentLength, StatusCodes.REQUEST_RANGE_NOT_SATISFIABLE); } rangeLength = end - start + 1; } return new RangeResponseResult(start, end, rangeLength, "bytes " + start + "-" + end + "/" + resourceContentLength, StatusCodes.PARTIAL_CONTENT); } public static class RangeResponseResult { private final long start; private final long end; private final long contentLength; private final String contentRange; private final int statusCode; public RangeResponseResult(long start, long end, long contentLength, String contentRange, int statusCode) { this.start = start; this.end = end; this.contentLength = contentLength; this.contentRange = contentRange; this.statusCode = statusCode; } public long getStart() { return start; } public long getEnd() { return end; } public long getContentLength() { return contentLength; } public String getContentRange() { return contentRange; } public int getStatusCode() { return statusCode; } } public static class Range { private final long start, end; public Range(long start, long end) { this.start = start; this.end = end; } public long getStart() { return start; } public long getEnd() { return end; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/CanonicalPathUtils.java000066400000000000000000000161701420065311100277060ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.util.ArrayList; import java.util.List; /** * @author Stuart Douglas */ public class CanonicalPathUtils { /** * System property the revert to legacy behaviour of ignoring backslash */ private static final boolean DONT_CANONICALIZE_BACKSLASH = Boolean.parseBoolean("io.undertow.DONT_CANONICALIZE_BACKSLASH"); public static String canonicalize(final String path) { return canonicalize(path, false); } public static String canonicalize(final String path, final boolean nullAllowed) { int state = START; for (int i = path.length() - 1; i >= 0; --i) { final char c = path.charAt(i); switch (c) { case '/': if (state == FIRST_SLASH) { return realCanonicalize(path, i + 1, FIRST_SLASH, nullAllowed); } else if (state == ONE_DOT) { return realCanonicalize(path, i + 2, FIRST_SLASH, nullAllowed); } else if (state == TWO_DOT) { return realCanonicalize(path, i + 3, FIRST_SLASH, nullAllowed); } state = FIRST_SLASH; break; case '.': if (state == FIRST_SLASH || state == START || state == FIRST_BACKSLASH) { state = ONE_DOT; } else if(state == ONE_DOT) { state = TWO_DOT; } else { state = NORMAL; } break; case '\\': if(!DONT_CANONICALIZE_BACKSLASH) { if (state == FIRST_BACKSLASH) { return realCanonicalize(path, i + 1, FIRST_BACKSLASH, nullAllowed); } else if (state == ONE_DOT) { return realCanonicalize(path, i + 2, FIRST_BACKSLASH, nullAllowed); } else if (state == TWO_DOT) { return realCanonicalize(path, i + 3, FIRST_BACKSLASH, nullAllowed); } state = FIRST_BACKSLASH; break; } //fall through default: state = NORMAL; break; } } return path; } static final int START = -1; static final int NORMAL = 0; static final int FIRST_SLASH = 1; static final int ONE_DOT = 2; static final int TWO_DOT = 3; static final int FIRST_BACKSLASH = 4; private static String realCanonicalize(final String path, final int lastDot, final int initialState, final boolean nullAllowed) { int state = initialState; int eatCount = 0; int tokenEnd = path.length(); final List parts = new ArrayList<>(); for (int i = lastDot - 1; i >= 0; --i) { final char c = path.charAt(i); switch (state) { case NORMAL: { if (c == '/') { state = FIRST_SLASH; if (eatCount > 0) { --eatCount; tokenEnd = i; } } else if (c == '\\' && !DONT_CANONICALIZE_BACKSLASH) { state = FIRST_BACKSLASH; if (eatCount > 0) { --eatCount; tokenEnd = i; } } break; } case FIRST_SLASH: { if (c == '.') { state = ONE_DOT; } else if (c == '/') { if (eatCount > 0) { --eatCount; tokenEnd = i; } else { parts.add(path.substring(i + 1, tokenEnd)); tokenEnd = i; } } else { state = NORMAL; } break; } case FIRST_BACKSLASH: { if (c == '.') { state = ONE_DOT; } else if (c == '\\') { if (eatCount > 0) { --eatCount; tokenEnd = i; } else { parts.add(path.substring(i + 1, tokenEnd)); tokenEnd = i; } } else { state = NORMAL; } break; } case ONE_DOT: { if (c == '.') { state = TWO_DOT; } else if (c == '/' || (c == '\\' && !DONT_CANONICALIZE_BACKSLASH)) { if (i + 2 != tokenEnd) { parts.add(path.substring(i + 2, tokenEnd)); } tokenEnd = i; state = c == '/' ? FIRST_SLASH : FIRST_BACKSLASH; } else { state = NORMAL; } break; } case TWO_DOT: { if (c == '/' || (c == '\\' && !DONT_CANONICALIZE_BACKSLASH)) { if (i + 3 != tokenEnd) { parts.add(path.substring(i + 3, tokenEnd)); } tokenEnd = i; eatCount++; state = c == '/' ? FIRST_SLASH : FIRST_BACKSLASH; } else { state = NORMAL; } } } } if (eatCount > 0 && nullAllowed) { // the relative path is outside the context and null allowed return null; } final StringBuilder result = new StringBuilder(); if (tokenEnd != 0) { result.append(path.substring(0, tokenEnd)); } for (int i = parts.size() - 1; i >= 0; --i) { result.append(parts.get(i)); } if(result.length() == 0) { return "/"; } return result.toString(); } private CanonicalPathUtils() { } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/Certificates.java000066400000000000000000000034431420065311100265650ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; /** * Utility class for dealing with certificates * * @author Stuart Douglas */ public class Certificates { public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----"; public static final String END_CERT = "-----END CERTIFICATE-----"; public static String toPem(final javax.security.cert.X509Certificate certificate) throws javax.security.cert.CertificateEncodingException { return toPem(certificate.getEncoded()); } public static String toPem(final java.security.cert.Certificate certificate) throws java.security.cert.CertificateEncodingException { return toPem(certificate.getEncoded()); } private static String toPem(final byte[] encodedCertificate) { final StringBuilder builder = new StringBuilder(); builder.append(BEGIN_CERT); builder.append('\n'); builder.append(FlexBase64.encodeString(encodedCertificate, true)); builder.append('\n'); builder.append(END_CERT); return builder.toString(); } private Certificates() { } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/ChainedHandlerWrapper.java000066400000000000000000000025461420065311100303550ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.util.List; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; /** * Handler wrapper that chains several handler wrappers together. * * @author Stuart Douglas */ public class ChainedHandlerWrapper implements HandlerWrapper { private final List handlers; public ChainedHandlerWrapper(List handlers) { this.handlers = handlers; } @Override public HttpHandler wrap(HttpHandler handler) { HttpHandler cur = handler; for(HandlerWrapper h : handlers) { cur = h.wrap(cur); } return cur; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/ClosingChannelExceptionHandler.java000066400000000000000000000030121420065311100322140ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.io.Closeable; import java.io.IOException; import java.nio.channels.Channel; import org.xnio.ChannelExceptionHandler; import org.xnio.IoUtils; import io.undertow.UndertowLogger; /** * * Channel exception handler that closes the channel, logs a debug level * message and closes arbitrary other resources. * * @author Stuart Douglas */ public class ClosingChannelExceptionHandler implements ChannelExceptionHandler { private final Closeable[] closable; public ClosingChannelExceptionHandler(Closeable... closable) { this.closable = closable; } @Override public void handleException(T t, IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); IoUtils.safeClose(t); IoUtils.safeClose(closable); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/ConcurrentDirectDeque.java000066400000000000000000000037611420065311100304240ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.lang.reflect.Constructor; import java.util.AbstractCollection; import java.util.Deque; /** * A concurrent deque that allows direct item removal without traversal. * * @author Jason T. Greene */ public abstract class ConcurrentDirectDeque extends AbstractCollection implements Deque, java.io.Serializable { private static final Constructor CONSTRUCTOR; static { boolean fast = false; try { new FastConcurrentDirectDeque(); fast = true; } catch (Throwable t) { } Class klazz = fast ? FastConcurrentDirectDeque.class : PortableConcurrentDirectDeque.class; try { CONSTRUCTOR = klazz.getConstructor(); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } } public static ConcurrentDirectDeque newInstance() { try { return CONSTRUCTOR.newInstance(); } catch (Exception e) { throw new IllegalStateException(e); } } public abstract Object offerFirstAndReturnToken(E e); public abstract Object offerLastAndReturnToken(E e); public abstract void removeToken(Object token); } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/ConduitFactory.java000066400000000000000000000017201420065311100271110ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import org.xnio.conduits.Conduit; /** * @author Stuart Douglas */ public interface ConduitFactory { /** * Create the channel instance. * * @return the channel instance */ C create(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/ConnectionUtils.java000066400000000000000000000132451420065311100273010ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.UndertowLogger; import org.xnio.ChannelExceptionHandler; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.StreamConnection; import org.xnio.XnioExecutor; import org.xnio.conduits.ConduitStreamSinkChannel; import org.xnio.conduits.ConduitStreamSourceChannel; import java.io.Closeable; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; /** * @author Stuart Douglas */ public class ConnectionUtils { private static final long MAX_DRAIN_TIME = Long.getLong("io.undertow.max-drain-time", 10000); private ConnectionUtils() { } /** * Cleanly close a connection, by shutting down and flushing writes and then draining reads. *

* If this fails the connection is forcibly closed. * * @param connection The connection * @param additional Any additional resources to close once the connection has been closed */ public static void cleanClose(StreamConnection connection, Closeable... additional) { try { connection.getSinkChannel().shutdownWrites(); if (!connection.getSinkChannel().flush()) { connection.getSinkChannel().setWriteListener(ChannelListeners.flushingChannelListener(new ChannelListener() { @Override public void handleEvent(ConduitStreamSinkChannel channel) { doDrain(connection, additional); } }, new ChannelExceptionHandler() { @Override public void handleException(ConduitStreamSinkChannel channel, IOException exception) { UndertowLogger.REQUEST_IO_LOGGER.ioException(exception); IoUtils.safeClose(connection); IoUtils.safeClose(additional); } })); connection.getSinkChannel().resumeWrites(); } else { doDrain(connection, additional); } } catch (Throwable e) { if (e instanceof IOException) { UndertowLogger.REQUEST_IO_LOGGER.ioException((IOException) e); } else { UndertowLogger.REQUEST_IO_LOGGER.ioException(new IOException(e)); } IoUtils.safeClose(connection); IoUtils.safeClose(additional); } } private static void doDrain(final StreamConnection connection, final Closeable... additional) { if (!connection.getSourceChannel().isOpen()) { IoUtils.safeClose(connection); IoUtils.safeClose(additional); return; } final ByteBuffer b = ByteBuffer.allocate(1); try { int res = connection.getSourceChannel().read(b); b.clear(); if (res == 0) { final XnioExecutor.Key key = WorkerUtils.executeAfter(connection.getIoThread(), new Runnable() { @Override public void run() { IoUtils.safeClose(connection); IoUtils.safeClose(additional); } }, MAX_DRAIN_TIME, TimeUnit.MILLISECONDS); connection.getSourceChannel().setReadListener(new ChannelListener() { @Override public void handleEvent(ConduitStreamSourceChannel channel) { try { int res = channel.read(b); if (res != 0) { IoUtils.safeClose(connection); IoUtils.safeClose(additional); key.remove(); } } catch (Exception e) { if (e instanceof IOException) { UndertowLogger.REQUEST_IO_LOGGER.ioException((IOException) e); } else { UndertowLogger.REQUEST_IO_LOGGER.ioException(new IOException(e)); } IoUtils.safeClose(connection); IoUtils.safeClose(additional); key.remove(); } } }); connection.getSourceChannel().resumeReads(); } else { IoUtils.safeClose(connection); IoUtils.safeClose(additional); } } catch (Throwable e) { if (e instanceof IOException) { UndertowLogger.REQUEST_IO_LOGGER.ioException((IOException) e); } else { UndertowLogger.REQUEST_IO_LOGGER.ioException(new IOException(e)); } IoUtils.safeClose(connection); IoUtils.safeClose(additional); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/Cookies.java000066400000000000000000000420441420065311100255540ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.server.handlers.Cookie; import io.undertow.server.handlers.CookieImpl; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; /** * Class that contains utility methods for dealing with cookies. * * @author Stuart Douglas * @author Andre Dietisheim * @author Richard Opalka */ public class Cookies { public static final String DOMAIN = "$Domain"; public static final String VERSION = "$Version"; public static final String PATH = "$Path"; /** * Parses a "Set-Cookie:" response header value into its cookie representation. The header value is parsed according to the * syntax that's defined in RFC2109: * *

     * 
     *  set-cookie      =       "Set-Cookie:" cookies
     *   cookies         =       1#cookie
     *   cookie          =       NAME "=" VALUE *(";" cookie-av)
     *   NAME            =       attr
     *   VALUE           =       value
     *   cookie-av       =       "Comment" "=" value
     *                   |       "Domain" "=" value
     *                   |       "Max-Age" "=" value
     *                   |       "Path" "=" value
     *                   |       "Secure"
     *                   |       "Version" "=" 1*DIGIT
     *
     * 
     * 
* * @param headerValue The header value * @return The cookie * * @see Cookie * @see rfc2109 */ public static Cookie parseSetCookieHeader(final String headerValue) { String key = null; CookieImpl cookie = null; int state = 0; int current = 0; for (int i = 0; i < headerValue.length(); ++i) { char c = headerValue.charAt(i); switch (state) { case 0: { //reading key if (c == '=') { key = headerValue.substring(current, i); current = i + 1; state = 1; } else if ((c == ';' || c == ' ') && current == i) { current++; } else if (c == ';') { if (cookie == null) { throw UndertowMessages.MESSAGES.couldNotParseCookie(headerValue); } else { handleValue(cookie, headerValue.substring(current, i), null); } current = i + 1; } break; } case 1: { if (c == ';') { if (cookie == null) { cookie = new CookieImpl(key, headerValue.substring(current, i)); } else { handleValue(cookie, key, headerValue.substring(current, i)); } state = 0; current = i + 1; key = null; } else if (c == '"' && current == i) { current++; state = 2; } break; } case 2: { if (c == '"') { if (cookie == null) { cookie = new CookieImpl(key, headerValue.substring(current, i)); } else { handleValue(cookie, key, headerValue.substring(current, i)); } state = 0; current = i + 1; key = null; } break; } } } if (key == null) { if (current != headerValue.length()) { handleValue(cookie, headerValue.substring(current, headerValue.length()), null); } } else { if (current != headerValue.length()) { if(cookie == null) { cookie = new CookieImpl(key, headerValue.substring(current, headerValue.length())); } else { handleValue(cookie, key, headerValue.substring(current, headerValue.length())); } } else { handleValue(cookie, key, null); } } return cookie; } private static void handleValue(CookieImpl cookie, String key, String value) { if (key == null) { return; } if (key.equalsIgnoreCase("path")) { cookie.setPath(value); } else if (key.equalsIgnoreCase("domain")) { cookie.setDomain(value); } else if (key.equalsIgnoreCase("max-age")) { cookie.setMaxAge(Integer.parseInt(value)); } else if (key.equalsIgnoreCase("expires")) { cookie.setExpires(DateUtils.parseDate(value)); } else if (key.equalsIgnoreCase("discard")) { cookie.setDiscard(true); } else if (key.equalsIgnoreCase("secure")) { cookie.setSecure(true); } else if (key.equalsIgnoreCase("httpOnly")) { cookie.setHttpOnly(true); } else if (key.equalsIgnoreCase("version")) { cookie.setVersion(Integer.parseInt(value)); } else if (key.equalsIgnoreCase("comment")) { cookie.setComment(value); } else if (key.equalsIgnoreCase("samesite")) { cookie.setSameSite(true); cookie.setSameSiteMode(value); } //otherwise ignore this key-value pair } /** /** * Parses the cookies from a list of "Cookie:" header values. The cookie header values are parsed according to RFC2109 that * defines the following syntax: * *
     * 
     * cookie          =  "Cookie:" cookie-version
     *                    1*((";" | ",") cookie-value)
     * cookie-value    =  NAME "=" VALUE [";" path] [";" domain]
     * cookie-version  =  "$Version" "=" value
     * NAME            =  attr
     * VALUE           =  value
     * path            =  "$Path" "=" value
     * domain          =  "$Domain" "=" value
     * 
     * 
* * @param maxCookies The maximum number of cookies. Used to prevent hash collision attacks * @param allowEqualInValue if true equal characters are allowed in cookie values * @param cookies The cookie values to parse * @return A pared cookie map * * @see Cookie * @see rfc2109 * @deprecated use {@link #parseRequestCookies(int, boolean, List, Set)} instead */ @Deprecated public static Map parseRequestCookies(int maxCookies, boolean allowEqualInValue, List cookies) { return parseRequestCookies(maxCookies, allowEqualInValue, cookies, LegacyCookieSupport.COMMA_IS_SEPARATOR); } public static void parseRequestCookies(int maxCookies, boolean allowEqualInValue, List cookies, Set parsedCookies) { parseRequestCookies(maxCookies, allowEqualInValue, cookies, parsedCookies, LegacyCookieSupport.COMMA_IS_SEPARATOR); } @Deprecated static Map parseRequestCookies(int maxCookies, boolean allowEqualInValue, List cookies, boolean commaIsSeperator) { return parseRequestCookies(maxCookies, allowEqualInValue, cookies, commaIsSeperator, LegacyCookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0); } static void parseRequestCookies(int maxCookies, boolean allowEqualInValue, List cookies, Set parsedCookies, boolean commaIsSeperator) { parseRequestCookies(maxCookies, allowEqualInValue, cookies, parsedCookies, commaIsSeperator, LegacyCookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0); } static Map parseRequestCookies(int maxCookies, boolean allowEqualInValue, List cookies, boolean commaIsSeperator, boolean allowHttpSepartorsV0) { if (cookies == null) { return new TreeMap<>(); } final Set parsedCookies = new HashSet<>(); for (String cookie : cookies) { parseCookie(cookie, parsedCookies, maxCookies, allowEqualInValue, commaIsSeperator, allowHttpSepartorsV0); } final Map retVal = new TreeMap<>(); for (Cookie cookie : parsedCookies) { retVal.put(cookie.getName(), cookie); } return retVal; } static void parseRequestCookies(int maxCookies, boolean allowEqualInValue, List cookies, Set parsedCookies, boolean commaIsSeperator, boolean allowHttpSepartorsV0) { if (cookies != null) { for (String cookie : cookies) { parseCookie(cookie, parsedCookies, maxCookies, allowEqualInValue, commaIsSeperator, allowHttpSepartorsV0); } } } private static void parseCookie(final String cookie, final Set parsedCookies, int maxCookies, boolean allowEqualInValue, boolean commaIsSeperator, boolean allowHttpSepartorsV0) { int state = 0; String name = null; int start = 0; boolean containsEscapedQuotes = false; int cookieCount = parsedCookies.size(); final Map cookies = new HashMap<>(); final Map additional = new HashMap<>(); for (int i = 0; i < cookie.length(); ++i) { char c = cookie.charAt(i); switch (state) { case 0: { //eat leading whitespace if (c == ' ' || c == '\t' || c == ';') { start = i + 1; break; } state = 1; //fall through } case 1: { //extract key if (c == '=') { name = cookie.substring(start, i); start = i + 1; state = 2; } else if (c == ';' || (commaIsSeperator && c == ',')) { if(name != null) { cookieCount = createCookie(name, cookie.substring(start, i), maxCookies, cookieCount, cookies, additional); } else if(UndertowLogger.REQUEST_LOGGER.isTraceEnabled()) { UndertowLogger.REQUEST_LOGGER.trace("Ignoring invalid cookies in header " + cookie); } state = 0; start = i + 1; } break; } case 2: { //extract value if (c == ';' || (commaIsSeperator && c == ',')) { cookieCount = createCookie(name, cookie.substring(start, i), maxCookies, cookieCount, cookies, additional); state = 0; start = i + 1; } else if (c == '"' && start == i) { //only process the " if it is the first character containsEscapedQuotes = false; state = 3; start = i + 1; } else if (c == '=') { if (!allowEqualInValue && !allowHttpSepartorsV0) { cookieCount = createCookie(name, cookie.substring(start, i), maxCookies, cookieCount, cookies, additional); state = 4; start = i + 1; } } else if (c != ':' && !allowHttpSepartorsV0 && LegacyCookieSupport.isHttpSeparator(c)) { // http separators are not allowed in V0 cookie value unless io.undertow.legacy.cookie.ALLOW_HTTP_SEPARATORS_IN_V0 is set to true. // However, ":" (e.g. master:node1) is added as jvmRoute (instance-id) by default in WildFly domain mode. // Though ":" is http separator, we allow it by default. Because, when Undertow runs as a proxy server (mod_cluster), // we need to handle jvmRoute containing ":" in the request cookie value correctly to maintain the sticky session. cookieCount = createCookie(name, cookie.substring(start, i), maxCookies, cookieCount, cookies, additional); state = 4; start = i + 1; } break; } case 3: { //extract quoted value if (c == '"') { cookieCount = createCookie(name, containsEscapedQuotes ? unescapeDoubleQuotes(cookie.substring(start, i)) : cookie.substring(start, i), maxCookies, cookieCount, cookies, additional); state = 0; start = i + 1; } // Skip the next double quote char '"' when it is escaped by backslash '\' (i.e. \") inside the quoted value if (c == '\\' && (i + 1 < cookie.length()) && cookie.charAt(i + 1) == '"') { // But..., do not skip at the following conditions if (i + 2 == cookie.length()) { // Cookie: key="\" or Cookie: key="...\" break; } if (i + 2 < cookie.length() && (cookie.charAt(i + 2) == ';' // Cookie: key="\"; key2=... || (commaIsSeperator && cookie.charAt(i + 2) == ','))) { // Cookie: key="\", key2=... break; } // Skip the next double quote char ('"' behind '\') in the cookie value i++; containsEscapedQuotes = true; } break; } case 4: { //skip value portion behind '=' if (c == ';' || (commaIsSeperator && c == ',')) { state = 0; } start = i + 1; break; } } } if (state == 2) { createCookie(name, cookie.substring(start), maxCookies, cookieCount, cookies, additional); } for (final Map.Entry entry : cookies.entrySet()) { Cookie c = new CookieImpl(entry.getKey(), entry.getValue()); String domain = additional.get(DOMAIN); if (domain != null) { c.setDomain(domain); } String version = additional.get(VERSION); if (version != null) { c.setVersion(Integer.parseInt(version)); } String path = additional.get(PATH); if (path != null) { c.setPath(path); } parsedCookies.add(c); } } private static int createCookie(final String name, final String value, int maxCookies, int cookieCount, final Map cookies, final Map additional) { if (!name.isEmpty() && name.charAt(0) == '$') { if(additional.containsKey(name)) { return cookieCount; } additional.put(name, value); return cookieCount; } else { if (cookieCount == maxCookies) { throw UndertowMessages.MESSAGES.tooManyCookies(maxCookies); } if(cookies.containsKey(name)) { return cookieCount; } cookies.put(name, value); return ++cookieCount; } } private static String unescapeDoubleQuotes(final String value) { if (value == null || value.isEmpty()) { return value; } // Replace all escaped double quote (\") to double quote (") char[] tmp = new char[value.length()]; int dest = 0; for(int i = 0; i < value.length(); i++) { if (value.charAt(i) == '\\' && (i + 1 < value.length()) && value.charAt(i + 1) == '"') { i++; } tmp[dest] = value.charAt(i); dest++; } return new String(tmp, 0, dest); } private Cookies() { } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/CopyOnWriteMap.java000066400000000000000000000107051420065311100270370ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; /** * A basic copy on write map. It simply delegates to an underlying map, that is swapped out * every time the map is updated. * * Note: this is not a secure map. It should not be used in situations where the map is populated * from user input. * * @author Stuart Douglas */ public class CopyOnWriteMap implements ConcurrentMap { private volatile Map delegate = Collections.emptyMap(); public CopyOnWriteMap() { } public CopyOnWriteMap(Map existing) { this.delegate = new HashMap<>(existing); } @Override public synchronized V putIfAbsent(K key, V value) { final Map delegate = this.delegate; V existing = delegate.get(key); if(existing != null) { return existing; } putInternal(key, value); return null; } @Override public synchronized boolean remove(Object key, Object value) { final Map delegate = this.delegate; V existing = delegate.get(key); if(existing.equals(value)) { removeInternal(key); return true; } return false; } @Override public synchronized boolean replace(K key, V oldValue, V newValue) { final Map delegate = this.delegate; V existing = delegate.get(key); if(existing.equals(oldValue)) { putInternal(key, newValue); return true; } return false; } @Override public synchronized V replace(K key, V value) { final Map delegate = this.delegate; V existing = delegate.get(key); if(existing != null) { putInternal(key, value); return existing; } return null; } @Override public int size() { return delegate.size(); } @Override public boolean isEmpty() { return delegate.isEmpty(); } @Override public boolean containsKey(Object key) { return delegate.containsKey(key); } @Override public boolean containsValue(Object value) { return delegate.containsValue(value); } @Override public V get(Object key) { return delegate.get(key); } @Override public synchronized V put(K key, V value) { return putInternal(key, value); } @Override public synchronized V remove(Object key) { return removeInternal(key); } @Override public synchronized void putAll(Map m) { final Map delegate = new HashMap<>(this.delegate); for(Entry e : m.entrySet()) { delegate.put(e.getKey(), e.getValue()); } this.delegate = delegate; } @Override public synchronized void clear() { delegate = Collections.emptyMap(); } @Override public Set keySet() { return delegate.keySet(); } @Override public Collection values() { return delegate.values(); } @Override public Set> entrySet() { return delegate.entrySet(); } //must be called under lock private V putInternal(final K key, final V value) { final Map delegate = new HashMap<>(this.delegate); V existing = delegate.put(key, value); this.delegate = delegate; return existing; } public V removeInternal(final Object key) { final Map delegate = new HashMap<>(this.delegate); V existing = delegate.remove(key); this.delegate = delegate; return existing; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/DateUtils.java000066400000000000000000000226521420065311100260610ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.UndertowOptions; import io.undertow.server.HttpServerExchange; import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.TimeZone; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; /** * Utility for parsing and generating dates * * @author Stuart Douglas */ public class DateUtils { private static final Locale LOCALE_US = Locale.US; private static final TimeZone GMT_ZONE = TimeZone.getTimeZone("GMT"); private static final String RFC1123_PATTERN = "EEE, dd MMM yyyy HH:mm:ss z"; private static final AtomicReference cachedDateString = new AtomicReference<>(); /** * Thread local cache of this date format. This is technically a small memory leak, however * in practice it is fine, as it will only be used by server threads. *

* This is the most common date format, which is why we cache it. */ private static final ThreadLocal RFC1123_PATTERN_FORMAT = new ThreadLocal() { @Override protected SimpleDateFormat initialValue() { SimpleDateFormat df = new SimpleDateFormat(RFC1123_PATTERN, LOCALE_US); return df; } }; /** * Invalidates the current date */ private static final Runnable INVALIDATE_TASK = new Runnable() { @Override public void run() { cachedDateString.set(null); } }; private static final String RFC1036_PATTERN = "EEEEEEEEE, dd-MMM-yy HH:mm:ss z"; private static final String ASCITIME_PATTERN = "EEE MMM d HH:mm:ss yyyyy"; private static final String OLD_COOKIE_PATTERN = "EEE, dd-MMM-yyyy HH:mm:ss z"; private static final String COMMON_LOG_PATTERN = "[dd/MMM/yyyy:HH:mm:ss Z]"; private static final ThreadLocal COMMON_LOG_PATTERN_FORMAT = new ThreadLocal() { @Override protected SimpleDateFormat initialValue() { SimpleDateFormat df = new SimpleDateFormat(COMMON_LOG_PATTERN, LOCALE_US); return df; } }; private static final ThreadLocal OLD_COOKIE_FORMAT = new ThreadLocal() { @Override protected SimpleDateFormat initialValue() { SimpleDateFormat df = new SimpleDateFormat(OLD_COOKIE_PATTERN, LOCALE_US); df.setTimeZone(GMT_ZONE); return df; } }; /** * Converts a date to a format suitable for use in a HTTP request * * @param date The date * @return The RFC-1123 formatted date */ public static String toDateString(final Date date) { SimpleDateFormat df = RFC1123_PATTERN_FORMAT.get(); //we always need to set the time zone //because date format is stupid, and calling parse() can mutate the timezone //see UNDERTOW-458 df.setTimeZone(GMT_ZONE); return df.format(date); } public static String toOldCookieDateString(final Date date) { return OLD_COOKIE_FORMAT.get().format(date); } public static String toCommonLogFormat(final Date date) { return COMMON_LOG_PATTERN_FORMAT.get().format(date); } /** * Attempts to pass a HTTP date. * * @param date The date to parse * @return The parsed date, or null if parsing failed */ public static Date parseDate(final String date) { /* IE9 sends a superflous lenght parameter after date in the If-Modified-Since header, which needs to be stripped before parsing. */ final int semicolonIndex = date.indexOf(';'); final String trimmedDate = semicolonIndex >= 0 ? date.substring(0, semicolonIndex) : date; ParsePosition pp = new ParsePosition(0); SimpleDateFormat dateFormat = RFC1123_PATTERN_FORMAT.get(); dateFormat.setTimeZone(GMT_ZONE); Date val = dateFormat.parse(trimmedDate, pp); if (val != null && pp.getIndex() == trimmedDate.length()) { return val; } pp = new ParsePosition(0); dateFormat = new SimpleDateFormat(RFC1036_PATTERN, LOCALE_US); dateFormat.setTimeZone(GMT_ZONE); val = dateFormat.parse(trimmedDate, pp); if (val != null && pp.getIndex() == trimmedDate.length()) { return val; } pp = new ParsePosition(0); dateFormat = new SimpleDateFormat(ASCITIME_PATTERN, LOCALE_US); dateFormat.setTimeZone(GMT_ZONE); val = dateFormat.parse(trimmedDate, pp); if (val != null && pp.getIndex() == trimmedDate.length()) { return val; } pp = new ParsePosition(0); dateFormat = new SimpleDateFormat(OLD_COOKIE_PATTERN, LOCALE_US); dateFormat.setTimeZone(GMT_ZONE); val = dateFormat.parse(trimmedDate, pp); if (val != null && pp.getIndex() == trimmedDate.length()) { return val; } return null; } /** * Handles the if-modified-since header. returns true if the request should proceed, false otherwise * * @param exchange the exchange * @param lastModified The last modified date * @return */ public static boolean handleIfModifiedSince(final HttpServerExchange exchange, final Date lastModified) { return handleIfModifiedSince(exchange.getRequestHeaders().getFirst(Headers.IF_MODIFIED_SINCE), lastModified); } /** * Handles the if-modified-since header. returns true if the request should proceed, false otherwise * * @param modifiedSince the modified since date * @param lastModified The last modified date * @return */ public static boolean handleIfModifiedSince(final String modifiedSince, final Date lastModified) { if (lastModified == null) { return true; } if (modifiedSince == null) { return true; } Date modDate = parseDate(modifiedSince); if (modDate == null) { return true; } return lastModified.getTime() > (modDate.getTime() + 999); //UNDERTOW-341 +999 as there is no millisecond part in the if-modified-since } /** * Handles the if-unmodified-since header. returns true if the request should proceed, false otherwise * * @param exchange the exchange * @param lastModified The last modified date * @return */ public static boolean handleIfUnmodifiedSince(final HttpServerExchange exchange, final Date lastModified) { return handleIfUnmodifiedSince(exchange.getRequestHeaders().getFirst(Headers.IF_UNMODIFIED_SINCE), lastModified); } /** * Handles the if-unmodified-since header. returns true if the request should proceed, false otherwise * * @param modifiedSince the if unmodified since date * @param lastModified The last modified date * @return */ public static boolean handleIfUnmodifiedSince(final String modifiedSince, final Date lastModified) { if (lastModified == null) { return true; } if (modifiedSince == null) { return true; } Date modDate = parseDate(modifiedSince); if (modDate == null) { return true; } return lastModified.getTime() < (modDate.getTime() + 999); //UNDERTOW-341 +999 as there is no millisecond part in the if-unmodified-since } public static void addDateHeaderIfRequired(HttpServerExchange exchange) { HeaderMap responseHeaders = exchange.getResponseHeaders(); if (exchange.getConnection().getUndertowOptions().get(UndertowOptions.ALWAYS_SET_DATE, true) && !responseHeaders.contains(Headers.DATE)) { String dateString = getCurrentDateTime(exchange); responseHeaders.put(Headers.DATE, dateString); } } public static String getCurrentDateTime(HttpServerExchange exchange) { String dateString = cachedDateString.get(); if (dateString == null) { //set the time and register a timer to invalidate it //note that this is racey, it does not matter if multiple threads do this //the perf cost of synchronizing would be more than the perf cost of multiple threads running it long realTime = System.currentTimeMillis(); long mod = realTime % 1000; long toGo = 1000 - mod; dateString = DateUtils.toDateString(new Date(realTime)); if (cachedDateString.compareAndSet(null, dateString)) { WorkerUtils.executeAfter(exchange.getIoThread(), INVALIDATE_TASK, toGo, TimeUnit.MILLISECONDS); } } return dateString; } private DateUtils() { } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/ETag.java000066400000000000000000000034011420065311100247720ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; /** * @author Stuart Douglas */ public class ETag { private final boolean weak; private final String tag; public ETag(final boolean weak, final String tag) { this.weak = weak; this.tag = tag; } public boolean isWeak() { return weak; } public String getTag() { return tag; } @Override public String toString() { if(weak) { return "W/\"" + tag + "\""; } else { return "\"" + tag + "\""; } } @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final ETag eTag = (ETag) o; if (weak != eTag.weak) return false; if (tag != null ? !tag.equals(eTag.tag) : eTag.tag != null) return false; return true; } @Override public int hashCode() { int result = (weak ? 1 : 0); result = 31 * result + (tag != null ? tag.hashCode() : 0); return result; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/ETagUtils.java000066400000000000000000000264361420065311100260300ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.util.ArrayList; import java.util.Collections; import java.util.List; import io.undertow.server.HttpServerExchange; /** * @author Stuart Douglas */ public class ETagUtils { private static final char COMMA = ','; private static final char QUOTE = '"'; private static final char W = 'W'; private static final char SLASH = '/'; /** * Handles the if-match header. returns true if the request should proceed, false otherwise * * @param exchange the exchange * @param etag The etags * @return */ public static boolean handleIfMatch(final HttpServerExchange exchange, final ETag etag, boolean allowWeak) { return handleIfMatch(exchange, Collections.singletonList(etag), allowWeak); } /** * Handles the if-match header. returns true if the request should proceed, false otherwise * * @param exchange the exchange * @param etags The etags * @return */ public static boolean handleIfMatch(final HttpServerExchange exchange, final List etags, boolean allowWeak) { return handleIfMatch(exchange.getRequestHeaders().getFirst(Headers.IF_MATCH), etags, allowWeak); } /** * Handles the if-match header. returns true if the request should proceed, false otherwise * * @param ifMatch The if match header * @param etag The etags * @return */ public static boolean handleIfMatch(final String ifMatch, final ETag etag, boolean allowWeak) { return handleIfMatch(ifMatch, Collections.singletonList(etag), allowWeak); } /** * Handles the if-match header. returns true if the request should proceed, false otherwise * * @param ifMatch The ifMatch header * @param etags The etags * @return */ public static boolean handleIfMatch(final String ifMatch, final List etags, boolean allowWeak) { if (ifMatch == null) { return true; } if (ifMatch.equals("*")) { return true; //todo: how to tell if there is a current entity for the request } List parts = parseETagList(ifMatch); for (ETag part : parts) { if (part.isWeak() && !allowWeak) { continue; } for (ETag tag : etags) { if (tag != null) { if (tag.isWeak() && !allowWeak) { continue; } if (tag.getTag().equals(part.getTag())) { return true; } } } } return false; } /** * Handles the if-none-match header. returns true if the request should proceed, false otherwise * * @param exchange the exchange * @param etag The etags * @return */ public static boolean handleIfNoneMatch(final HttpServerExchange exchange, final ETag etag, boolean allowWeak) { return handleIfNoneMatch(exchange, Collections.singletonList(etag), allowWeak); } /** * Handles the if-none-match header. returns true if the request should proceed, false otherwise * * @param exchange the exchange * @param etags The etags * @return */ public static boolean handleIfNoneMatch(final HttpServerExchange exchange, final List etags, boolean allowWeak) { return handleIfNoneMatch(exchange.getRequestHeaders().getFirst(Headers.IF_NONE_MATCH), etags, allowWeak); } /** * Handles the if-none-match header. returns true if the request should proceed, false otherwise * * @param ifNoneMatch the header * @param etag The etags * @return */ public static boolean handleIfNoneMatch(final String ifNoneMatch, final ETag etag, boolean allowWeak) { return handleIfNoneMatch(ifNoneMatch, Collections.singletonList(etag), allowWeak); } /** * Handles the if-none-match header. returns true if the request should proceed, false otherwise * * @param ifNoneMatch the header * @param etags The etags * @return */ public static boolean handleIfNoneMatch(final String ifNoneMatch, final List etags, boolean allowWeak) { if (ifNoneMatch == null) { return true; } List parts = parseETagList(ifNoneMatch); for (ETag part : parts) { if (part.getTag().equals("*")) { return false; } if (part.isWeak() && !allowWeak) { continue; } for (ETag tag : etags) { if (tag != null) { if (tag.isWeak() && !allowWeak) { continue; } if (tag.getTag().equals(part.getTag())) { return false; } } } } return true; } public static List parseETagList(final String header) { char[] headerChars = header.toCharArray(); // The LinkedHashMap is used so that the parameter order can also be retained. List response = new ArrayList<>(); SearchingFor searchingFor = SearchingFor.START_OF_VALUE; int valueStart = 0; boolean weak = false; boolean malformed = false; for (int i = 0; i < headerChars.length; i++) { switch (searchingFor) { case START_OF_VALUE: if (headerChars[i] != COMMA && !Character.isWhitespace(headerChars[i])) { if (headerChars[i] == QUOTE) { valueStart = i + 1; searchingFor = SearchingFor.LAST_QUOTE; weak = false; malformed = false; } else if (headerChars[i] == W) { searchingFor = SearchingFor.WEAK_SLASH; } } break; case WEAK_SLASH: if (headerChars[i] == QUOTE) { valueStart = i + 1; searchingFor = SearchingFor.LAST_QUOTE; weak = true; malformed = false; } else if (headerChars[i] != SLASH) { malformed = true; searchingFor = SearchingFor.END_OF_VALUE; } break; case LAST_QUOTE: if (headerChars[i] == QUOTE) { String value = String.valueOf(headerChars, valueStart, i - valueStart); response.add(new ETag(weak, value.trim())); searchingFor = SearchingFor.START_OF_VALUE; } break; case END_OF_VALUE: if (headerChars[i] == COMMA || Character.isWhitespace(headerChars[i])) { if (!malformed) { String value = String.valueOf(headerChars, valueStart, i - valueStart); response.add(new ETag(weak, value.trim())); searchingFor = SearchingFor.START_OF_VALUE; } } break; } } if (searchingFor == SearchingFor.END_OF_VALUE || searchingFor == SearchingFor.LAST_QUOTE) { if (!malformed) { // Special case where we reached the end of the array containing the header values. String value = String.valueOf(headerChars, valueStart, headerChars.length - valueStart); response.add(new ETag(weak, value.trim())); } } return response; } /** * @param exchange The exchange * @return The ETag for the exchange, or null if the etag is not set */ public static ETag getETag(final HttpServerExchange exchange) { final String tag = exchange.getResponseHeaders().getFirst(Headers.ETAG); if (tag == null) { return null; } char[] headerChars = tag.toCharArray(); SearchingFor searchingFor = SearchingFor.START_OF_VALUE; int valueStart = 0; boolean weak = false; boolean malformed = false; for (int i = 0; i < headerChars.length; i++) { switch (searchingFor) { case START_OF_VALUE: if (headerChars[i] != COMMA && !Character.isWhitespace(headerChars[i])) { if (headerChars[i] == QUOTE) { valueStart = i + 1; searchingFor = SearchingFor.LAST_QUOTE; weak = false; malformed = false; } else if (headerChars[i] == W) { searchingFor = SearchingFor.WEAK_SLASH; } } break; case WEAK_SLASH: if (headerChars[i] == QUOTE) { valueStart = i + 1; searchingFor = SearchingFor.LAST_QUOTE; weak = true; malformed = false; } else if (headerChars[i] != SLASH) { return null; //malformed } break; case LAST_QUOTE: if (headerChars[i] == QUOTE) { String value = String.valueOf(headerChars, valueStart, i - valueStart); return new ETag(weak, value.trim()); } break; case END_OF_VALUE: if (headerChars[i] == COMMA || Character.isWhitespace(headerChars[i])) { if (!malformed) { String value = String.valueOf(headerChars, valueStart, i - valueStart); return new ETag(weak, value.trim()); } } break; } } if (searchingFor == SearchingFor.END_OF_VALUE || searchingFor == SearchingFor.LAST_QUOTE) { if (!malformed) { // Special case where we reached the end of the array containing the header values. String value = String.valueOf(headerChars, valueStart, headerChars.length - valueStart); return new ETag(weak, value.trim()); } } return null; } enum SearchingFor { START_OF_VALUE, LAST_QUOTE, END_OF_VALUE, WEAK_SLASH; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/FastConcurrentDirectDeque.java000066400000000000000000001623121420065311100312400ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ /* * Written by Doug Lea and Martin Buchholz with assistance from members of * JCP JSR-166 Expert Group and released to the public domain, as explained * at http://creativecommons.org/publicdomain/zero/1.0/ */ package io.undertow.util; import java.io.Serializable; import java.lang.reflect.Field; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Collection; import java.util.Deque; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Queue; import java.util.Spliterator; import java.util.Spliterators; import java.util.function.Consumer; import sun.misc.Unsafe; /** * A modified version of ConcurrentLinkedDeque which includes direct * removal. Like the original, it relies on Unsafe for better performance. * * More specifically, an unbounded concurrent {@linkplain Deque deque} based on linked nodes. * Concurrent insertion, removal, and access operations execute safely * across multiple threads. * A {@code ConcurrentLinkedDeque} is an appropriate choice when * many threads will share access to a common collection. * Like most other concurrent collection implementations, this class * does not permit the use of {@code null} elements. * *

Iterators and spliterators are * weakly consistent. * *

Beware that, unlike in most collections, the {@code size} method * is NOT a constant-time operation. Because of the * asynchronous nature of these deques, determining the current number * of elements requires a traversal of the elements, and so may report * inaccurate results if this collection is modified during traversal. * Additionally, the bulk operations {@code addAll}, * {@code removeAll}, {@code retainAll}, {@code containsAll}, * {@code equals}, and {@code toArray} are not guaranteed * to be performed atomically. For example, an iterator operating * concurrently with an {@code addAll} operation might view only some * of the added elements. * *

This class and its iterator implement all of the optional * methods of the {@link Deque} and {@link Iterator} interfaces. * *

Memory consistency effects: As with other concurrent collections, * actions in a thread prior to placing an object into a * {@code ConcurrentLinkedDeque} * happen-before * actions subsequent to the access or removal of that element from * the {@code ConcurrentLinkedDeque} in another thread. * *

This class is a member of the * * Java Collections Framework. * * Based on revision 1.50 of ConcurrentLinkedDeque * (see http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/main/java/util/concurrent/ConcurrentLinkedDeque.java?revision=1.50&view=markup) * This is the version used in JDK 1.8.0_121. * * @since 1.7 * @author Doug Lea * @author Martin Buchholz * @author Jason T. Grene * @param the type of elements held in this collection */ public class FastConcurrentDirectDeque extends ConcurrentDirectDeque implements Deque, Serializable { /* * This is an implementation of a concurrent lock-free deque * supporting interior removes but not interior insertions, as * required to support the entire Deque interface. * * We extend the techniques developed for ConcurrentLinkedQueue and * LinkedTransferQueue (see the internal docs for those classes). * Understanding the ConcurrentLinkedQueue implementation is a * prerequisite for understanding the implementation of this class. * * The data structure is a symmetrical doubly-linked "GC-robust" * linked list of nodes. We minimize the number of volatile writes * using two techniques: advancing multiple hops with a single CAS * and mixing volatile and non-volatile writes of the same memory * locations. * * A node contains the expected E ("item") and links to predecessor * ("prev") and successor ("next") nodes: * * class Node { volatile Node prev, next; volatile E item; } * * A node p is considered "live" if it contains a non-null item * (p.item != null). When an item is CASed to null, the item is * atomically logically deleted from the collection. * * At any time, there is precisely one "first" node with a null * prev reference that terminates any chain of prev references * starting at a live node. Similarly there is precisely one * "last" node terminating any chain of next references starting at * a live node. The "first" and "last" nodes may or may not be live. * The "first" and "last" nodes are always mutually reachable. * * A new element is added atomically by CASing the null prev or * next reference in the first or last node to a fresh node * containing the element. The element's node atomically becomes * "live" at that point. * * A node is considered "active" if it is a live node, or the * first or last node. Active nodes cannot be unlinked. * * A "self-link" is a next or prev reference that is the same node: * p.prev == p or p.next == p * Self-links are used in the node unlinking process. Active nodes * never have self-links. * * A node p is active if and only if: * * p.item != null || * (p.prev == null && p.next != p) || * (p.next == null && p.prev != p) * * The deque object has two node references, "head" and "tail". * The head and tail are only approximations to the first and last * nodes of the deque. The first node can always be found by * following prev pointers from head; likewise for tail. However, * it is permissible for head and tail to be referring to deleted * nodes that have been unlinked and so may not be reachable from * any live node. * * There are 3 stages of node deletion; * "logical deletion", "unlinking", and "gc-unlinking". * * 1. "logical deletion" by CASing item to null atomically removes * the element from the collection, and makes the containing node * eligible for unlinking. * * 2. "unlinking" makes a deleted node unreachable from active * nodes, and thus eventually reclaimable by GC. Unlinked nodes * may remain reachable indefinitely from an iterator. * * Physical node unlinking is merely an optimization (albeit a * critical one), and so can be performed at our convenience. At * any time, the set of live nodes maintained by prev and next * links are identical, that is, the live nodes found via next * links from the first node is equal to the elements found via * prev links from the last node. However, this is not true for * nodes that have already been logically deleted - such nodes may * be reachable in one direction only. * * 3. "gc-unlinking" takes unlinking further by making active * nodes unreachable from deleted nodes, making it easier for the * GC to reclaim future deleted nodes. This step makes the data * structure "gc-robust", as first described in detail by Boehm * (http://portal.acm.org/citation.cfm?doid=503272.503282). * * GC-unlinked nodes may remain reachable indefinitely from an * iterator, but unlike unlinked nodes, are never reachable from * head or tail. * * Making the data structure GC-robust will eliminate the risk of * unbounded memory retention with conservative GCs and is likely * to improve performance with generational GCs. * * When a node is dequeued at either end, e.g. via poll(), we would * like to break any references from the node to active nodes. We * develop further the use of self-links that was very effective in * other concurrent collection classes. The idea is to replace * prev and next pointers with special values that are interpreted * to mean off-the-list-at-one-end. These are approximations, but * good enough to preserve the properties we want in our * traversals, e.g. we guarantee that a traversal will never visit * the same element twice, but we don't guarantee whether a * traversal that runs out of elements will be able to see more * elements later after enqueues at that end. Doing gc-unlinking * safely is particularly tricky, since any node can be in use * indefinitely (for example by an iterator). We must ensure that * the nodes pointed at by head/tail never get gc-unlinked, since * head/tail are needed to get "back on track" by other nodes that * are gc-unlinked. gc-unlinking accounts for much of the * implementation complexity. * * Since neither unlinking nor gc-unlinking are necessary for * correctness, there are many implementation choices regarding * frequency (eagerness) of these operations. Since volatile * reads are likely to be much cheaper than CASes, saving CASes by * unlinking multiple adjacent nodes at a time may be a win. * gc-unlinking can be performed rarely and still be effective, * since it is most important that long chains of deleted nodes * are occasionally broken. * * The actual representation we use is that p.next == p means to * goto the first node (which in turn is reached by following prev * pointers from head), and p.next == null && p.prev == p means * that the iteration is at an end and that p is a (static final) * dummy node, NEXT_TERMINATOR, and not the last active node. * Finishing the iteration when encountering such a TERMINATOR is * good enough for read-only traversals, so such traversals can use * p.next == null as the termination condition. When we need to * find the last (active) node, for enqueueing a new node, we need * to check whether we have reached a TERMINATOR node; if so, * restart traversal from tail. * * The implementation is completely directionally symmetrical, * except that most public methods that iterate through the list * follow next pointers ("forward" direction). * * We believe (without full proof) that all single-element deque * operations (e.g., addFirst, peekLast, pollLast) are linearizable * (see Herlihy and Shavit's book). However, some combinations of * operations are known not to be linearizable. In particular, * when an addFirst(A) is racing with pollFirst() removing B, it is * possible for an observer iterating over the elements to observe * A B C and subsequently observe A C, even though no interior * removes are ever performed. Nevertheless, iterators behave * reasonably, providing the "weakly consistent" guarantees. * * Empirically, microbenchmarks suggest that this class adds about * 40% overhead relative to ConcurrentLinkedQueue, which feels as * good as we can hope for. */ private static final long serialVersionUID = 876323262645176354L; /** * A node from which the first node on list (that is, the unique node p * with p.prev == null && p.next != p) can be reached in O(1) time. * Invariants: * - the first node is always O(1) reachable from head via prev links * - all live nodes are reachable from the first node via succ() * - head != null * - (tmp = head).next != tmp || tmp != head * - head is never gc-unlinked (but may be unlinked) * Non-invariants: * - head.item may or may not be null * - head may not be reachable from the first or last node, or from tail */ private transient volatile Node head; /** * A node from which the last node on list (that is, the unique node p * with p.next == null && p.prev != p) can be reached in O(1) time. * Invariants: * - the last node is always O(1) reachable from tail via next links * - all live nodes are reachable from the last node via pred() * - tail != null * - tail is never gc-unlinked (but may be unlinked) * Non-invariants: * - tail.item may or may not be null * - tail may not be reachable from the first or last node, or from head */ private transient volatile Node tail; private static final Node PREV_TERMINATOR, NEXT_TERMINATOR; @SuppressWarnings("unchecked") Node prevTerminator() { return (Node) PREV_TERMINATOR; } @SuppressWarnings("unchecked") Node nextTerminator() { return (Node) NEXT_TERMINATOR; } static final class Node { volatile Node prev; volatile E item; volatile Node next; Node() { // default constructor for NEXT_TERMINATOR, PREV_TERMINATOR } /** * Constructs a new node. Uses relaxed write because item can * only be seen after publication via casNext or casPrev. */ Node(E item) { UNSAFE.putObject(this, itemOffset, item); } boolean casItem(E cmp, E val) { return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val); } void lazySetNext(Node val) { UNSAFE.putOrderedObject(this, nextOffset, val); } boolean casNext(Node cmp, Node val) { return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val); } void lazySetPrev(Node val) { UNSAFE.putOrderedObject(this, prevOffset, val); } boolean casPrev(Node cmp, Node val) { return UNSAFE.compareAndSwapObject(this, prevOffset, cmp, val); } // Unsafe mechanics private static final sun.misc.Unsafe UNSAFE; private static final long prevOffset; private static final long itemOffset; private static final long nextOffset; static { try { UNSAFE = getUnsafe(); Class k = Node.class; prevOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("prev")); itemOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("item")); nextOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("next")); } catch (Exception e) { throw new Error(e); } } private static Unsafe getUnsafe() { if (System.getSecurityManager() != null) { return AccessController.doPrivileged(new PrivilegedAction() { public Unsafe run() { return getUnsafe0(); } }); } return getUnsafe0(); } } /** * Links e as first element. */ private Node linkFirst(E e) { checkNotNull(e); final Node newNode = new Node<>(e); restartFromHead: for (;;) for (Node h = head, p = h, q;;) { if ((q = p.prev) != null && (q = (p = q).prev) != null) // Check for head updates every other hop. // If p == q, we are sure to follow head instead. p = (h != (h = head)) ? h : q; else if (p.next == p) // PREV_TERMINATOR continue restartFromHead; else { // p is first node newNode.lazySetNext(p); // CAS piggyback if (p.casPrev(null, newNode)) { // Successful CAS is the linearization point // for e to become an element of this deque, // and for newNode to become "live". if (p != h) // hop two nodes at a time casHead(h, newNode); // Failure is OK. return newNode; } // Lost CAS race to another thread; re-read prev } } } /** * Links e as last element. */ private Node linkLast(E e) { checkNotNull(e); final Node newNode = new Node<>(e); restartFromTail: for (;;) for (Node t = tail, p = t, q;;) { if ((q = p.next) != null && (q = (p = q).next) != null) // Check for tail updates every other hop. // If p == q, we are sure to follow tail instead. p = (t != (t = tail)) ? t : q; else if (p.prev == p) // NEXT_TERMINATOR continue restartFromTail; else { // p is last node newNode.lazySetPrev(p); // CAS piggyback if (p.casNext(null, newNode)) { // Successful CAS is the linearization point // for e to become an element of this deque, // and for newNode to become "live". if (p != t) // hop two nodes at a time casTail(t, newNode); // Failure is OK. return newNode; } // Lost CAS race to another thread; re-read next } } } private static final int HOPS = 2; /** * Unlinks non-null node x. */ void unlink(Node x) { final Node prev = x.prev; final Node next = x.next; if (prev == null) { unlinkFirst(x, next); } else if (next == null) { unlinkLast(x, prev); } else { // Unlink interior node. // // This is the common case, since a series of polls at the // same end will be "interior" removes, except perhaps for // the first one, since end nodes cannot be unlinked. // // At any time, all active nodes are mutually reachable by // following a sequence of either next or prev pointers. // // Our strategy is to find the unique active predecessor // and successor of x. Try to fix up their links so that // they point to each other, leaving x unreachable from // active nodes. If successful, and if x has no live // predecessor/successor, we additionally try to gc-unlink, // leaving active nodes unreachable from x, by rechecking // that the status of predecessor and successor are // unchanged and ensuring that x is not reachable from // tail/head, before setting x's prev/next links to their // logical approximate replacements, self/TERMINATOR. Node activePred, activeSucc; boolean isFirst, isLast; int hops = 1; // Find active predecessor for (Node p = prev; ; ++hops) { if (p.item != null) { activePred = p; isFirst = false; break; } Node q = p.prev; if (q == null) { if (p.next == p) return; activePred = p; isFirst = true; break; } else if (p == q) return; else p = q; } // Find active successor for (Node p = next; ; ++hops) { if (p.item != null) { activeSucc = p; isLast = false; break; } Node q = p.next; if (q == null) { if (p.prev == p) return; activeSucc = p; isLast = true; break; } else if (p == q) return; else p = q; } // TODO: better HOP heuristics if (hops < HOPS // always squeeze out interior deleted nodes && (isFirst || isLast)) return; // Squeeze out deleted nodes between activePred and // activeSucc, including x. skipDeletedSuccessors(activePred); skipDeletedPredecessors(activeSucc); // Try to gc-unlink, if possible if ((isFirst || isLast) && // Recheck expected state of predecessor and successor (activePred.next == activeSucc) && (activeSucc.prev == activePred) && (isFirst ? activePred.prev == null : activePred.item != null) && (isLast ? activeSucc.next == null : activeSucc.item != null)) { updateHead(); // Ensure x is not reachable from head updateTail(); // Ensure x is not reachable from tail // Finally, actually gc-unlink x.lazySetPrev(isFirst ? prevTerminator() : x); x.lazySetNext(isLast ? nextTerminator() : x); } } } /** * Unlinks non-null first node. */ private void unlinkFirst(Node first, Node next) { for (Node o = null, p = next, q;;) { if (p.item != null || (q = p.next) == null) { if (o != null && p.prev != p && first.casNext(next, p)) { skipDeletedPredecessors(p); if (first.prev == null && (p.next == null || p.item != null) && p.prev == first) { updateHead(); // Ensure o is not reachable from head updateTail(); // Ensure o is not reachable from tail // Finally, actually gc-unlink o.lazySetNext(o); o.lazySetPrev(prevTerminator()); } } return; } else if (p == q) return; else { o = p; p = q; } } } /** * Unlinks non-null last node. */ private void unlinkLast(Node last, Node prev) { for (Node o = null, p = prev, q;;) { if (p.item != null || (q = p.prev) == null) { if (o != null && p.next != p && last.casPrev(prev, p)) { skipDeletedSuccessors(p); if (last.next == null && (p.prev == null || p.item != null) && p.next == last) { updateHead(); // Ensure o is not reachable from head updateTail(); // Ensure o is not reachable from tail // Finally, actually gc-unlink o.lazySetPrev(o); o.lazySetNext(nextTerminator()); } } return; } else if (p == q) return; else { o = p; p = q; } } } /** * Guarantees that any node which was unlinked before a call to * this method will be unreachable from head after it returns. * Does not guarantee to eliminate slack, only that head will * point to a node that was active while this method was running. */ private void updateHead() { // Either head already points to an active node, or we keep // trying to cas it to the first node until it does. Node h, p, q; restartFromHead: while ((h = head).item == null && (p = h.prev) != null) { for (;;) { if ((q = p.prev) == null || (q = (p = q).prev) == null) { // It is possible that p is PREV_TERMINATOR, // but if so, the CAS is guaranteed to fail. if (casHead(h, p)) return; else continue restartFromHead; } else if (h != head) continue restartFromHead; else p = q; } } } /** * Guarantees that any node which was unlinked before a call to * this method will be unreachable from tail after it returns. * Does not guarantee to eliminate slack, only that tail will * point to a node that was active while this method was running. */ private void updateTail() { // Either tail already points to an active node, or we keep // trying to cas it to the last node until it does. Node t, p, q; restartFromTail: while ((t = tail).item == null && (p = t.next) != null) { for (;;) { if ((q = p.next) == null || (q = (p = q).next) == null) { // It is possible that p is NEXT_TERMINATOR, // but if so, the CAS is guaranteed to fail. if (casTail(t, p)) return; else continue restartFromTail; } else if (t != tail) continue restartFromTail; else p = q; } } } private void skipDeletedPredecessors(Node x) { whileActive: do { Node prev = x.prev; Node p = prev; findActive: for (;;) { if (p.item != null) break findActive; Node q = p.prev; if (q == null) { if (p.next == p) continue whileActive; break findActive; } else if (p == q) continue whileActive; else p = q; } // found active CAS target if (prev == p || x.casPrev(prev, p)) return; } while (x.item != null || x.next == null); } private void skipDeletedSuccessors(Node x) { whileActive: do { Node next = x.next; Node p = next; findActive: for (;;) { if (p.item != null) break findActive; Node q = p.next; if (q == null) { if (p.prev == p) continue whileActive; break findActive; } else if (p == q) continue whileActive; else p = q; } // found active CAS target if (next == p || x.casNext(next, p)) return; } while (x.item != null || x.prev == null); } /** * Returns the successor of p, or the first node if p.next has been * linked to self, which will only be true if traversing with a * stale pointer that is now off the list. */ final Node succ(Node p) { // TODO: should we skip deleted nodes here? Node q = p.next; return (p == q) ? first() : q; } /** * Returns the predecessor of p, or the last node if p.prev has been * linked to self, which will only be true if traversing with a * stale pointer that is now off the list. */ final Node pred(Node p) { Node q = p.prev; return (p == q) ? last() : q; } /** * Returns the first node, the unique node p for which: * p.prev == null && p.next != p * The returned node may or may not be logically deleted. * Guarantees that head is set to the returned node. */ Node first() { restartFromHead: for (;;) for (Node h = head, p = h, q;;) { if ((q = p.prev) != null && (q = (p = q).prev) != null) // Check for head updates every other hop. // If p == q, we are sure to follow head instead. p = (h != (h = head)) ? h : q; else if (p == h // It is possible that p is PREV_TERMINATOR, // but if so, the CAS is guaranteed to fail. || casHead(h, p)) return p; else continue restartFromHead; } } /** * Returns the last node, the unique node p for which: * p.next == null && p.prev != p * The returned node may or may not be logically deleted. * Guarantees that tail is set to the returned node. */ Node last() { restartFromTail: for (;;) for (Node t = tail, p = t, q;;) { if ((q = p.next) != null && (q = (p = q).next) != null) // Check for tail updates every other hop. // If p == q, we are sure to follow tail instead. p = (t != (t = tail)) ? t : q; else if (p == t // It is possible that p is NEXT_TERMINATOR, // but if so, the CAS is guaranteed to fail. || casTail(t, p)) return p; else continue restartFromTail; } } // Minor convenience utilities /** * Throws NullPointerException if argument is null. * * @param v the element */ private static void checkNotNull(Object v) { if (v == null) throw new NullPointerException(); } /** * Returns element unless it is null, in which case throws * NoSuchElementException. * * @param v the element * @return the element */ private E screenNullResult(E v) { if (v == null) throw new NoSuchElementException(); return v; } /** * Creates an array list and fills it with elements of this list. * Used by toArray. * * @return the array list */ private ArrayList toArrayList() { ArrayList list = new ArrayList<>(); for (Node p = first(); p != null; p = succ(p)) { E item = p.item; if (item != null) list.add(item); } return list; } /** * Constructs an empty deque. */ public FastConcurrentDirectDeque() { head = tail = new Node<>(null); } /** * Constructs a deque initially containing the elements of * the given collection, added in traversal order of the * collection's iterator. * * @param c the collection of elements to initially contain * @throws NullPointerException if the specified collection or any * of its elements are null */ public FastConcurrentDirectDeque(Collection c) { // Copy c into a private chain of Nodes Node h = null, t = null; for (E e : c) { checkNotNull(e); Node newNode = new Node<>(e); if (h == null) h = t = newNode; else { t.lazySetNext(newNode); newNode.lazySetPrev(t); t = newNode; } } initHeadTail(h, t); } /** * Initializes head and tail, ensuring invariants hold. */ private void initHeadTail(Node h, Node t) { if (h == t) { if (h == null) h = t = new Node<>(null); else { // Avoid edge case of a single Node with non-null item. Node newNode = new Node<>(null); t.lazySetNext(newNode); newNode.lazySetPrev(t); t = newNode; } } head = h; tail = t; } /** * Inserts the specified element at the front of this deque. * As the deque is unbounded, this method will never throw * {@link IllegalStateException}. * * @throws NullPointerException if the specified element is null */ public void addFirst(E e) { linkFirst(e); } /** * Inserts the specified element at the end of this deque. * As the deque is unbounded, this method will never throw * {@link IllegalStateException}. * *

This method is equivalent to {@link #add}. * * @throws NullPointerException if the specified element is null */ public void addLast(E e) { linkLast(e); } /** * Inserts the specified element at the front of this deque. * As the deque is unbounded, this method will never return {@code false}. * * @return {@code true} (as specified by {@link Deque#offerFirst}) * @throws NullPointerException if the specified element is null */ public boolean offerFirst(E e) { linkFirst(e); return true; } public Object offerFirstAndReturnToken(E e) { return linkFirst(e); } public Object offerLastAndReturnToken(E e) { return linkLast(e); } public void removeToken(Object token) { if (!(token instanceof Node)) { throw new IllegalArgumentException(); } Node node = (Node) (token); while (! node.casItem(node.item, null)) {} unlink(node); } /** * Inserts the specified element at the end of this deque. * As the deque is unbounded, this method will never return {@code false}. * *

This method is equivalent to {@link #add}. * * @return {@code true} (as specified by {@link Deque#offerLast}) * @throws NullPointerException if the specified element is null */ public boolean offerLast(E e) { linkLast(e); return true; } public E peekFirst() { for (Node p = first(); p != null; p = succ(p)) { E item = p.item; if (item != null) return item; } return null; } public E peekLast() { for (Node p = last(); p != null; p = pred(p)) { E item = p.item; if (item != null) return item; } return null; } /** * @throws NoSuchElementException {@inheritDoc} */ public E getFirst() { return screenNullResult(peekFirst()); } /** * @throws NoSuchElementException {@inheritDoc} */ public E getLast() { return screenNullResult(peekLast()); } public E pollFirst() { for (Node p = first(); p != null; p = succ(p)) { E item = p.item; if (item != null && p.casItem(item, null)) { unlink(p); return item; } } return null; } public E pollLast() { for (Node p = last(); p != null; p = pred(p)) { E item = p.item; if (item != null && p.casItem(item, null)) { unlink(p); return item; } } return null; } /** * @throws NoSuchElementException {@inheritDoc} */ public E removeFirst() { return screenNullResult(pollFirst()); } /** * @throws NoSuchElementException {@inheritDoc} */ public E removeLast() { return screenNullResult(pollLast()); } // *** Queue and stack methods *** /** * Inserts the specified element at the tail of this deque. * As the deque is unbounded, this method will never return {@code false}. * * @return {@code true} (as specified by {@link Queue#offer}) * @throws NullPointerException if the specified element is null */ public boolean offer(E e) { return offerLast(e); } /** * Inserts the specified element at the tail of this deque. * As the deque is unbounded, this method will never throw * {@link IllegalStateException} or return {@code false}. * * @return {@code true} (as specified by {@link Collection#add}) * @throws NullPointerException if the specified element is null */ public boolean add(E e) { return offerLast(e); } public E poll() { return pollFirst(); } public E peek() { return peekFirst(); } /** * @throws NoSuchElementException {@inheritDoc} */ public E remove() { return removeFirst(); } /** * @throws NoSuchElementException {@inheritDoc} */ public E pop() { return removeFirst(); } /** * @throws NoSuchElementException {@inheritDoc} */ public E element() { return getFirst(); } /** * @throws NullPointerException {@inheritDoc} */ public void push(E e) { addFirst( e ); } /** * Removes the first element {@code e} such that * {@code o.equals(e)}, if such an element exists in this deque. * If the deque does not contain the element, it is unchanged. * * @param o element to be removed from this deque, if present * @return {@code true} if the deque contained the specified element * @throws NullPointerException if the specified element is null */ public boolean removeFirstOccurrence(Object o) { checkNotNull(o); for (Node p = first(); p != null; p = succ(p)) { E item = p.item; if (item != null && o.equals(item) && p.casItem(item, null)) { unlink(p); return true; } } return false; } /** * Removes the last element {@code e} such that * {@code o.equals(e)}, if such an element exists in this deque. * If the deque does not contain the element, it is unchanged. * * @param o element to be removed from this deque, if present * @return {@code true} if the deque contained the specified element * @throws NullPointerException if the specified element is null */ public boolean removeLastOccurrence(Object o) { checkNotNull(o); for (Node p = last(); p != null; p = pred(p)) { E item = p.item; if (item != null && o.equals(item) && p.casItem(item, null)) { unlink(p); return true; } } return false; } /** * Returns {@code true} if this deque contains at least one * element {@code e} such that {@code o.equals(e)}. * * @param o element whose presence in this deque is to be tested * @return {@code true} if this deque contains the specified element */ public boolean contains(Object o) { if (o == null) return false; for (Node p = first(); p != null; p = succ(p)) { E item = p.item; if (item != null && o.equals(item)) return true; } return false; } /** * Returns {@code true} if this collection contains no elements. * * @return {@code true} if this collection contains no elements */ public boolean isEmpty() { return peekFirst() == null; } /** * Returns the number of elements in this deque. If this deque * contains more than {@code Integer.MAX_VALUE} elements, it * returns {@code Integer.MAX_VALUE}. * *

Beware that, unlike in most collections, this method is * NOT a constant-time operation. Because of the * asynchronous nature of these deques, determining the current * number of elements requires traversing them all to count them. * Additionally, it is possible for the size to change during * execution of this method, in which case the returned result * will be inaccurate. Thus, this method is typically not very * useful in concurrent applications. * * @return the number of elements in this deque */ public int size() { int count = 0; for (Node p = first(); p != null; p = succ(p)) if (p.item != null) // Collection.size() spec says to max out if (++count == Integer.MAX_VALUE) break; return count; } /** * Removes the first element {@code e} such that * {@code o.equals(e)}, if such an element exists in this deque. * If the deque does not contain the element, it is unchanged. * * @param o element to be removed from this deque, if present * @return {@code true} if the deque contained the specified element * @throws NullPointerException if the specified element is null */ public boolean remove(Object o) { return removeFirstOccurrence(o); } /** * Appends all of the elements in the specified collection to the end of * this deque, in the order that they are returned by the specified * collection's iterator. Attempts to {@code addAll} of a deque to * itself result in {@code IllegalArgumentException}. * * @param c the elements to be inserted into this deque * @return {@code true} if this deque changed as a result of the call * @throws NullPointerException if the specified collection or any * of its elements are null * @throws IllegalArgumentException if the collection is this deque */ public boolean addAll(Collection c) { if (c == this) // As historically specified in AbstractQueue#addAll throw new IllegalArgumentException(); // Copy c into a private chain of Nodes Node beginningOfTheEnd = null, last = null; for (E e : c) { checkNotNull(e); Node newNode = new Node<>(e); if (beginningOfTheEnd == null) beginningOfTheEnd = last = newNode; else { last.lazySetNext(newNode); newNode.lazySetPrev(last); last = newNode; } } if (beginningOfTheEnd == null) return false; // Atomically append the chain at the tail of this collection restartFromTail: for (;;) for (Node t = tail, p = t, q;;) { if ((q = p.next) != null && (q = (p = q).next) != null) // Check for tail updates every other hop. // If p == q, we are sure to follow tail instead. p = (t != (t = tail)) ? t : q; else if (p.prev == p) // NEXT_TERMINATOR continue restartFromTail; else { // p is last node beginningOfTheEnd.lazySetPrev(p); // CAS piggyback if (p.casNext(null, beginningOfTheEnd)) { // Successful CAS is the linearization point // for all elements to be added to this deque. if (!casTail(t, last)) { // Try a little harder to update tail, // since we may be adding many elements. t = tail; if (last.next == null) casTail(t, last); } return true; } // Lost CAS race to another thread; re-read next } } } /** * Removes all of the elements from this deque. */ public void clear() { while (pollFirst() != null) { } } /** * Returns an array containing all of the elements in this deque, in * proper sequence (from first to last element). * *

The returned array will be "safe" in that no references to it are * maintained by this deque. (In other words, this method must allocate * a new array). The caller is thus free to modify the returned array. * *

This method acts as bridge between array-based and collection-based * APIs. * * @return an array containing all of the elements in this deque */ public Object[] toArray() { return toArrayList().toArray(); } /** * Returns an array containing all of the elements in this deque, * in proper sequence (from first to last element); the runtime * type of the returned array is that of the specified array. If * the deque fits in the specified array, it is returned therein. * Otherwise, a new array is allocated with the runtime type of * the specified array and the size of this deque. * *

If this deque fits in the specified array with room to spare * (i.e., the array has more elements than this deque), the element in * the array immediately following the end of the deque is set to * {@code null}. * *

Like the {@link #toArray()} method, this method acts as * bridge between array-based and collection-based APIs. Further, * this method allows precise control over the runtime type of the * output array, and may, under certain circumstances, be used to * save allocation costs. * *

Suppose {@code x} is a deque known to contain only strings. * The following code can be used to dump the deque into a newly * allocated array of {@code String}: * *

 {@code String[] y = x.toArray(new String[0]);}
* * Note that {@code toArray(new Object[0])} is identical in function to * {@code toArray()}. * * @param a the array into which the elements of the deque are to * be stored, if it is big enough; otherwise, a new array of the * same runtime type is allocated for this purpose * @return an array containing all of the elements in this deque * @throws ArrayStoreException if the runtime type of the specified array * is not a supertype of the runtime type of every element in * this deque * @throws NullPointerException if the specified array is null */ public T[] toArray(T[] a) { return toArrayList().toArray(a); } /** * Returns an iterator over the elements in this deque in proper sequence. * The elements will be returned in order from first (head) to last (tail). * *

The returned iterator is * weakly consistent. * * @return an iterator over the elements in this deque in proper sequence */ public Iterator iterator() { return new Itr(); } /** * Returns an iterator over the elements in this deque in reverse * sequential order. The elements will be returned in order from * last (tail) to first (head). * *

The returned iterator is * weakly consistent. * * @return an iterator over the elements in this deque in reverse order */ public Iterator descendingIterator() { return new DescendingItr(); } private abstract class AbstractItr implements Iterator { /** * Next node to return item for. */ private Node nextNode; /** * nextItem holds on to item fields because once we claim * that an element exists in hasNext(), we must return it in * the following next() call even if it was in the process of * being removed when hasNext() was called. */ private E nextItem; /** * Node returned by most recent call to next. Needed by remove. * Reset to null if this element is deleted by a call to remove. */ private Node lastRet; abstract Node startNode(); abstract Node nextNode(Node p); AbstractItr() { advance(); } /** * Sets nextNode and nextItem to next valid node, or to null * if no such. */ private void advance() { lastRet = nextNode; Node p = (nextNode == null) ? startNode() : nextNode(nextNode); for (;; p = nextNode(p)) { if (p == null) { // p might be active end or TERMINATOR node; both are OK nextNode = null; nextItem = null; break; } E item = p.item; if (item != null) { nextNode = p; nextItem = item; break; } } } public boolean hasNext() { return nextItem != null; } public E next() { E item = nextItem; if (item == null) throw new NoSuchElementException(); advance(); return item; } public void remove() { Node l = lastRet; if (l == null) throw new IllegalStateException(); l.item = null; unlink(l); lastRet = null; } } /** * Forward iterator */ private class Itr extends AbstractItr { Node startNode() { return first(); } Node nextNode(Node p) { return succ( p ); } } /** * Descending iterator */ private class DescendingItr extends AbstractItr { Node startNode() { return last(); } Node nextNode(Node p) { return pred( p ); } } /** A customized variant of Spliterators.IteratorSpliterator */ static final class CLDSpliterator implements Spliterator { static final int MAX_BATCH = 1 << 25; // max batch array size; final FastConcurrentDirectDeque queue; Node current; // current node; null until initialized int batch; // batch size for splits boolean exhausted; // true when no more nodes CLDSpliterator(FastConcurrentDirectDeque queue) { this.queue = queue; } public Spliterator trySplit() { Node p; final FastConcurrentDirectDeque q = this.queue; int b = batch; int n = (b <= 0) ? 1 : (b >= MAX_BATCH) ? MAX_BATCH : b + 1; if (!exhausted && ((p = current) != null || (p = q.first()) != null)) { if (p.item == null && p == (p = p.next)) current = p = q.first(); if (p != null && p.next != null) { Object[] a = new Object[n]; int i = 0; do { if ((a[i] = p.item) != null) ++i; if (p == (p = p.next)) p = q.first(); } while (p != null && i < n); if ((current = p) == null) exhausted = true; if (i > 0) { batch = i; return Spliterators.spliterator (a, 0, i, Spliterator.ORDERED | Spliterator.NONNULL | Spliterator.CONCURRENT); } } } return null; } public void forEachRemaining(Consumer action) { Node p; if (action == null) throw new NullPointerException(); final FastConcurrentDirectDeque q = this.queue; if (!exhausted && ((p = current) != null || (p = q.first()) != null)) { exhausted = true; do { E e = p.item; if (p == (p = p.next)) p = q.first(); if (e != null) action.accept(e); } while (p != null); } } public boolean tryAdvance(Consumer action) { Node p; if (action == null) throw new NullPointerException(); final FastConcurrentDirectDeque q = this.queue; if (!exhausted && ((p = current) != null || (p = q.first()) != null)) { E e; do { e = p.item; if (p == (p = p.next)) p = q.first(); } while (e == null && p != null); if ((current = p) == null) exhausted = true; if (e != null) { action.accept(e); return true; } } return false; } public long estimateSize() { return Long.MAX_VALUE; } public int characteristics() { return Spliterator.ORDERED | Spliterator.NONNULL | Spliterator.CONCURRENT; } } /** * Returns a {@link Spliterator} over the elements in this deque. * *

The returned spliterator is * weakly consistent. * *

The {@code Spliterator} reports {@link Spliterator#CONCURRENT}, * {@link Spliterator#ORDERED}, and {@link Spliterator#NONNULL}. * * @implNote * The {@code Spliterator} implements {@code trySplit} to permit limited * parallelism. * * @return a {@code Spliterator} over the elements in this deque * @since 1.8 */ public Spliterator spliterator() { return new CLDSpliterator(this); } /** * Saves this deque to a stream (that is, serializes it). * * @param s the stream * @throws java.io.IOException if an I/O error occurs * @serialData All of the elements (each an {@code E}) in * the proper order, followed by a null */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { // Write out any hidden stuff s.defaultWriteObject(); // Write out all elements in the proper order. for (Node p = first(); p != null; p = succ(p)) { E item = p.item; if (item != null) s.writeObject(item); } // Use trailing null as sentinel s.writeObject(null); } /** * Reconstitutes this deque from a stream (that is, deserializes it). * @param s the stream * @throws ClassNotFoundException if the class of a serialized object * could not be found * @throws java.io.IOException if an I/O error occurs */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); // Read in elements until trailing null sentinel found Node h = null, t = null; Object item; while ((item = s.readObject()) != null) { @SuppressWarnings("unchecked") Node newNode = new Node<>((E) item); if (h == null) h = t = newNode; else { t.lazySetNext(newNode); newNode.lazySetPrev(t); t = newNode; } } initHeadTail(h, t); } private boolean casHead(Node cmp, Node val) { return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val); } private boolean casTail(Node cmp, Node val) { return UNSAFE.compareAndSwapObject(this, tailOffset, cmp, val); } // Unsafe mechanics private static final sun.misc.Unsafe UNSAFE; private static final long headOffset; private static final long tailOffset; static { PREV_TERMINATOR = new Node<>(); PREV_TERMINATOR.next = PREV_TERMINATOR; NEXT_TERMINATOR = new Node<>(); NEXT_TERMINATOR.prev = NEXT_TERMINATOR; try { UNSAFE = getUnsafe(); Class k = FastConcurrentDirectDeque.class; headOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("head")); tailOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("tail")); } catch (Exception e) { throw new Error(e); } } private static Unsafe getUnsafe() { if (System.getSecurityManager() != null) { return AccessController.doPrivileged(new PrivilegedAction() { public Unsafe run() { return getUnsafe0(); } }); } return getUnsafe0(); } private static Unsafe getUnsafe0() { try { Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true); return (Unsafe) theUnsafe.get(null); } catch (Throwable t) { throw new RuntimeException("JDK did not allow accessing unsafe", t); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/FileUtils.java000066400000000000000000000060621420065311100260600ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; /** * @author Stuart Douglas */ public class FileUtils { private FileUtils() { } public static String readFile(Class testClass, String fileName) { final URL res = testClass.getResource(fileName); return readFile(res); } public static String readFile(URL url) { try { return readFile(url.openStream()); } catch (IOException e) { throw new RuntimeException(e); } } /** * Reads the {@link InputStream file} and converting it to {@link String} using UTF-8 encoding. */ public static String readFile(InputStream file) { try (BufferedInputStream stream = new BufferedInputStream(file)) { byte[] buff = new byte[1024]; StringBuilder builder = new StringBuilder(); int read; while ((read = stream.read(buff)) != -1) { builder.append(new String(buff, 0, read, StandardCharsets.UTF_8)); } return builder.toString(); } catch (IOException e) { throw new RuntimeException(e); } } public static void deleteRecursive(final Path directory) throws IOException { if(!Files.isDirectory(directory)) { return; } Files.walkFileTree(directory, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { try { Files.delete(file); } catch (IOException e) { // ignored } return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { try { Files.delete(dir); } catch (IOException e) { // ignored } return FileVisitResult.CONTINUE; } }); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/FlexBase64.java000066400000000000000000002212341420065311100260230ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Constructor; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.security.AccessController; import java.security.PrivilegedExceptionAction; /** * An efficient and flexible Base64 implementation. * * This class can deal with both MIME Base64 and Base64url. * * @author Jason T. Greene */ public class FlexBase64 { /* * Note that this code heavily favors performance over reuse and clean style. */ private static final byte[] STANDARD_ENCODING_TABLE; private static final byte[] STANDARD_DECODING_TABLE = new byte[80]; private static final byte[] URL_ENCODING_TABLE; private static final byte[] URL_DECODING_TABLE = new byte[80]; private static final Constructor STRING_CONSTRUCTOR; static { STANDARD_ENCODING_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes(StandardCharsets.US_ASCII); URL_ENCODING_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".getBytes(StandardCharsets.US_ASCII); for (int i = 0; i < STANDARD_ENCODING_TABLE.length; i++) { int v = (STANDARD_ENCODING_TABLE[i] & 0xFF) - 43; STANDARD_DECODING_TABLE[v] = (byte)(i + 1); // zero = illegal } for (int i = 0; i < URL_ENCODING_TABLE.length; i++) { int v = (URL_ENCODING_TABLE[i] & 0xFF) - 43; URL_DECODING_TABLE[v] = (byte)(i + 1); // zero = illegal } Constructor c = null; try { PrivilegedExceptionAction> runnable = new PrivilegedExceptionAction>() { @Override public Constructor run() throws Exception { Constructor c; c = String.class.getDeclaredConstructor(char[].class, boolean.class); c.setAccessible(true); return c; } }; if (System.getSecurityManager() != null) { c = AccessController.doPrivileged(runnable); } else { c = runnable.run(); } } catch (Throwable t) { } STRING_CONSTRUCTOR = c; } /** * Creates a state driven base64 encoder. * *

The Encoder instance is not thread-safe, and must not be shared between threads without establishing a * happens-before relationship.

* * @param wrap whether or not to wrap at 76 characters with CRLF * @return an createEncoder instance */ public static Encoder createEncoder(boolean wrap) { return new Encoder(wrap, false); } /** * Creates a state driven base64url encoder. * *

The Encoder instance is not thread-safe, and must not be shared between threads without establishing a * happens-before relationship.

* * @param wrap whether or not to wrap at 76 characters with CRLF * @return an createEncoder instance */ public static Encoder createURLEncoder(boolean wrap) { return new Encoder(wrap, true); } /** * Creates a state driven base64 decoder. * *

The Decoder instance is not thread-safe, and must not be shared between threads without establishing a * happens-before relationship.

* * @return a new createDecoder instance */ public static Decoder createDecoder() { return new Decoder(false); } /** * Creates a state driven base64url decoder. * *

The Decoder instance is not thread-safe, and must not be shared between threads without establishing a * happens-before relationship.

* * @return a new createDecoder instance */ public static Decoder createURLDecoder() { return new Decoder(true); } /** * Encodes a fixed and complete byte array into a Base64 String. * *

This method is only useful for applications which require a String and have all data to be encoded up-front. * Note that byte arrays or buffers are almost always a better storage choice. They consume half the memory and * can be reused (modified). In other words, it is almost always better to use {@link #encodeBytes}, * {@link #createEncoder}, or {@link #createEncoderOutputStream} instead. * instead. * * @param source the byte array to encode from * @param wrap whether or not to wrap the output at 76 chars with CRLFs * @return a new String representing the Base64 output */ public static String encodeString(byte[] source, boolean wrap) { return Encoder.encodeString(source, 0, source.length, wrap, false); } /** * Encodes a fixed and complete byte array into a Base64url String. * *

This method is only useful for applications which require a String and have all data to be encoded up-front. * Note that byte arrays or buffers are almost always a better storage choice. They consume half the memory and * can be reused (modified). In other words, it is almost always better to use {@link #encodeBytes}, * {@link #createEncoder}, or {@link #createEncoderOutputStream} instead. * instead. * * @param source the byte array to encode from * @param wrap whether or not to wrap the output at 76 chars with CRLFs * @return a new String representing the Base64url output */ public static String encodeStringURL(byte[] source, boolean wrap) { return Encoder.encodeString(source, 0, source.length, wrap, true); } /** * Encodes a fixed and complete byte array into a Base64 String. * *

This method is only useful for applications which require a String and have all data to be encoded up-front. * Note that byte arrays or buffers are almost always a better storage choice. They consume half the memory and * can be reused (modified). In other words, it is almost always better to use {@link #encodeBytes}, * {@link #createEncoder}, or {@link #createEncoderOutputStream} instead.

* *

     *    // Encodes "ell"
     *    FlexBase64.encodeString("hello".getBytes("US-ASCII"), 1, 4);
     * 
* * @param source the byte array to encode from * @param pos the position to start encoding from * @param limit the position to halt encoding at (exclusive) * @param wrap whether or not to wrap the output at 76 chars with CRLFs * @return a new String representing the Base64 output */ public static String encodeString(byte[] source, int pos, int limit, boolean wrap) { return Encoder.encodeString(source, pos, limit, wrap, false); } /** * Encodes a fixed and complete byte array into a Base64url String. * *

This method is only useful for applications which require a String and have all data to be encoded up-front. * Note that byte arrays or buffers are almost always a better storage choice. They consume half the memory and * can be reused (modified). In other words, it is almost always better to use {@link #encodeBytes}, * {@link #createEncoder}, or {@link #createEncoderOutputStream} instead.

* *

     *    // Encodes "ell"
     *    FlexBase64.encodeStringURL("hello".getBytes("US-ASCII"), 1, 4);
     * 
* * @param source the byte array to encode from * @param pos the position to start encoding from * @param limit the position to halt encoding at (exclusive) * @param wrap whether or not to wrap the output at 76 chars with CRLFs * @return a new String representing the Base64url output */ public static String encodeStringURL(byte[] source, int pos, int limit, boolean wrap) { return Encoder.encodeString(source, pos, limit, wrap, true); } /** * Encodes a fixed and complete byte buffer into a Base64 String. * *

This method is only useful for applications which require a String and have all data to be encoded up-front. * Note that byte arrays or buffers are almost always a better storage choice. They consume half the memory and * can be reused (modified). In other words, it is almost always better to use {@link #encodeBytes}, * {@link #createEncoder}, or {@link #createEncoderOutputStream} instead.

* *

     *    // Encodes "hello"
     *    FlexBase64.encodeString(ByteBuffer.wrap("hello".getBytes("US-ASCII")), false);
     * 
* * @param source the byte buffer to encode from * @param wrap whether or not to wrap the output at 76 chars with CRLFs * @return a new String representing the Base64 output */ public static String encodeString(ByteBuffer source, boolean wrap) { return Encoder.encodeString(source, wrap, false); } /** * Encodes a fixed and complete byte buffer into a Base64url String. * *

This method is only useful for applications which require a String and have all data to be encoded up-front. * Note that byte arrays or buffers are almost always a better storage choice. They consume half the memory and * can be reused (modified). In other words, it is almost always better to use {@link #encodeBytes}, * {@link #createEncoder}, or {@link #createEncoderOutputStream} instead.

* *

     *    // Encodes "hello"
     *    FlexBase64.encodeStringURL(ByteBuffer.wrap("hello".getBytes("US-ASCII")), false);
     * 
* * @param source the byte buffer to encode from * @param wrap whether or not to wrap the output at 76 chars with CRLFs * @return a new String representing the Base64url output */ public static String encodeStringURL(ByteBuffer source, boolean wrap) { return Encoder.encodeString(source, wrap, true); } /** * Encodes a fixed and complete byte buffer into a Base64 byte array. * *

     *    // Encodes "ell"
     *    FlexBase64.encodeString("hello".getBytes("US-ASCII"), 1, 4, false);
     * 
* * @param source the byte array to encode from * @param pos the position to start encoding at * @param limit the position to halt encoding at (exclusive) * @param wrap whether or not to wrap at 76 characters with CRLFs * @return a new byte array containing the encoded ASCII values */ public static byte[] encodeBytes(byte[] source, int pos, int limit, boolean wrap) { return Encoder.encodeBytes(source, pos, limit, wrap, false); } /** * Encodes a fixed and complete byte buffer into a Base64url byte array. * *

     *    // Encodes "ell"
     *    FlexBase64.encodeStringURL("hello".getBytes("US-ASCII"), 1, 4, false);
     * 
* * @param source the byte array to encode from * @param pos the position to start encoding at * @param limit the position to halt encoding at (exclusive) * @param wrap whether or not to wrap at 76 characters with CRLFs * @return a new byte array containing the encoded ASCII values */ public static byte[] encodeBytesURL(byte[] source, int pos, int limit, boolean wrap) { return Encoder.encodeBytes(source, pos, limit, wrap, true); } /** * Decodes a Base64 encoded string into a new byte buffer. The returned byte buffer is a heap buffer, * and it is therefor possible to retrieve the backing array using {@link java.nio.ByteBuffer#array()}, * {@link java.nio.ByteBuffer#arrayOffset()} and {@link java.nio.ByteBuffer#limit()}. The latter is very * important since the decoded array may be larger than the decoded data. This is due to length estimation which * avoids an unnecessary array copy. * * @param source the Base64 string to decode * @return a byte buffer containing the decoded output * @throws IOException if the encoding is invalid or corrupted */ public static ByteBuffer decode(String source) throws IOException { return Decoder.decode(source, false); } /** * Decodes a Base64url encoded string into a new byte buffer. The returned byte buffer is a heap buffer, * and it is therefor possible to retrieve the backing array using {@link java.nio.ByteBuffer#array()}, * {@link java.nio.ByteBuffer#arrayOffset()} and {@link java.nio.ByteBuffer#limit()}. The latter is very * important since the decoded array may be larger than the decoded data. This is due to length estimation which * avoids an unnecessary array copy. * * @param source the Base64 string to decode * @return a byte buffer containing the decoded output * @throws IOException if the encoding is invalid or corrupted */ public static ByteBuffer decodeURL(String source) throws IOException { return Decoder.decode(source, true); } /** * Decodes a Base64 encoded byte buffer into a new byte buffer. The returned byte buffer is a heap buffer, * and it is therefor possible to retrieve the backing array using {@link java.nio.ByteBuffer#array()}, * {@link java.nio.ByteBuffer#arrayOffset()} and {@link java.nio.ByteBuffer#limit()}. The latter is very * important since the decoded array may be larger than the decoded data. This is due to length estimation which * avoids an unnecessary array copy. * * @param source the Base64 content to decode * @return a byte buffer containing the decoded output * @throws IOException if the encoding is invalid or corrupted */ public static ByteBuffer decode(ByteBuffer source) throws IOException { return Decoder.decode(source, false); } /** * Decodes a Base64url encoded byte buffer into a new byte buffer. The returned byte buffer is a heap buffer, * and it is therefor possible to retrieve the backing array using {@link java.nio.ByteBuffer#array()}, * {@link java.nio.ByteBuffer#arrayOffset()} and {@link java.nio.ByteBuffer#limit()}. The latter is very * important since the decoded array may be larger than the decoded data. This is due to length estimation which * avoids an unnecessary array copy. * * @param source the Base64 content to decode * @return a byte buffer containing the decoded output * @throws IOException if the encoding is invalid or corrupted */ public static ByteBuffer decodeURL(ByteBuffer source) throws IOException { return Decoder.decode(source, true); } /** * Decodes a Base64 encoded byte array into a new byte buffer. The returned byte buffer is a heap buffer, * and it is therefor possible to retrieve the backing array using {@link java.nio.ByteBuffer#array()}, * {@link java.nio.ByteBuffer#arrayOffset()} and {@link java.nio.ByteBuffer#limit()}. The latter is very * important since the decoded array may be larger than the decoded data. This is due to length estimation which * avoids an unnecessary array copy. * * @param source the Base64 content to decode * @param off position to start decoding from in source * @param limit position to stop decoding in source (exclusive) * @return a byte buffer containing the decoded output * @throws IOException if the encoding is invalid or corrupted */ public static ByteBuffer decode(byte[] source, int off, int limit) throws IOException { return Decoder.decode(source, off, limit, false); } /** * Decodes a Base64url encoded byte array into a new byte buffer. The returned byte buffer is a heap buffer, * and it is therefor possible to retrieve the backing array using {@link java.nio.ByteBuffer#array()}, * {@link java.nio.ByteBuffer#arrayOffset()} and {@link java.nio.ByteBuffer#limit()}. The latter is very * important since the decoded array may be larger than the decoded data. This is due to length estimation which * avoids an unnecessary array copy. * * @param source the Base64url content to decode * @param off position to start decoding from in source * @param limit position to stop decoding in source (exclusive) * @return a byte buffer containing the decoded output * @throws IOException if the encoding is invalid or corrupted */ public static ByteBuffer decodeURL(byte[] source, int off, int limit) throws IOException { return Decoder.decode(source, off, limit, true); } /** * Creates an InputStream wrapper which encodes a source into base64 as it is read, until the source hits EOF. * Upon hitting EOF, a standard base64 termination sequence will be readable. Clients can simply treat this input * stream as if they were reading from a base64 encoded file. This stream attempts to read and encode in buffer * size chunks from the source, in order to improve overall performance. Thus, BufferInputStream is not necessary * and will lead to double buffering. * *

This stream is not thread-safe, and should not be shared between threads, without establishing a * happens-before relationship.

* * @param source an input source to read from * @param bufferSize the chunk size to buffer from the source * @param wrap whether or not the stream should wrap base64 output at 76 characters * @return an encoded input stream instance. */ public static EncoderInputStream createEncoderInputStream(InputStream source, int bufferSize, boolean wrap) { return new EncoderInputStream(source, bufferSize, wrap, false); } /** * Creates an InputStream wrapper which encodes a source into base64 as it is read, until the source hits EOF. * Upon hitting EOF, a standard base64 termination sequence will be readable. Clients can simply treat this input * stream as if they were reading from a base64 encoded file. This stream attempts to read and encode in 8192 byte * chunks. Thus, BufferedInputStream is not necessary as a source and will lead to double buffering. * *

This stream is not thread-safe, and should not be shared between threads, without establishing a * happens-before relationship.

* * @param source an input source to read from * @return an encoded input stream instance. */ public static EncoderInputStream createEncoderInputStream(InputStream source) { return new EncoderInputStream(source); } /** * Creates an InputStream wrapper which decodes a base64 input source into the decoded content as it is read, * until the source hits EOF. Upon hitting EOF, a standard base64 termination sequence will be readable. * Clients can simply treat this input stream as if they were reading from a base64 encoded file. This stream * attempts to read and encode in buffer size byte chunks. Thus, BufferedInputStream is not necessary * as a source and will lead to double buffering. * *

Note that the end of a base64 stream can not reliably be detected, so if multiple base64 streams exist on the * wire, the source stream will need to simulate an EOF when the boundary mechanism is detected.

* *

This stream is not thread-safe, and should not be shared between threads, without establishing a * happens-before relationship.

* * @param source an input source to read from * @param bufferSize the chunk size to buffer before when reading from the target * @return a decoded input stream instance. */ public static DecoderInputStream createDecoderInputStream(InputStream source, int bufferSize) { return new DecoderInputStream(source, bufferSize); } /** * Creates an InputStream wrapper which decodes a base64 input source into the decoded content as it is read, * until the source hits EOF. Upon hitting EOF, a standard base64 termination sequence will be readable. * Clients can simply treat this input stream as if they were reading from a base64 encoded file. This stream * attempts to read and encode in 8192 byte chunks. Thus, BufferedInputStream is not necessary * as a source and will lead to double buffering. * *

Note that the end of a base64 stream can not reliably be detected, so if multiple base64 streams exist on the * wire, the source stream will need to simulate an EOF when the boundary mechanism is detected.

* *

This stream is not thread-safe, and should not be shared between threads, without establishing a * happens-before relationship.

* * @param source an input source to read from * @return a decoded input stream instance. */ public static DecoderInputStream createDecoderInputStream(InputStream source) { return new DecoderInputStream(source); } /** * Creates an OutputStream wrapper which base64 encodes and writes to the passed OutputStream target. When this * stream is closed base64 padding will be added if needed. Alternatively if this represents an "inner stream", * the {@link FlexBase64.EncoderOutputStream#complete()} method can be called to close out * the inner stream without closing the wrapped target. * *

All bytes written will be queued to a buffer in the specified size. This stream, therefore, does not require * BufferedOutputStream, which would lead to double buffering. * * @param target an output target to write to * @param bufferSize the chunk size to buffer before writing to the target * @param wrap whether or not the stream should wrap base64 output at 76 characters * @return an encoded output stream instance. */ public static EncoderOutputStream createEncoderOutputStream(OutputStream target, int bufferSize, boolean wrap) { return new EncoderOutputStream(target, bufferSize, wrap); } /** * Creates an OutputStream wrapper which base64 encodes and writes to the passed OutputStream target. When this * stream is closed base64 padding will be added if needed. Alternatively if this represents an "inner stream", * the {@link FlexBase64.EncoderOutputStream#complete()} method can be called to close out * the inner stream without closing the wrapped target. * *

All bytes written will be queued to an 8192 byte buffer. This stream, therefore, does not require * BufferedOutputStream, which would lead to double buffering.

* *

This stream is not thread-safe, and should not be shared between threads, without establishing a * happens-before relationship.

* * @param output the output stream to write encoded output to * @return an encoded output stream instance. */ public static EncoderOutputStream createEncoderOutputStream(OutputStream output) { return new EncoderOutputStream(output); } /** * Creates an OutputStream wrapper which decodes base64 content before writing to the passed OutputStream target. * *

All bytes written will be queued to a buffer using the specified buffer size. This stream, therefore, does * not require BufferedOutputStream, which would lead to double buffering.

* *

This stream is not thread-safe, and should not be shared between threads, without establishing a * happens-before relationship.

* * @param output the output stream to write decoded output to * @param bufferSize the buffer size to buffer writes to * @return a decoded output stream instance. */ public static DecoderOutputStream createDecoderOutputStream(OutputStream output, int bufferSize) { return new DecoderOutputStream(output, bufferSize); } /** * Creates an OutputStream wrapper which decodes base64 content before writing to the passed OutputStream target. * *

All bytes written will be queued to an 8192 byte buffer. This stream, therefore, does * not require BufferedOutputStream, which would lead to double buffering.

* *

This stream is not thread-safe, and should not be shared between threads, without establishing a * happens-before relationship.

* * @param output the output stream to write decoded output to * @return a decoded output stream instance. */ public static DecoderOutputStream createDecoderOutputStream(OutputStream output) { return new DecoderOutputStream(output); } /** * Controls the encoding process. */ public static final class Encoder { private int state; private int last; private int count; private final boolean wrap; private int lastPos; private final byte[] encodingTable; private Encoder(boolean wrap, boolean url) { this.wrap = wrap; this.encodingTable = url ? URL_ENCODING_TABLE : STANDARD_ENCODING_TABLE; } /** * Encodes bytes read from source and writes them in base64 format to target. If the source limit is hit, this * method will return and save the current state, such that future calls can resume the encoding process. * In addition, if the target does not have the capacity to fit an entire quad of bytes, this method will also * return and save state for subsequent calls to this method. Once all bytes have been encoded to the target, * {@link #complete(java.nio.ByteBuffer)} should be called to add the necessary padding characters. * * @param source the byte buffer to read from * @param target the byte buffer to write to */ public void encode(ByteBuffer source, ByteBuffer target) { if (target == null) throw new IllegalStateException(); int last = this.last; int state = this.state; boolean wrap = this.wrap; int count = this.count; final byte[] ENCODING_TABLE = encodingTable; int remaining = source.remaining(); while (remaining > 0) { // Unrolled state machine for performance (resumes and executes all states in one iteration) int require = 4 - state; require = wrap && (count >= 72) ? require + 2 : require; if (target.remaining() < require) { break; } // ( 6 | 2) (4 | 4) (2 | 6) int b = source.get() & 0xFF; if (state == 0) { target.put(ENCODING_TABLE[b >>> 2]); last = (b & 0x3) << 4; state++; if (--remaining <= 0) { break; } b = source.get() & 0xFF; } if (state == 1) { target.put(ENCODING_TABLE[last | (b >>> 4)]); last = (b & 0x0F) << 2; state++; if (--remaining <= 0) { break; } b = source.get() & 0xFF; } if (state == 2) { target.put(ENCODING_TABLE[last | (b >>> 6)]); target.put(ENCODING_TABLE[b & 0x3F]); last = state = 0; remaining--; } if (wrap) { count += 4; if (count >= 76) { count = 0; target.putShort((short)0x0D0A); } } } this.count = count; this.last = last; this.state = state; this.lastPos = source.position(); } /** * Encodes bytes read from source and writes them in base64 format to target. If the source limit is hit, this * method will return and save the current state, such that future calls can resume the encoding process. * In addition, if the target does not have the capacity to fit an entire quad of bytes, this method will also * return and save state for subsequent calls to this method. Once all bytes have been encoded to the target, * {@link #complete(byte[], int)} should be called to add the necessary padding characters. In order to * determine the last read position, the {@link #getLastInputPosition()} can be used. * *

Note that the limit values are not lengths, they are positions similar to * {@link java.nio.ByteBuffer#limit()}. To calculate a length simply subtract position from limit.

* *

         *  Encoder encoder = FlexBase64.createEncoder(false);
         *  byte[] outBuffer = new byte[10];
         *  // Encode "ell"
         *  int outPosition = encoder.encode("hello".getBytes("US-ASCII"), 1, 4, outBuffer, 5, 10);
         *  // Prints "9 : ZWxs"
         *  System.out.println(outPosition + " : " + new String(outBuffer, 0, 5, outPosition - 5));
         * 
* * @param source the byte array to read from * @param pos ths position in the byte array to start reading from * @param limit the position in the byte array that is after the end of the source data * @param target the byte array to write base64 bytes to * @param opos the position to start writing to the target array at * @param olimit the position in the target byte array that makes the end of the writable area (exclusive) * @return the position in the target array immediately following the last byte written */ public int encode(byte[] source, int pos, int limit, byte[] target, int opos, int olimit) { if (target == null) throw new IllegalStateException(); int last = this.last; int state = this.state; int count = this.count; boolean wrap = this.wrap; final byte[] ENCODING_TABLE = encodingTable; while (limit > pos) { // Unrolled state machine for performance (resumes and executes all states in one iteration) int require = 4 - state; require = wrap && count >= 72 ? require + 2 : require; if ((require + opos) > olimit) { break; } // ( 6 | 2) (4 | 4) (2 | 6) int b = source[pos++] & 0xFF; if (state == 0) { target[opos++] = ENCODING_TABLE[b >>> 2]; last = (b & 0x3) << 4; state++; if (pos >= limit) { break; } b = source[pos++] & 0xFF; } if (state == 1) { target[opos++] = ENCODING_TABLE[last | (b >>> 4)]; last = (b & 0x0F) << 2; state++; if (pos >= limit) { break; } b = source[pos++] & 0xFF; } if (state == 2) { target[opos++] = ENCODING_TABLE[last | (b >>> 6)]; target[opos++] = ENCODING_TABLE[b & 0x3F]; last = state = 0; } if (wrap) { count += 4; if (count >= 76) { count = 0; target[opos++] = 0x0D; target[opos++] = 0x0A; } } } this.count = count; this.last = last; this.state = state; this.lastPos = pos; return opos; } private static String encodeString(byte[] source, int pos, int limit, boolean wrap, boolean url) { int olimit = (limit - pos); int remainder = olimit % 3; olimit = (olimit + (remainder == 0 ? 0 : 3 - remainder)) / 3 * 4; olimit += (wrap ? (olimit / 76) * 2 + 2 : 0); char[] target = new char[olimit]; int opos = 0; int last = 0; int count = 0; int state = 0; final byte[] ENCODING_TABLE = url ? URL_ENCODING_TABLE : STANDARD_ENCODING_TABLE; while (limit > pos) { // ( 6 | 2) (4 | 4) (2 | 6) int b = source[pos++] & 0xFF; target[opos++] = (char) ENCODING_TABLE[b >>> 2]; last = (b & 0x3) << 4; if (pos >= limit) { state = 1; break; } b = source[pos++] & 0xFF; target[opos++] = (char) ENCODING_TABLE[last | (b >>> 4)]; last = (b & 0x0F) << 2; if (pos >= limit) { state = 2; break; } b = source[pos++] & 0xFF; target[opos++] = (char) ENCODING_TABLE[last | (b >>> 6)]; target[opos++] = (char) ENCODING_TABLE[b & 0x3F]; if (wrap) { count += 4; if (count >= 76) { count = 0; target[opos++] = 0x0D; target[opos++] = 0x0A; } } } complete(target, opos, state, last, wrap, url); try { // Eliminate copying on Open/Oracle JDK if (STRING_CONSTRUCTOR != null) { return STRING_CONSTRUCTOR.newInstance(target, Boolean.TRUE); } } catch (Exception e) { // Ignoring on purpose } return new String(target); } private static byte[] encodeBytes(byte[] source, int pos, int limit, boolean wrap, boolean url) { int olimit = (limit - pos); int remainder = olimit % 3; olimit = (olimit + (remainder == 0 ? 0 : 3 - remainder)) / 3 * 4; olimit += (wrap ? (olimit / 76) * 2 + 2 : 0); byte[] target = new byte[olimit]; int opos = 0; int count = 0; int last = 0; int state = 0; final byte[] ENCODING_TABLE = url ? URL_ENCODING_TABLE : STANDARD_ENCODING_TABLE; while (limit > pos) { // ( 6 | 2) (4 | 4) (2 | 6) int b = source[pos++] & 0xFF; target[opos++] = ENCODING_TABLE[b >>> 2]; last = (b & 0x3) << 4; if (pos >= limit) { state = 1; break; } b = source[pos++] & 0xFF; target[opos++] = ENCODING_TABLE[last | (b >>> 4)]; last = (b & 0x0F) << 2; if (pos >= limit) { state = 2; break; } b = source[pos++] & 0xFF; target[opos++] = ENCODING_TABLE[last | (b >>> 6)]; target[opos++] = ENCODING_TABLE[b & 0x3F]; if (wrap) { count += 4; if (count >= 76) { count = 0; target[opos++] = 0x0D; target[opos++] = 0x0A; } } } complete(target, opos, state, last, wrap, url); return target; } private static String encodeString(ByteBuffer source, boolean wrap, boolean url) { int remaining = source.remaining(); int remainder = remaining % 3; int olimit = (remaining + (remainder == 0 ? 0 : 3 - remainder)) / 3 * 4; olimit += (wrap ? olimit / 76 * 2 + 2 : 0); char[] target = new char[olimit]; int opos = 0; int last = 0; int state = 0; int count = 0; final byte[] ENCODING_TABLE = url ? URL_ENCODING_TABLE : STANDARD_ENCODING_TABLE; while (remaining > 0) { // ( 6 | 2) (4 | 4) (2 | 6) int b = source.get() & 0xFF; target[opos++] = (char) ENCODING_TABLE[b >>> 2]; last = (b & 0x3) << 4; if (--remaining <= 0) { state = 1; break; } b = source.get() & 0xFF; target[opos++] = (char) ENCODING_TABLE[last | (b >>> 4)]; last = (b & 0x0F) << 2; if (--remaining <= 0) { state = 2; break; } b = source.get() & 0xFF; target[opos++] = (char) ENCODING_TABLE[last | (b >>> 6)]; target[opos++] = (char) ENCODING_TABLE[b & 0x3F]; remaining--; if (wrap) { count += 4; if (count >= 76) { count = 0; target[opos++] = 0x0D; target[opos++] = 0x0A; } } } complete(target, opos, state, last, wrap, url); try { // Eliminate copying on Open/Oracle JDK if (STRING_CONSTRUCTOR != null) { return STRING_CONSTRUCTOR.newInstance(target, Boolean.TRUE); } } catch (Exception e) { // Ignoring on purpose } return new String(target); } /** * Gets the last position where encoding left off in the last byte array that was used. * If the target for encoded content does not have the necessary capacity, this method should be used to * determine where to start from on subsequent reads. * * @return the last known read position */ public int getLastInputPosition() { return lastPos; } /** * Completes an encoding session by writing out the necessary padding. This is essential to complying * with the Base64 format. This method will write at most 4 or 2 bytes starting at pos,depending on * whether or not wrapping is enabled. * *

         *  Encoder encoder = FlexBase64.createEncoder(false);
         *  byte[] outBuffer = new byte[13];
         *
         *  // Encodes "ello"
         *  int outPosition = encoder.encode("hello".getBytes("US-ASCII"), 0, 4, outBuffer, 5, 13);
         *  outPosition = encoder.complete(outBuffer, outPosition);
         *
         *  // Prints "13 : aGVsbA=="
         *  System.out.println(outPosition + " : " + new String(outBuffer, 0, 5, outPosition - 5));
         * 
* * @param target the byte array to write to * @param pos the position to start writing at * @return the position after the last byte written */ public int complete(byte[] target, int pos) { if (state > 0) { target[pos++] = encodingTable[last]; for (int i = state; i < 3; i++) { target[pos++] = (byte)'='; } last = state = 0; } if (wrap) { target[pos++] = 0x0D; target[pos++] = 0x0A; } return pos; } private static int complete(char[] target, int pos, int state, int last, boolean wrap, boolean url) { if (state > 0) { target[pos++] = (char) (url ? URL_ENCODING_TABLE : STANDARD_ENCODING_TABLE)[last]; for (int i = state; i < 3; i++) { target[pos++] = '='; } } if (wrap) { target[pos++] = 0x0D; target[pos++] = 0x0A; } return pos; } private static int complete(byte[] target, int pos, int state, int last, boolean wrap, boolean url) { if (state > 0) { target[pos++] = (url ? URL_ENCODING_TABLE : STANDARD_ENCODING_TABLE)[last]; for (int i = state; i < 3; i++) { target[pos++] = '='; } } if (wrap) { target[pos++] = 0x0D; target[pos++] = 0x0A; } return pos; } /** * Completes an encoding session by writing out the necessary padding. This is essential to complying * with the Base64 format. This method will write at most 4 or 2 bytes, depending on whether or not wrapping * is enabled. * * @param target the byte buffer to write to */ public void complete(ByteBuffer target) { if (state > 0) { target.put(encodingTable[last]); for (int i = state; i < 3; i++) { target.put((byte)'='); } last = state = 0; } if (wrap) { target.putShort((short)0x0D0A); } count = 0; } } /** * Controls the decoding process. */ public static final class Decoder { private int state; private int last; private int lastPos; private final byte[] decodingTable; private static final int SKIP = 0x0FD00; private static final int MARK = 0x0FE00; private static final int DONE = 0x0FF00; private static final int ERROR = 0xF0000; private Decoder(boolean url) { this.decodingTable = url ? URL_DECODING_TABLE : STANDARD_DECODING_TABLE; } private int nextByte(ByteBuffer buffer, int state, int last, boolean ignoreErrors) throws IOException { return nextByte(buffer.get() & 0xFF, state, last, ignoreErrors); } private int nextByte(Object source, int pos, int state, int last, boolean ignoreErrors) throws IOException { int c; if (source instanceof byte[]) { c = ((byte[])source)[pos] & 0xFF; } else if (source instanceof String) { c = ((String)source).charAt(pos) & 0xFF; } else { throw new IllegalArgumentException(); } return nextByte(c, state, last, ignoreErrors); } private int nextByte(int c, int state, int last, boolean ignoreErrors) throws IOException { if (last == MARK) { if (c != '=') { throw new IOException("Expected padding character"); } return DONE; } if (c == '=') { if (state == 2) { return MARK; } else if (state == 3) { return DONE; } else { throw new IOException("Unexpected padding character"); } } if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { return SKIP; } if (c < 43 || c > 122) { if (ignoreErrors) { return ERROR; } throw new IOException("Invalid base64 character encountered: " + c); } int b = (decodingTable[c - 43] & 0xFF) - 1; if (b < 0) { if (ignoreErrors) { return ERROR; } throw new IOException("Invalid base64 character encountered: " + c); } return b; } /** * Decodes one Base64 byte buffer into another. This method will return and save state * if the target does not have the required capacity. Subsequent calls with a new target will * resume reading where it last left off (the source buffer's position). Similarly not all of the * source data need be available, this method can be repetitively called as data is made available. * *

The decoder will skip white space, but will error if it detects corruption.

* * @param source the byte buffer to read encoded data from * @param target the byte buffer to write decoded data to * @throws IOException if the encoded data is corrupted */ public void decode(ByteBuffer source, ByteBuffer target) throws IOException { if (target == null) throw new IllegalStateException(); int last = this.last; int state = this.state; int remaining = source.remaining(); int targetRemaining = target.remaining(); int b = 0; while (remaining-- > 0 && targetRemaining > 0) { b = nextByte(source, state, last, false); if (b == MARK) { last = MARK; if (--remaining <= 0) { break; } b = nextByte(source, state, last, false); } if (b == DONE) { last = state = 0; break; } if (b == SKIP) { continue; } // ( 6 | 2) (4 | 4) (2 | 6) if (state == 0) { last = b << 2; state++; if (remaining-- <= 0) { break; } b = nextByte(source, state, last, false); if ((b & 0xF000) != 0) { source.position(source.position() - 1); continue; } } if (state == 1) { target.put((byte)(last | (b >>> 4))); last = (b & 0x0F) << 4; state++; if (remaining-- <= 0 || --targetRemaining <= 0) { break; } b = nextByte(source, state, last, false); if ((b & 0xF000) != 0) { source.position(source.position() - 1); continue; } } if (state == 2) { target.put((byte) (last | (b >>> 2))); last = (b & 0x3) << 6; state++; if (remaining-- <= 0 || --targetRemaining <= 0) { break; } b = nextByte(source, state, last, false); if ((b & 0xF000) != 0) { source.position(source.position() - 1); continue; } } if (state == 3) { target.put((byte)(last | b)); last = state = 0; targetRemaining--; } } if (remaining > 0) { drain(source, b, state, last); } this.last = last; this.state = state; this.lastPos = source.position(); } private void drain(ByteBuffer source, int b, int state, int last) { while (b != DONE && source.remaining() > 0) { try { b = nextByte(source, state, last, true); } catch (IOException e) { b = 0; } if (b == MARK) { last = MARK; continue; } // Not WS/pad if ((b & 0xF000) == 0) { source.position(source.position() - 1); break; } } if (b == DONE) { // SKIP one line of trailing whitespace while (source.remaining() > 0) { b = source.get(); if (b == '\n') { break; } else if (b != ' ' && b != '\t' && b != '\r') { source.position(source.position() - 1); break; } } } } private int drain(Object source, int pos, int limit, int b, int state, int last) { while (b != DONE && limit > pos) { try { b = nextByte(source, pos++, state, last, true); } catch (IOException e) { b = 0; } if (b == MARK) { last = MARK; continue; } // Not WS/pad if ((b & 0xF000) == 0) { pos--; break; } } if (b == DONE) { // SKIP one line of trailing whitespace while (limit > pos) { if (source instanceof byte[]) { b = ((byte[])source)[pos++] & 0xFF; } else if (source instanceof String) { b = ((String)source).charAt(pos++) & 0xFF; } else { throw new IllegalArgumentException(); } if (b == '\n') { break; } else if (b != ' ' && b != '\t' && b != '\r') { pos--; break; } } } return pos; } private int decode(Object source, int sourcePos, int sourceLimit, byte[] target, int targetPos, int targetLimit) throws IOException { if (target == null) throw new IllegalStateException(); int last = this.last; int state = this.state; int pos = sourcePos; int opos = targetPos; int limit = sourceLimit; int olimit = targetLimit; int b = 0; while (limit > pos && olimit > opos) { b = nextByte(source, pos++, state, last, false); if (b == MARK) { last = MARK; if (pos >= limit) { break; } b = nextByte(source, pos++, state, last, false); } if (b == DONE) { last = state = 0; break; } if (b == SKIP) { continue; } // ( 6 | 2) (4 | 4) (2 | 6) if (state == 0) { last = b << 2; state++; if (pos >= limit) { break; } b = nextByte(source, pos++, state, last, false); if ((b & 0xF000) != 0) { pos--; continue; } } if (state == 1) { target[opos++] = ((byte)(last | (b >>> 4))); last = (b & 0x0F) << 4; state++; if (pos >= limit || opos >= olimit) { break; } b = nextByte(source, pos++, state, last, false); if ((b & 0xF000) != 0) { pos--; continue; } } if (state == 2) { target[opos++] = ((byte) (last | (b >>> 2))); last = (b & 0x3) << 6; state++; if (pos >= limit || opos >= olimit) { break; } b = nextByte(source, pos++, state, last, false); if ((b & 0xF000) != 0) { pos--; continue; } } if (state == 3) { target[opos++] = ((byte)(last | b)); last = state = 0; } } if (limit > pos) { pos = drain(source, pos, limit, b, state, last); } this.last = last; this.state = state; this.lastPos = pos; return opos; } /** * Gets the last position where decoding left off in the last byte array that was used for reading. * If the target for decoded content does not have the necessary capacity, this method should be used to * determine where to start from on subsequent decode calls. * * @return the last known read position */ public int getLastInputPosition() { return lastPos; } /** * Decodes one Base64 byte array into another byte array. If the source limit is hit, this method will * return and save the current state, such that future calls can resume the decoding process. Likewise, * if the target does not have the capacity, this method will also return and save state for subsequent * calls to this method. * *

When multiple calls are made, {@link #getLastInputPosition()} should be used to determine what value * should be set for sourcePos. Likewise, the returned target position should be used as the targetPos * in a subsequent call.

* *

The decoder will skip white space, but will error if it detects corruption.

* * @param source a Base64 encoded string to decode data from * @param sourcePos the position in the source array to start decoding from * @param sourceLimit the position in the source array to halt decoding when hit (exclusive) * @param target the byte buffer to write decoded data to * @param targetPos the position in the target byte array to begin writing at * @param targetLimit the position in the target byte array to halt writing (exclusive) * @throws IOException if the encoded data is corrupted * @return the position in the target array immediately following the last byte written * */ public int decode(String source, int sourcePos, int sourceLimit, byte[] target, int targetPos, int targetLimit) throws IOException { return decode((Object)source, sourcePos, sourceLimit, target, targetPos, targetLimit); } /** * Decodes a Base64 encoded string into the passed byte array. This method will return and save state * if the target does not have the required capacity. Subsequent calls with a new target will * resume reading where it last left off (the source buffer's position). Similarly not all of the * source data need be available, this method can be repetitively called as data is made available. * *

Since this method variant assumes a position of 0 and a limit of the item length, * repeated calls will need fresh source and target values. {@link #decode(String, int, int, byte[], int, int)} * would be a better fit if you need reuse

* *

The decoder will skip white space, but will error if it detects corruption.

* * @param source a base64 encoded string to decode from * @param target a byte array to write to * @throws java.io.IOException if the base64 content is malformed * @return output position following the last written byte */ public int decode(String source, byte[] target) throws IOException { return decode(source, 0, source.length(), target, 0, target.length); } /** * Decodes one Base64 byte array into another byte array. If the source limit is hit, this method will * return and save the current state, such that future calls can resume the decoding process. Likewise, * if the target does not have the capacity, this method will also return and save state for subsequent * calls to this method. * *

When multiple calls are made, {@link #getLastInputPosition()} should be used to determine what value * should be set for sourcePos. Likewise, the returned target position should be used as the targetPos * in a subsequent call.

* *

The decoder will skip white space, but will error if it detects corruption.

* *

         *  Decoder decoder = FlexBase64.createDecoder();
         *  byte[] outBuffer = new byte[10];
         *  byte[] bytes = "aGVsbG8=".getBytes("US-ASCII");
         *  // Decode only 2 bytes
         *  int outPosition = decoder.decode(bytes, 0, 8, outBuffer, 5, 7);
         *  // Resume where we left off and get the rest
         *  outPosition = decoder.decode(bytes, decoder.getLastInputPosition(), 8, outBuffer, outPosition, 10);
         *  // Prints "10 : Hello"
         *  System.out.println(outPosition + " : " + new String(outBuffer, 0, 5, outPosition - 5));
         * 
* * * @param source the byte array to read encoded data from * @param sourcePos the position in the source array to start decoding from * @param sourceLimit the position in the source array to halt decoding when hit (exclusive) * @param target the byte buffer to write decoded data to * @param targetPos the position in the target byte array to begin writing at * @param targetLimit the position in the target byte array to halt writing (exclusive) * @throws IOException if the encoded data is corrupted * @return the position in the target array immediately following the last byte written */ public int decode(byte[] source, int sourcePos, int sourceLimit, byte[] target, int targetPos, int targetLimit) throws IOException { return decode((Object)source, sourcePos, sourceLimit, target, targetPos, targetLimit); } private static ByteBuffer decode(String source, boolean url) throws IOException { int remainder = source.length() % 4; int size = ((source.length() / 4) + (remainder == 0 ? 0 : 4 - remainder)) * 3; byte[] buffer = new byte[size]; int actual = new Decoder(url).decode(source, 0, source.length(), buffer, 0, size); return ByteBuffer.wrap(buffer, 0, actual); } private static ByteBuffer decode(byte[] source, int off, int limit, boolean url) throws IOException { int len = limit - off; int remainder = len % 4; int size = ((len / 4) + (remainder == 0 ? 0 : 4 - remainder)) * 3; byte[] buffer = new byte[size]; int actual = new Decoder(url).decode(source, off, limit, buffer, 0, size); return ByteBuffer.wrap(buffer, 0, actual); } private static ByteBuffer decode(ByteBuffer source, boolean url) throws IOException { int len = source.remaining(); int remainder = len % 4; int size = ((len / 4) + (remainder == 0 ? 0 : 4 - remainder)) * 3; ByteBuffer buffer = ByteBuffer.allocate(size); new Decoder(url).decode(source, buffer); buffer.flip(); return buffer; } } /** * An input stream which decodes bytes as they are read from a stream with Base64 encoded data. */ public static class DecoderInputStream extends InputStream { private final InputStream input; private final byte[] buffer; private final Decoder decoder = createDecoder(); private int pos = 0; private int limit = 0; private byte[] one; private DecoderInputStream(InputStream input) { this(input, 8192); } private DecoderInputStream(InputStream input, int bufferSize) { this.input = input; buffer = new byte[bufferSize]; } private int fill() throws IOException { byte[] buffer = this.buffer; int read = input.read(buffer, 0, buffer.length); pos = 0; limit = read; return read; } /** * {@inheritDoc} */ @Override public int read(byte[] b, int off, int len) throws IOException { for (;;) { byte[] source = buffer; int pos = this.pos; int limit = this.limit; boolean setPos = true; if (pos >= limit) { if (len > source.length) { source = new byte[len]; limit = input.read(source, 0, len); pos = 0; setPos = false; } else { limit = fill(); pos = 0; } if (limit == -1) { return -1; } } int requested = len + pos; limit = limit > requested ? requested : limit; int read = decoder.decode(source, pos, limit, b, off, off+len) - off; if (setPos) { this.pos = decoder.getLastInputPosition(); } if (read > 0) { return read; } } } /** * {@inheritDoc} */ @Override public int read() throws IOException { byte[] one = this.one; if (one == null) { one = this.one = new byte[1]; } int read = this.read(one, 0, 1); return read > 0 ? one[0] & 0xFF : -1; } /** * {@inheritDoc} */ @Override public void close() throws IOException { input.close(); } } /** * An input stream which encodes bytes as they are read from a stream. */ public static class EncoderInputStream extends InputStream { private final InputStream input; private final byte[] buffer; private final byte[] overflow = new byte[6]; private int overflowPos; private int overflowLimit; private final Encoder encoder; private int pos = 0; private int limit = 0; private byte[] one; private boolean complete; private EncoderInputStream(InputStream input) { this(input, 8192, true, false); } private EncoderInputStream(InputStream input, int bufferSize, boolean wrap, boolean url) { this.input = input; buffer = new byte[bufferSize]; this.encoder = new Encoder(wrap, url); } private int fill() throws IOException { byte[] buffer = this.buffer; int read = input.read(buffer, 0, buffer.length); pos = 0; limit = read; return read; } /** * {@inheritDoc} */ @Override public int read() throws IOException { byte[] one = this.one; if (one == null) { one = this.one = new byte[1]; } int read = this.read(one, 0, 1); return read > 0 ? one[0] & 0xFF : -1; } /** * {@inheritDoc} */ @Override public int read(byte[] b, int off, int len) throws IOException { byte[] buffer = this.buffer; byte[] overflow = this.overflow; int overflowPos = this.overflowPos; int overflowLimit = this.overflowLimit; boolean complete = this.complete; boolean wrap = encoder.wrap; int copy = 0; if (overflowPos < overflowLimit) { copy = copyOverflow(b, off, len, overflow, overflowPos, overflowLimit); if (len <= copy || complete) { return copy; } len -= copy; off += copy; } else if (complete) { return -1; } for (;;) { byte[] source = buffer; int pos = this.pos; int limit = this.limit; boolean setPos = true; if (pos >= limit) { if (len > source.length) { // If requested length exceeds buffer, allocate a new temporary buffer that will be // one block less than an exact encoded output. This is to handle partial quad carryover // from an earlier read. int adjust = (len / 4 * 3) - 3; if (wrap) { adjust -= adjust / 76 * 2 + 2; } source = new byte[adjust]; limit = input.read(source, 0, adjust); pos = 0; setPos = false; } else { limit = fill(); pos = 0; } if (limit <= 0) { this.complete = true; if (len < (wrap ? 4 : 2)) { overflowLimit = encoder.complete(overflow, 0); this.overflowLimit = overflowLimit; int ret = copyOverflow(b, off, len, overflow, 0, overflowLimit) + copy; return ret == 0 ? -1 : ret; } int ret = encoder.complete(b, off) - off + copy; return ret == 0 ? -1 : ret; } } if (len < (wrap ? 6 : 4)) { overflowLimit = encoder.encode(source, pos, limit, overflow, 0, overflow.length); this.overflowLimit = overflowLimit; this.pos = encoder.getLastInputPosition(); return copyOverflow(b, off, len, overflow, 0, overflowLimit) + copy; } int read = encoder.encode(source, pos, limit, b, off, off+len) - off; if (setPos) { this.pos = encoder.getLastInputPosition(); } if (read > 0) { return read + copy; } } } private int copyOverflow(byte[] b, int off, int len, byte[] overflow, int pos, int limit) { limit -= pos; len = limit <= len ? limit : len; System.arraycopy(overflow, pos, b, off, len); this.overflowPos = pos + len; return len; } } /** * An output stream which base64 encodes all passed data and writes it to the wrapped target output stream. * *

Closing this stream will result in the correct padding sequence being written. However, as * required by the OutputStream contract, the wrapped stream will also be closed. If this is not desired, * the {@link #complete()} method should be used.

*/ public static class EncoderOutputStream extends OutputStream { private final OutputStream output; private final byte[] buffer; private final Encoder encoder; private int pos = 0; private byte[] one; private EncoderOutputStream(OutputStream output) { this(output, 8192, true); } private EncoderOutputStream(OutputStream output, int bufferSize, boolean wrap) { this.output = output; this.buffer = new byte[bufferSize]; this.encoder = createEncoder(wrap); } /** * {@inheritDoc} */ @Override public void write(byte[] b, int off, int len) throws IOException { byte[] buffer = this.buffer; Encoder encoder = this.encoder; int pos = this.pos; int limit = off + len; int ipos = off; while (ipos < limit) { pos = encoder.encode(b, ipos, limit, buffer, pos, buffer.length); int last = encoder.getLastInputPosition(); if (last == ipos || pos >= buffer.length) { output.write(buffer, 0, pos); pos = 0; } ipos = last; } this.pos = pos; } /** * {@inheritDoc} */ @Override public void write(int b) throws IOException { byte[] one = this.one; if (one == null) { this.one = one = new byte[1]; } one[0] = (byte)b; write(one, 0, 1); } /** * {@inheritDoc} */ @Override public void flush() throws IOException { OutputStream output = this.output; output.write(buffer, 0, pos); output.flush(); } /** * Completes the stream, writing out base64 padding characters if needed. * * @throws IOException if the underlying stream throws one */ public void complete() throws IOException { OutputStream output = this.output; byte[] buffer = this.buffer; int pos = this.pos; boolean completed = false; if (buffer.length - pos >= (encoder.wrap ? 2 : 4)) { this.pos = encoder.complete(buffer, pos); completed = true; } flush(); if (!completed) { int len = encoder.complete(buffer, 0); output.write(buffer, 0, len); output.flush(); } } /** * {@inheritDoc} */ @Override public void close() throws IOException { try { complete(); } catch (IOException e) { // eat } try { output.flush(); } catch (IOException e) { // eat } output.close(); } } /** * An output stream which decodes base64 data written to it, and writes the decoded output to the * wrapped inner stream. */ public static class DecoderOutputStream extends OutputStream { private final OutputStream output; private final byte[] buffer; private final Decoder decoder; private int pos = 0; private byte[] one; private DecoderOutputStream(OutputStream output) { this(output, 8192); } private DecoderOutputStream(OutputStream output, int bufferSize) { this.output = output; this.buffer = new byte[bufferSize]; this.decoder = createDecoder(); } /** * {@inheritDoc} */ @Override public void write(byte[] b, int off, int len) throws IOException { byte[] buffer = this.buffer; Decoder decoder = this.decoder; int pos = this.pos; int limit = off + len; int ipos = off; while (ipos < limit) { pos = decoder.decode(b, ipos, limit, buffer, pos, buffer.length); int last = decoder.getLastInputPosition(); if (last == ipos || pos >= buffer.length) { output.write(buffer, 0, pos); pos = 0; } ipos = last; } this.pos = pos; } /** * {@inheritDoc} */ @Override public void write(int b) throws IOException { byte[] one = this.one; if (one == null) { this.one = one = new byte[1]; } one[0] = (byte)b; write(one, 0, 1); } /** * {@inheritDoc} */ @Override public void flush() throws IOException { OutputStream output = this.output; output.write(buffer, 0, pos); output.flush(); } /** * {@inheritDoc} */ @Override public void close() throws IOException { try { flush(); } catch (IOException e) { // eat } try { output.flush(); } catch (IOException e) { // eat } output.close(); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/HeaderMap.java000066400000000000000000000656541420065311100260220ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.util.AbstractCollection; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; /** * An optimized array-backed header map. * * @author David M. Lloyd */ public final class HeaderMap implements Iterable { private Object[] table; private int size; private Collection headerNames; public HeaderMap() { table = new Object[16]; } private HeaderValues getEntry(final HttpString headerName) { if (headerName == null) { return null; } final int hc = headerName.hashCode(); final int idx = hc & (table.length - 1); final Object o = table[idx]; if (o == null) { return null; } HeaderValues headerValues; if (o instanceof HeaderValues) { headerValues = (HeaderValues) o; if (! headerName.equals(headerValues.key)) { return null; } return headerValues; } else { final HeaderValues[] row = (HeaderValues[]) o; for (int i = 0; i < row.length; i++) { headerValues = row[i]; if (headerValues != null && headerName.equals(headerValues.key)) { return headerValues; } } return null; } } private HeaderValues getEntry(final String headerName) { if (headerName == null) { return null; } final int hc = HttpString.hashCodeOf(headerName); final int idx = hc & (table.length - 1); final Object o = table[idx]; if (o == null) { return null; } HeaderValues headerValues; if (o instanceof HeaderValues) { headerValues = (HeaderValues) o; if (! headerValues.key.equalToString(headerName)) { return null; } return headerValues; } else { final HeaderValues[] row = (HeaderValues[]) o; for (int i = 0; i < row.length; i++) { headerValues = row[i]; if (headerValues != null && headerValues.key.equalToString(headerName)) { return headerValues; } } return null; } } private HeaderValues removeEntry(final HttpString headerName) { if (headerName == null) { return null; } final int hc = headerName.hashCode(); final Object[] table = this.table; final int idx = hc & (table.length - 1); final Object o = table[idx]; if (o == null) { return null; } HeaderValues headerValues; if (o instanceof HeaderValues) { headerValues = (HeaderValues) o; if (! headerName.equals(headerValues.key)) { return null; } table[idx] = null; size --; return headerValues; } else { final HeaderValues[] row = (HeaderValues[]) o; for (int i = 0; i < row.length; i++) { headerValues = row[i]; if (headerValues != null && headerName.equals(headerValues.key)) { row[i] = null; size --; return headerValues; } } return null; } } private HeaderValues removeEntry(final String headerName) { if (headerName == null) { return null; } final int hc = HttpString.hashCodeOf(headerName); final Object[] table = this.table; final int idx = hc & (table.length - 1); final Object o = table[idx]; if (o == null) { return null; } HeaderValues headerValues; if (o instanceof HeaderValues) { headerValues = (HeaderValues) o; if (! headerValues.key.equalToString(headerName)) { return null; } table[idx] = null; size --; return headerValues; } else { final HeaderValues[] row = (HeaderValues[]) o; for (int i = 0; i < row.length; i++) { headerValues = row[i]; if (headerValues != null && headerValues.key.equalToString(headerName)) { row[i] = null; size --; return headerValues; } } return null; } } private void resize() { final int oldLen = table.length; if (oldLen == 0x40000000) { return; } assert Integer.bitCount(oldLen) == 1; Object[] newTable = Arrays.copyOf(table, oldLen << 1); table = newTable; for (int i = 0; i < oldLen; i ++) { if (newTable[i] == null) { continue; } if (newTable[i] instanceof HeaderValues) { HeaderValues e = (HeaderValues) newTable[i]; if ((e.key.hashCode() & oldLen) != 0) { newTable[i] = null; newTable[i + oldLen] = e; } continue; } HeaderValues[] oldRow = (HeaderValues[]) newTable[i]; HeaderValues[] newRow = oldRow.clone(); int rowLen = oldRow.length; newTable[i + oldLen] = newRow; HeaderValues item; for (int j = 0; j < rowLen; j ++) { item = oldRow[j]; if (item != null) { if ((item.key.hashCode() & oldLen) != 0) { oldRow[j] = null; } else { newRow[j] = null; } } } } } private HeaderValues getOrCreateEntry(final HttpString headerName) { if (headerName == null) { return null; } final int hc = headerName.hashCode(); final Object[] table = this.table; final int length = table.length; final int idx = hc & (length - 1); final Object o = table[idx]; HeaderValues headerValues; if (o == null) { if (size >= length >> 1) { resize(); return getOrCreateEntry(headerName); } headerValues = new HeaderValues(headerName); table[idx] = headerValues; size++; return headerValues; } return getOrCreateNonEmpty(headerName, table, length, idx, o); } private HeaderValues getOrCreateNonEmpty(HttpString headerName, Object[] table, int length, int idx, Object o) { HeaderValues headerValues; if (o instanceof HeaderValues) { headerValues = (HeaderValues) o; if (! headerName.equals(headerValues.key)) { if (size >= length >> 1) { resize(); return getOrCreateEntry(headerName); } size++; final HeaderValues[] row = { headerValues, new HeaderValues(headerName), null, null }; table[idx] = row; return row[1]; } return headerValues; } else { final HeaderValues[] row = (HeaderValues[]) o; int empty = -1; for (int i = 0; i < row.length; i++) { headerValues = row[i]; if (headerValues != null) { if (headerName.equals(headerValues.key)) { return headerValues; } } else if (empty == -1) { empty = i; } } if (size >= length >> 1) { resize(); return getOrCreateEntry(headerName); } size++; headerValues = new HeaderValues(headerName); if (empty != -1) { row[empty] = headerValues; } else { if (row.length >= 16) { throw new SecurityException("Excessive collisions"); } final HeaderValues[] newRow = Arrays.copyOf(row, row.length + 3); newRow[row.length] = headerValues; table[idx] = newRow; } return headerValues; } } // get public HeaderValues get(final HttpString headerName) { return getEntry(headerName); } public HeaderValues get(final String headerName) { return getEntry(headerName); } public String getFirst(HttpString headerName) { HeaderValues headerValues = getEntry(headerName); if (headerValues == null) return null; return headerValues.getFirst(); } public String getFirst(String headerName) { HeaderValues headerValues = getEntry(headerName); if (headerValues == null) return null; return headerValues.getFirst(); } public String get(HttpString headerName, int index) throws IndexOutOfBoundsException { if (headerName == null) { return null; } final HeaderValues headerValues = getEntry(headerName); if (headerValues == null) { return null; } return headerValues.get(index); } public String get(String headerName, int index) throws IndexOutOfBoundsException { if (headerName == null) { return null; } final HeaderValues headerValues = getEntry(headerName); if (headerValues == null) { return null; } return headerValues.get(index); } public String getLast(HttpString headerName) { if (headerName == null) { return null; } HeaderValues headerValues = getEntry(headerName); if (headerValues == null) return null; return headerValues.getLast(); } public String getLast(String headerName) { if (headerName == null) { return null; } HeaderValues headerValues = getEntry(headerName); if (headerValues == null) return null; return headerValues.getLast(); } // count public int count(HttpString headerName) { if (headerName == null) { return 0; } final HeaderValues headerValues = getEntry(headerName); if (headerValues == null) { return 0; } return headerValues.size(); } public int count(String headerName) { if (headerName == null) { return 0; } final HeaderValues headerValues = getEntry(headerName); if (headerValues == null) { return 0; } return headerValues.size(); } public int size() { return size; } // iterate /** * Do a fast iteration of this header map without creating any objects. * * @return an opaque iterating cookie, or -1 if no iteration is possible * * @see #fiNext(long) * @see #fiCurrent(long) */ public long fastIterate() { final Object[] table = this.table; final int len = table.length; int ri = 0; int ci; while (ri < len) { final Object item = table[ri]; if (item != null) { if (item instanceof HeaderValues) { return (long)ri << 32L; } else { final HeaderValues[] row = (HeaderValues[]) item; ci = 0; final int rowLen = row.length; while (ci < rowLen) { if (row[ci] != null) { return (long)ri << 32L | (ci & 0xffffffffL); } ci ++; } } } ri++; } return -1L; } /** * Do a fast iteration of this header map without creating any objects, only considering non-empty header values. * * @return an opaque iterating cookie, or -1 if no iteration is possible */ public long fastIterateNonEmpty() { final Object[] table = this.table; final int len = table.length; int ri = 0; int ci; while (ri < len) { final Object item = table[ri]; if (item != null) { if (item instanceof HeaderValues) { if(!((HeaderValues) item).isEmpty()) { return (long) ri << 32L; } } else { final HeaderValues[] row = (HeaderValues[]) item; ci = 0; final int rowLen = row.length; while (ci < rowLen) { if (row[ci] != null && !row[ci].isEmpty()) { return (long)ri << 32L | (ci & 0xffffffffL); } ci ++; } } } ri++; } return -1L; } /** * Find the next index in a fast iteration. * * @param cookie the previous cookie value * @return the next cookie value, or -1L if iteration is done */ public long fiNext(long cookie) { if (cookie == -1L) return -1L; final Object[] table = this.table; final int len = table.length; int ri = (int) (cookie >> 32); int ci = (int) cookie; Object item = table[ri]; if (item instanceof HeaderValues[]) { final HeaderValues[] row = (HeaderValues[]) item; final int rowLen = row.length; if (++ci >= rowLen) { ri ++; ci = 0; } else if (row[ci] != null) { return (long)ri << 32L | (ci & 0xffffffffL); } } else { ri ++; ci = 0; } while (ri < len) { item = table[ri]; if (item instanceof HeaderValues) { return (long)ri << 32L; } else if (item instanceof HeaderValues[]) { final HeaderValues[] row = (HeaderValues[]) item; final int rowLen = row.length; while (ci < rowLen) { if (row[ci] != null) { return (long)ri << 32L | (ci & 0xffffffffL); } ci ++; } } ci = 0; ri ++; } return -1L; } /** * Find the next non-empty index in a fast iteration. * * @param cookie the previous cookie value * @return the next cookie value, or -1L if iteration is done */ public long fiNextNonEmpty(long cookie) { if (cookie == -1L) return -1L; final Object[] table = this.table; final int len = table.length; int ri = (int) (cookie >> 32); int ci = (int) cookie; Object item = table[ri]; if (item instanceof HeaderValues[]) { final HeaderValues[] row = (HeaderValues[]) item; final int rowLen = row.length; if (++ci >= rowLen) { ri ++; ci = 0; } else if (row[ci] != null && !row[ci].isEmpty()) { return (long)ri << 32L | (ci & 0xffffffffL); } } else { ri ++; ci = 0; } while (ri < len) { item = table[ri]; if (item instanceof HeaderValues && !((HeaderValues) item).isEmpty()) { return (long)ri << 32L; } else if (item instanceof HeaderValues[]) { final HeaderValues[] row = (HeaderValues[]) item; final int rowLen = row.length; while (ci < rowLen) { if (row[ci] != null && !row[ci].isEmpty()) { return (long)ri << 32L | (ci & 0xffffffffL); } ci ++; } } ci = 0; ri ++; } return -1L; } /** * Return the value at the current index in a fast iteration. * * @param cookie the iteration cookie value * @return the values object at this position * @throws NoSuchElementException if the cookie value is invalid */ public HeaderValues fiCurrent(long cookie) { try { final Object[] table = this.table; int ri = (int) (cookie >> 32); int ci = (int) cookie; final Object item = table[ri]; if (item instanceof HeaderValues[]) { return ((HeaderValues[])item)[ci]; } else if (ci == 0) { return (HeaderValues) item; } else { throw new NoSuchElementException(); } } catch (RuntimeException e) { throw new NoSuchElementException(); } } public Iterable eachValue(final HttpString headerName) { if (headerName == null) { return Collections.emptyList(); } final HeaderValues entry = getEntry(headerName); if (entry == null) { return Collections.emptyList(); } return entry; } public Iterator iterator() { return new Iterator() { final Object[] table = HeaderMap.this.table; boolean consumed; int ri, ci; private HeaderValues _next() { for (;;) { if (ri >= table.length) { return null; } final Object o = table[ri]; if (o == null) { // zero-entry row ri++; ci = 0; consumed = false; continue; } if (o instanceof HeaderValues) { // one-entry row if (ci > 0 || consumed) { ri++; ci = 0; consumed = false; continue; } return (HeaderValues) o; } final HeaderValues[] row = (HeaderValues[]) o; final int len = row.length; if (ci >= len) { ri ++; ci = 0; consumed = false; continue; } if (consumed) { ci++; consumed = false; continue; } final HeaderValues headerValues = row[ci]; if (headerValues == null) { ci ++; continue; } return headerValues; } } public boolean hasNext() { return _next() != null; } public HeaderValues next() { final HeaderValues next = _next(); if (next == null) { throw new NoSuchElementException(); } consumed = true; return next; } public void remove() { } }; } public Collection getHeaderNames() { if (headerNames != null) { return headerNames; } return headerNames = new AbstractCollection() { public boolean contains(final Object o) { return o instanceof HttpString && getEntry((HttpString) o) != null; } public boolean add(final HttpString httpString) { getOrCreateEntry(httpString); return true; } public boolean remove(final Object o) { if (! (o instanceof HttpString)) return false; HttpString s = (HttpString) o; HeaderValues entry = getEntry(s); if (entry == null) { return false; } entry.clear(); return true; } public void clear() { HeaderMap.this.clear(); } public Iterator iterator() { final Iterator iterator = HeaderMap.this.iterator(); return new Iterator() { public boolean hasNext() { return iterator.hasNext(); } public HttpString next() { return iterator.next().getHeaderName(); } public void remove() { iterator.remove(); } }; } public int size() { return HeaderMap.this.size(); } }; } // add public HeaderMap add(HttpString headerName, String headerValue) { addLast(headerName, headerValue); return this; } public HeaderMap addFirst(final HttpString headerName, final String headerValue) { if (headerName == null) { throw new IllegalArgumentException("headerName is null"); } if (headerValue == null) { return this; } getOrCreateEntry(headerName).addFirst(headerValue); return this; } public HeaderMap addLast(final HttpString headerName, final String headerValue) { if (headerName == null) { throw new IllegalArgumentException("headerName is null"); } if (headerValue == null) { return this; } getOrCreateEntry(headerName).addLast(headerValue); return this; } public HeaderMap add(HttpString headerName, long headerValue) { add(headerName, Long.toString(headerValue)); return this; } public HeaderMap addAll(HttpString headerName, Collection headerValues) { if (headerName == null) { throw new IllegalArgumentException("headerName is null"); } if (headerValues == null || headerValues.isEmpty()) { return this; } getOrCreateEntry(headerName).addAll(headerValues); return this; } // put public HeaderMap put(HttpString headerName, String headerValue) { if (headerName == null) { throw new IllegalArgumentException("headerName is null"); } if (headerValue == null) { remove(headerName); return this; } final HeaderValues headerValues = getOrCreateEntry(headerName); headerValues.clear(); headerValues.add(headerValue); return this; } public HeaderMap put(HttpString headerName, long headerValue) { if (headerName == null) { throw new IllegalArgumentException("headerName is null"); } final HeaderValues entry = getOrCreateEntry(headerName); entry.clear(); entry.add(Long.toString(headerValue)); return this; } public HeaderMap putAll(HttpString headerName, Collection headerValues) { if (headerName == null) { throw new IllegalArgumentException("headerName is null"); } if (headerValues == null || headerValues.isEmpty()) { remove(headerName); return this; } final HeaderValues entry = getOrCreateEntry(headerName); entry.clear(); entry.addAll(headerValues); return this; } // clear public HeaderMap clear() { Arrays.fill(table, null); size = 0; return this; } // remove public Collection remove(HttpString headerName) { if (headerName == null) { return Collections.emptyList(); } final Collection values = removeEntry(headerName); return values != null ? values : Collections.emptyList(); } public Collection remove(String headerName) { if (headerName == null) { return Collections.emptyList(); } final Collection values = removeEntry(headerName); return values != null ? values : Collections.emptyList(); } // contains public boolean contains(HttpString headerName) { final HeaderValues headerValues = getEntry(headerName); if (headerValues == null) { return false; } final Object v = headerValues.value; if (v instanceof String) { return true; } final String[] list = (String[]) v; for (int i = 0; i < list.length; i++) { if (list[i] != null) { return true; } } return false; } public boolean contains(String headerName) { final HeaderValues headerValues = getEntry(headerName); if (headerValues == null) { return false; } final Object v = headerValues.value; if (v instanceof String) { return true; } final String[] list = (String[]) v; for (int i = 0; i < list.length; i++) { if (list[i] != null) { return true; } } return false; } // compare @Override public boolean equals(final Object o) { return o == this; } @Override public int hashCode() { return super.hashCode(); } @Override public String toString() { StringBuilder sb = new StringBuilder("{"); boolean first = true; for(HttpString name : getHeaderNames()) { if(first) { first = false; } else { sb.append(", "); } sb.append(name); sb.append("=["); boolean f = true; for(String val : get(name)) { if(f) { f = false; } else { sb.append(", "); } sb.append(val); } sb.append("]"); } sb.append("}"); return sb.toString(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/HeaderToken.java000066400000000000000000000024651420065311100263540ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; /** * Representation of a token allowed within a header. * * @author Darran Lofthouse */ public interface HeaderToken { /** * @return The name of the token as seen within the HTTP header. */ String getName(); /** * @return true if this header could be a quoted header. */ boolean isAllowQuoted(); /* * Additional items could be added and incorporated into the parsing checks: - * boolean isMandatory(); * boolean * isEnumeration(); * String[] getAllowedValues(); */ } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/HeaderTokenParser.java000066400000000000000000000133531420065311100275270ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import static io.undertow.UndertowMessages.MESSAGES; import java.util.LinkedHashMap; import java.util.Map; /** * Utility to parse the tokens contained within a HTTP header. * * @author Darran Lofthouse */ public class HeaderTokenParser { private static final char EQUALS = '='; private static final char COMMA = ','; private static final char QUOTE = '"'; private static final char ESCAPE = '\\'; private final Map expectedTokens; public HeaderTokenParser(final Map expectedTokens) { this.expectedTokens = expectedTokens; } public Map parseHeader(final String header) { char[] headerChars = header.toCharArray(); // The LinkedHashMap is used so that the parameter order can also be retained. Map response = new LinkedHashMap<>(); SearchingFor searchingFor = SearchingFor.START_OF_NAME; int nameStart = 0; E currentToken = null; int valueStart = 0; int escapeCount = 0; boolean containsEscapes = false; for (int i = 0; i < headerChars.length; i++) { switch (searchingFor) { case START_OF_NAME: // Eliminate any white space before the name of the parameter. if (headerChars[i] != COMMA && !Character.isWhitespace(headerChars[i])) { nameStart = i; searchingFor = SearchingFor.EQUALS_SIGN; } break; case EQUALS_SIGN: if (headerChars[i] == EQUALS) { String paramName = String.valueOf(headerChars, nameStart, i - nameStart); currentToken = expectedTokens.get(paramName); if (currentToken == null) { throw MESSAGES.unexpectedTokenInHeader(paramName); } searchingFor = SearchingFor.START_OF_VALUE; } break; case START_OF_VALUE: if (!Character.isWhitespace(headerChars[i])) { if (headerChars[i] == QUOTE && currentToken.isAllowQuoted()) { valueStart = i + 1; searchingFor = SearchingFor.LAST_QUOTE; } else { valueStart = i; searchingFor = SearchingFor.END_OF_VALUE; } } break; case LAST_QUOTE: if (headerChars[i] == ESCAPE) { escapeCount++; containsEscapes = true; } else if (headerChars[i] == QUOTE && (escapeCount % 2 == 0)) { String value = String.valueOf(headerChars, valueStart, i - valueStart); if(containsEscapes) { StringBuilder sb = new StringBuilder(); boolean lastEscape = false; for(int j = 0; j < value.length(); ++j) { char c = value.charAt(j); if(c == ESCAPE && !lastEscape) { lastEscape = true; } else { lastEscape = false; sb.append(c); } } value = sb.toString(); containsEscapes = false; } response.put(currentToken, value); searchingFor = SearchingFor.START_OF_NAME; escapeCount = 0; } else { escapeCount = 0; } break; case END_OF_VALUE: if (headerChars[i] == COMMA || Character.isWhitespace(headerChars[i])) { String value = String.valueOf(headerChars, valueStart, i - valueStart); response.put(currentToken, value); searchingFor = SearchingFor.START_OF_NAME; } break; } } if (searchingFor == SearchingFor.END_OF_VALUE) { // Special case where we reached the end of the array containing the header values. String value = String.valueOf(headerChars, valueStart, headerChars.length - valueStart); response.put(currentToken, value); } else if (searchingFor != SearchingFor.START_OF_NAME) { // Somehow we are still in the middle of searching for a current value. throw MESSAGES.invalidHeader(); } return response; } enum SearchingFor { START_OF_NAME, EQUALS_SIGN, START_OF_VALUE, LAST_QUOTE, END_OF_VALUE; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/HeaderValues.java000066400000000000000000000423411420065311100265300ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.util.AbstractCollection; import java.util.Arrays; import java.util.Collection; import java.util.Deque; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.NoSuchElementException; import java.util.RandomAccess; /** * An array-backed list/deque for header string values. * * @author David M. Lloyd */ public final class HeaderValues extends AbstractCollection implements Deque, List, RandomAccess { private static final String[] NO_STRINGS = new String[0]; final HttpString key; byte size; Object value; HeaderValues(final HttpString key) { this.key = key; } public HttpString getHeaderName() { return key; } public int size() { return size; } public boolean isEmpty() { return size == 0; } public void clear() { final byte size = this.size; if (size == 0) return; clearInternal(); } private void clearInternal() { final Object value = this.value; if (value instanceof String[]) { final String[] strings = (String[]) value; final int len = strings.length; Arrays.fill(strings, 0, len, null); } else { this.value = null; } this.size = 0; } private int index(int idx) { assert idx >= 0; assert idx < size; final int len = ((String[]) value).length; if (idx > len) { idx -= len; } return idx; } public ListIterator listIterator() { return iterator(0, true); } public ListIterator listIterator(final int index) { return iterator(index, true); } public Iterator iterator() { return iterator(0, true); } public Iterator descendingIterator() { return iterator(0, false); } private ListIterator iterator(final int start, final boolean forwards) { return new ListIterator() { int idx = start; int returned = -1; public boolean hasNext() { return idx < size; } public boolean hasPrevious() { return idx > 0; } public String next() { try { final String next; if (forwards) { int idx = this.idx; next = get(idx); returned = idx; this.idx = idx + 1; return next; } else { int idx = this.idx - 1; next = get(idx); this.idx = returned = idx; } return next; } catch (IndexOutOfBoundsException e) { throw new NoSuchElementException(); } } public int nextIndex() { return idx; } public String previous() { try { final String prev; if (forwards) { int idx = this.idx - 1; prev = get(idx); this.idx = returned = idx; } else { int idx = this.idx; prev = get(idx); returned = idx; this.idx = idx + 1; return prev; } return prev; } catch (IndexOutOfBoundsException e) { throw new NoSuchElementException(); } } public int previousIndex() { return idx - 1; } public void remove() { if (returned == -1) { throw new IllegalStateException(); } HeaderValues.this.remove(returned); returned = -1; } public void set(final String headerValue) { if (returned == -1) { throw new IllegalStateException(); } HeaderValues.this.set(returned, headerValue); } public void add(final String headerValue) { if (returned == -1) { throw new IllegalStateException(); } final int idx = this.idx; HeaderValues.this.add(idx, headerValue); this.idx = idx + 1; returned = -1; } }; } public boolean offerFirst(final String headerValue) { int size = this.size; if (headerValue == null || size == Byte.MAX_VALUE) return false; final Object value = this.value; if (value instanceof String[]) { final String[] strings = (String[]) value; final int len = strings.length; if (size == len) { final String[] newStrings = new String[len + 2]; System.arraycopy(strings, 0, newStrings, 1, len); newStrings[0] = headerValue; this.value = newStrings; } else { System.arraycopy(strings, 0, strings, 1, strings.length - 1); strings[0] = headerValue; } this.size = (byte) (size + 1); } else { if (size == 0) { this.value = headerValue; this.size = (byte) 1; } else { this.value = new String[] { headerValue, (String) value, null, null }; this.size = (byte) 2; } } return true; } public boolean offerLast(final String headerValue) { int size = this.size; if (headerValue == null || size == Byte.MAX_VALUE) return false; final Object value = this.value; if (value instanceof String[]) { offerLastMultiValue(headerValue, size, (String[]) value); } else { if (size == 0) { this.value = headerValue; this.size = (byte) 1; } else { this.value = new String[] { (String) value, headerValue, null, null }; this.size = (byte) 2; } } return true; } private void offerLastMultiValue(String headerValue, int size, String[] value) { final String[] strings = value; final int len = strings.length; if (size == len) { final String[] newStrings = new String[len + 2]; System.arraycopy(strings, 0, newStrings, 0, len); newStrings[len] = headerValue; this.value = newStrings; } else { strings[size] = headerValue; } this.size = (byte) (size + 1); } private boolean offer(int idx, final String headerValue) { int size = this.size; if (idx < 0 || idx > size || size == Byte.MAX_VALUE || headerValue == null) return false; if (idx == 0) return offerFirst(headerValue); if (idx == size) return offerLast(headerValue); assert size >= 2; // must be >= 2 to pass the last two checks final Object value = this.value; assert value instanceof String[]; final String[] strings = (String[]) value; final int len = strings.length; // This stuff is all algebraically derived. if (size == len) { // Grow the list, copy each segment into new spots so that head = 0 final int newLen = len + 2; final String[] newStrings = new String[newLen]; System.arraycopy(value, 0, newStrings, 0, idx); System.arraycopy(value, idx, newStrings, idx + 1, len - idx); // finally fill in the new value newStrings[idx] = headerValue; this.value = newStrings; } else{ System.arraycopy(value, idx, value, idx + 1, len - idx); // finally fill in the new value strings[idx] = headerValue; } this.size = (byte) (size + 1); return true; } public String pollFirst() { final byte size = this.size; if (size == 0) return null; final Object value = this.value; if (value instanceof String) { this.size = 0; this.value = null; return (String) value; } else { final String[] strings = (String[]) value; String ret = strings[0]; System.arraycopy(strings, 1, strings, 0, strings.length - 1); this.size = (byte) (size - 1); return ret; } } public String pollLast() { final byte size = this.size; if (size == 0) return null; final Object value = this.value; if (value instanceof String) { this.size = 0; this.value = null; return (String) value; } else { final String[] strings = (String[]) value; int idx = (this.size = (byte) (size - 1)); final int len = strings.length; if (idx > len) idx -= len; try { return strings[idx]; } finally { strings[idx] = null; } } } public String remove(int idx) { final int size = this.size; if (idx < 0 || idx >= size) throw new IndexOutOfBoundsException(); if (idx == 0) return removeFirst(); if (idx == size - 1) return removeLast(); assert size > 2; // must be > 2 to pass the last two checks // value must be an array since size > 2 final String[] value = (String[]) this.value; final int len = value.length; String ret = value[idx]; System.arraycopy(value, idx + 1, value, idx, len - idx - 1); value[len - 1] = null; this.size = (byte) (size - 1); return ret; } public String get(int idx) { if (idx > size) { throw new IndexOutOfBoundsException(); } Object value = this.value; assert value != null; if (value instanceof String) { assert size == 1; return (String) value; } final String[] a = (String[]) value; return a[index(idx)]; } public int indexOf(final Object o) { if (o == null || size == 0) return -1; if (value instanceof String[]) { final String[] list = (String[]) value; final int len = list.length; for (int i = 0; i < size; i ++) { if ((i > len ? list[i - len] : list[i]).equals(o)) { return i; } } } else if (o.equals(value)) { return 0; } return -1; } public int lastIndexOf(final Object o) { if (o == null || size == 0) return -1; if (value instanceof String[]) { final String[] list = (String[]) value; final int len = list.length; int idx; for (int i = size - 1; i >= 0; i --) { idx = i; if ((idx > len ? list[idx - len] : list[idx]).equals(o)) { return i; } } } else if (o.equals(value)) { return 0; } return -1; } public String set(final int index, final String element) { if (element == null) throw new IllegalArgumentException(); final byte size = this.size; if (index < 0 || index >= size) throw new IndexOutOfBoundsException(); final Object value = this.value; if (size == 1 && value instanceof String) try { return (String) value; } finally { this.value = element; } else { final String[] list = (String[]) value; final int i = index(index); try { return list[i]; } finally { list[i] = element; } } } public boolean addAll(int index, final Collection c) { final int size = this.size; if (index < 0 || index > size) throw new IndexOutOfBoundsException(); final Iterator iterator = c.iterator(); boolean result = false; while (iterator.hasNext()) { result |= offer(index, iterator.next()); } return result; } public List subList(final int fromIndex, final int toIndex) { // todo - this is about 75% correct, by spec... if (fromIndex < 0 || toIndex > size || fromIndex > toIndex) throw new IndexOutOfBoundsException(); final int len = toIndex - fromIndex; final String[] strings = new String[len]; for (int i = 0; i < len; i ++) { strings[i] = get(i + fromIndex); } return Arrays.asList(strings); } public String[] toArray() { int size = this.size; if (size == 0) { return NO_STRINGS; } final Object v = this.value; if (v instanceof String) return new String[] { (String) v }; final String[] list = (String[]) v; final int len = list.length; final int copyEnd = size; if (copyEnd < len) { return Arrays.copyOfRange(list, 0, copyEnd); } else { String[] ret = Arrays.copyOfRange(list, 0, copyEnd); System.arraycopy(list, 0, ret, len, copyEnd - len); return ret; } } public T[] toArray(final T[] a) { int size = this.size; if (size == 0) return a; final int inLen = a.length; final Object[] target = inLen < size ? Arrays.copyOfRange(a, inLen, inLen + size) : a; final Object v = this.value; if (v instanceof String) { target[0] = v; } else { System.arraycopy(v, 0, target, 0, size); } return (T[]) target; } //====================================== // // Derived methods // //====================================== public void addFirst(final String s) { if (s == null) return; if (! offerFirst(s)) throw new IllegalStateException(); } public void addLast(final String s) { if (s == null) return; if (! offerLast(s)) throw new IllegalStateException(); } public void add(final int index, final String s) { if (s == null) return; if (! offer(index, s)) throw new IllegalStateException(); } public boolean contains(final Object o) { return indexOf(o) != -1; } public String peekFirst() { return size == 0 ? null : get(0); } public String peekLast() { return size == 0 ? null : get(size - 1); } public boolean removeFirstOccurrence(final Object o) { int i = indexOf(o); return i != -1 && remove(i) != null; } public boolean removeLastOccurrence(final Object o) { int i = lastIndexOf(o); return i != -1 && remove(i) != null; } public boolean add(final String s) { addLast(s); return true; } public void push(final String s) { addFirst(s); } public String pop() { return removeFirst(); } public boolean offer(final String s) { return offerLast(s); } public String poll() { return pollFirst(); } public String peek() { return peekFirst(); } public String remove() { return removeFirst(); } public String removeFirst() { final String s = pollFirst(); if (s == null) { throw new NoSuchElementException(); } return s; } public String removeLast() { final String s = pollLast(); if (s == null) { throw new NoSuchElementException(); } return s; } public String getFirst() { final String s = peekFirst(); if (s == null) { throw new NoSuchElementException(); } return s; } public String getLast() { final String s = peekLast(); if (s == null) { throw new NoSuchElementException(); } return s; } public String element() { return getFirst(); } public boolean remove(Object obj) { return removeFirstOccurrence(obj); } public boolean addAll(final Collection c) { for (String s : c) { add(s); } return !c.isEmpty(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/Headers.java000066400000000000000000000555311420065311100255400ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.net.URLDecoder; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * NOTE: if you add a new header here you must also add it to {@link io.undertow.server.protocol.http.HttpRequestParser} * * @author David M. Lloyd */ public final class Headers { private Headers() { } // Headers as strings public static final String ACCEPT_STRING = "Accept"; public static final String ACCEPT_CHARSET_STRING = "Accept-Charset"; public static final String ACCEPT_ENCODING_STRING = "Accept-Encoding"; public static final String ACCEPT_LANGUAGE_STRING = "Accept-Language"; public static final String ACCEPT_RANGES_STRING = "Accept-Ranges"; public static final String AGE_STRING = "Age"; public static final String ALLOW_STRING = "Allow"; public static final String AUTHENTICATION_INFO_STRING = "Authentication-Info"; public static final String AUTHORIZATION_STRING = "Authorization"; public static final String CACHE_CONTROL_STRING = "Cache-Control"; public static final String COOKIE_STRING = "Cookie"; public static final String COOKIE2_STRING = "Cookie2"; public static final String CONNECTION_STRING = "Connection"; public static final String CONTENT_DISPOSITION_STRING = "Content-Disposition"; public static final String CONTENT_ENCODING_STRING = "Content-Encoding"; public static final String CONTENT_LANGUAGE_STRING = "Content-Language"; public static final String CONTENT_LENGTH_STRING = "Content-Length"; public static final String CONTENT_LOCATION_STRING = "Content-Location"; public static final String CONTENT_MD5_STRING = "Content-MD5"; public static final String CONTENT_RANGE_STRING = "Content-Range"; public static final String CONTENT_SECURITY_POLICY_STRING = "Content-Security-Policy"; public static final String CONTENT_TYPE_STRING = "Content-Type"; public static final String DATE_STRING = "Date"; public static final String ETAG_STRING = "ETag"; public static final String EXPECT_STRING = "Expect"; public static final String EXPIRES_STRING = "Expires"; public static final String FORWARDED_STRING = "Forwarded"; public static final String FROM_STRING = "From"; public static final String HOST_STRING = "Host"; public static final String IF_MATCH_STRING = "If-Match"; public static final String IF_MODIFIED_SINCE_STRING = "If-Modified-Since"; public static final String IF_NONE_MATCH_STRING = "If-None-Match"; public static final String IF_RANGE_STRING = "If-Range"; public static final String IF_UNMODIFIED_SINCE_STRING = "If-Unmodified-Since"; public static final String LAST_MODIFIED_STRING = "Last-Modified"; public static final String LOCATION_STRING = "Location"; public static final String MAX_FORWARDS_STRING = "Max-Forwards"; public static final String ORIGIN_STRING = "Origin"; public static final String PRAGMA_STRING = "Pragma"; public static final String PROXY_AUTHENTICATE_STRING = "Proxy-Authenticate"; public static final String PROXY_AUTHORIZATION_STRING = "Proxy-Authorization"; public static final String RANGE_STRING = "Range"; public static final String REFERER_STRING = "Referer"; public static final String REFERRER_POLICY_STRING = "Referrer-Policy"; public static final String REFRESH_STRING = "Refresh"; public static final String RETRY_AFTER_STRING = "Retry-After"; public static final String SEC_WEB_SOCKET_ACCEPT_STRING = "Sec-WebSocket-Accept"; public static final String SEC_WEB_SOCKET_EXTENSIONS_STRING = "Sec-WebSocket-Extensions"; public static final String SEC_WEB_SOCKET_KEY_STRING = "Sec-WebSocket-Key"; public static final String SEC_WEB_SOCKET_KEY1_STRING = "Sec-WebSocket-Key1"; public static final String SEC_WEB_SOCKET_KEY2_STRING = "Sec-WebSocket-Key2"; public static final String SEC_WEB_SOCKET_LOCATION_STRING = "Sec-WebSocket-Location"; public static final String SEC_WEB_SOCKET_ORIGIN_STRING = "Sec-WebSocket-Origin"; public static final String SEC_WEB_SOCKET_PROTOCOL_STRING = "Sec-WebSocket-Protocol"; public static final String SEC_WEB_SOCKET_VERSION_STRING = "Sec-WebSocket-Version"; public static final String SERVER_STRING = "Server"; public static final String SERVLET_ENGINE_STRING = "Servlet-Engine"; public static final String SET_COOKIE_STRING = "Set-Cookie"; public static final String SET_COOKIE2_STRING = "Set-Cookie2"; public static final String SSL_CLIENT_CERT_STRING = "SSL_CLIENT_CERT"; public static final String SSL_CIPHER_STRING = "SSL_CIPHER"; public static final String SSL_SESSION_ID_STRING = "SSL_SESSION_ID"; public static final String SSL_CIPHER_USEKEYSIZE_STRING = "SSL_CIPHER_USEKEYSIZE"; public static final String STATUS_STRING = "Status"; public static final String STRICT_TRANSPORT_SECURITY_STRING = "Strict-Transport-Security"; public static final String TE_STRING = "TE"; public static final String TRAILER_STRING = "Trailer"; public static final String TRANSFER_ENCODING_STRING = "Transfer-Encoding"; public static final String UPGRADE_STRING = "Upgrade"; public static final String USER_AGENT_STRING = "User-Agent"; public static final String VARY_STRING = "Vary"; public static final String VIA_STRING = "Via"; public static final String WARNING_STRING = "Warning"; public static final String WWW_AUTHENTICATE_STRING = "WWW-Authenticate"; public static final String X_CONTENT_TYPE_OPTIONS_STRING = "X-Content-Type-Options"; public static final String X_DISABLE_PUSH_STRING = "X-Disable-Push"; public static final String X_FORWARDED_FOR_STRING = "X-Forwarded-For"; public static final String X_FORWARDED_PROTO_STRING = "X-Forwarded-Proto"; public static final String X_FORWARDED_HOST_STRING = "X-Forwarded-Host"; public static final String X_FORWARDED_PORT_STRING = "X-Forwarded-Port"; public static final String X_FORWARDED_SERVER_STRING = "X-Forwarded-Server"; public static final String X_FRAME_OPTIONS_STRING = "X-Frame-Options"; public static final String X_XSS_PROTECTION_STRING = "X-Xss-Protection"; // Header names public static final HttpString ACCEPT = new HttpString(ACCEPT_STRING, 1); public static final HttpString ACCEPT_CHARSET = new HttpString(ACCEPT_CHARSET_STRING, 2); public static final HttpString ACCEPT_ENCODING = new HttpString(ACCEPT_ENCODING_STRING, 3); public static final HttpString ACCEPT_LANGUAGE = new HttpString(ACCEPT_LANGUAGE_STRING, 4); public static final HttpString ACCEPT_RANGES = new HttpString(ACCEPT_RANGES_STRING, 5); public static final HttpString AGE = new HttpString(AGE_STRING, 6); public static final HttpString ALLOW = new HttpString(ALLOW_STRING, 7); public static final HttpString AUTHENTICATION_INFO = new HttpString(AUTHENTICATION_INFO_STRING, 8); public static final HttpString AUTHORIZATION = new HttpString(AUTHORIZATION_STRING, 9); public static final HttpString CACHE_CONTROL = new HttpString(CACHE_CONTROL_STRING, 10); public static final HttpString CONNECTION = new HttpString(CONNECTION_STRING, 11); public static final HttpString CONTENT_DISPOSITION = new HttpString(CONTENT_DISPOSITION_STRING, 12); public static final HttpString CONTENT_ENCODING = new HttpString(CONTENT_ENCODING_STRING, 13); public static final HttpString CONTENT_LANGUAGE = new HttpString(CONTENT_LANGUAGE_STRING, 14); public static final HttpString CONTENT_LENGTH = new HttpString(CONTENT_LENGTH_STRING, 15); public static final HttpString CONTENT_LOCATION = new HttpString(CONTENT_LOCATION_STRING, 16); public static final HttpString CONTENT_MD5 = new HttpString(CONTENT_MD5_STRING, 17); public static final HttpString CONTENT_RANGE = new HttpString(CONTENT_RANGE_STRING, 18); public static final HttpString CONTENT_SECURITY_POLICY = new HttpString(CONTENT_SECURITY_POLICY_STRING, 19); public static final HttpString CONTENT_TYPE = new HttpString(CONTENT_TYPE_STRING, 20); public static final HttpString COOKIE = new HttpString(COOKIE_STRING, 21); public static final HttpString COOKIE2 = new HttpString(COOKIE2_STRING, 22); public static final HttpString DATE = new HttpString(DATE_STRING, 23); public static final HttpString ETAG = new HttpString(ETAG_STRING, 24); public static final HttpString EXPECT = new HttpString(EXPECT_STRING, 25); public static final HttpString EXPIRES = new HttpString(EXPIRES_STRING, 26); public static final HttpString FORWARDED = new HttpString(FORWARDED_STRING, 27); public static final HttpString FROM = new HttpString(FROM_STRING, 28); public static final HttpString HOST = new HttpString(HOST_STRING, 29); public static final HttpString IF_MATCH = new HttpString(IF_MATCH_STRING, 30); public static final HttpString IF_MODIFIED_SINCE = new HttpString(IF_MODIFIED_SINCE_STRING, 31); public static final HttpString IF_NONE_MATCH = new HttpString(IF_NONE_MATCH_STRING, 32); public static final HttpString IF_RANGE = new HttpString(IF_RANGE_STRING, 33); public static final HttpString IF_UNMODIFIED_SINCE = new HttpString(IF_UNMODIFIED_SINCE_STRING, 34); public static final HttpString LAST_MODIFIED = new HttpString(LAST_MODIFIED_STRING, 35); public static final HttpString LOCATION = new HttpString(LOCATION_STRING, 36); public static final HttpString MAX_FORWARDS = new HttpString(MAX_FORWARDS_STRING, 37); public static final HttpString ORIGIN = new HttpString(ORIGIN_STRING, 38); public static final HttpString PRAGMA = new HttpString(PRAGMA_STRING, 39); public static final HttpString PROXY_AUTHENTICATE = new HttpString(PROXY_AUTHENTICATE_STRING, 40); public static final HttpString PROXY_AUTHORIZATION = new HttpString(PROXY_AUTHORIZATION_STRING, 41); public static final HttpString RANGE = new HttpString(RANGE_STRING, 42); public static final HttpString REFERER = new HttpString(REFERER_STRING, 43); public static final HttpString REFERRER_POLICY = new HttpString(REFERRER_POLICY_STRING, 44); public static final HttpString REFRESH = new HttpString(REFRESH_STRING, 45); public static final HttpString RETRY_AFTER = new HttpString(RETRY_AFTER_STRING, 46); public static final HttpString SEC_WEB_SOCKET_ACCEPT = new HttpString(SEC_WEB_SOCKET_ACCEPT_STRING, 47); public static final HttpString SEC_WEB_SOCKET_EXTENSIONS = new HttpString(SEC_WEB_SOCKET_EXTENSIONS_STRING, 48); public static final HttpString SEC_WEB_SOCKET_KEY = new HttpString(SEC_WEB_SOCKET_KEY_STRING, 49); public static final HttpString SEC_WEB_SOCKET_KEY1 = new HttpString(SEC_WEB_SOCKET_KEY1_STRING, 50); public static final HttpString SEC_WEB_SOCKET_KEY2 = new HttpString(SEC_WEB_SOCKET_KEY2_STRING, 51); public static final HttpString SEC_WEB_SOCKET_LOCATION = new HttpString(SEC_WEB_SOCKET_LOCATION_STRING, 52); public static final HttpString SEC_WEB_SOCKET_ORIGIN = new HttpString(SEC_WEB_SOCKET_ORIGIN_STRING, 53); public static final HttpString SEC_WEB_SOCKET_PROTOCOL = new HttpString(SEC_WEB_SOCKET_PROTOCOL_STRING, 54); public static final HttpString SEC_WEB_SOCKET_VERSION = new HttpString(SEC_WEB_SOCKET_VERSION_STRING, 55); public static final HttpString SERVER = new HttpString(SERVER_STRING, 56); public static final HttpString SERVLET_ENGINE = new HttpString(SERVLET_ENGINE_STRING, 57); public static final HttpString SET_COOKIE = new HttpString(SET_COOKIE_STRING, 58); public static final HttpString SET_COOKIE2 = new HttpString(SET_COOKIE2_STRING, 59); public static final HttpString SSL_CIPHER = new HttpString(SSL_CIPHER_STRING, 60); public static final HttpString SSL_CIPHER_USEKEYSIZE = new HttpString(SSL_CIPHER_USEKEYSIZE_STRING, 61); public static final HttpString SSL_CLIENT_CERT = new HttpString(SSL_CLIENT_CERT_STRING, 62); public static final HttpString SSL_SESSION_ID = new HttpString(SSL_SESSION_ID_STRING, 63); public static final HttpString STATUS = new HttpString(STATUS_STRING, 64); public static final HttpString STRICT_TRANSPORT_SECURITY = new HttpString(STRICT_TRANSPORT_SECURITY_STRING, 65); public static final HttpString TE = new HttpString(TE_STRING, 66); public static final HttpString TRAILER = new HttpString(TRAILER_STRING, 67); public static final HttpString TRANSFER_ENCODING = new HttpString(TRANSFER_ENCODING_STRING, 68); public static final HttpString UPGRADE = new HttpString(UPGRADE_STRING, 69); public static final HttpString USER_AGENT = new HttpString(USER_AGENT_STRING, 70); public static final HttpString VARY = new HttpString(VARY_STRING, 71); public static final HttpString VIA = new HttpString(VIA_STRING, 72); public static final HttpString WARNING = new HttpString(WARNING_STRING, 73); public static final HttpString WWW_AUTHENTICATE = new HttpString(WWW_AUTHENTICATE_STRING, 74); public static final HttpString X_CONTENT_TYPE_OPTIONS = new HttpString(X_CONTENT_TYPE_OPTIONS_STRING, 75); public static final HttpString X_DISABLE_PUSH = new HttpString(X_DISABLE_PUSH_STRING, 76); public static final HttpString X_FORWARDED_FOR = new HttpString(X_FORWARDED_FOR_STRING, 77); public static final HttpString X_FORWARDED_HOST = new HttpString(X_FORWARDED_HOST_STRING, 78); public static final HttpString X_FORWARDED_PORT = new HttpString(X_FORWARDED_PORT_STRING, 79); public static final HttpString X_FORWARDED_PROTO = new HttpString(X_FORWARDED_PROTO_STRING, 80); public static final HttpString X_FORWARDED_SERVER = new HttpString(X_FORWARDED_SERVER_STRING, 81); public static final HttpString X_FRAME_OPTIONS = new HttpString(X_FRAME_OPTIONS_STRING, 82); public static final HttpString X_XSS_PROTECTION = new HttpString(X_XSS_PROTECTION_STRING, 83); // Content codings public static final HttpString COMPRESS = new HttpString("compress"); public static final HttpString X_COMPRESS = new HttpString("x-compress"); public static final HttpString DEFLATE = new HttpString("deflate"); public static final HttpString IDENTITY = new HttpString("identity"); public static final HttpString GZIP = new HttpString("gzip"); public static final HttpString X_GZIP = new HttpString("x-gzip"); // Transfer codings public static final HttpString CHUNKED = new HttpString("chunked"); // IDENTITY // GZIP // COMPRESS // DEFLATE // Connection values public static final HttpString KEEP_ALIVE = new HttpString("keep-alive"); public static final HttpString CLOSE = new HttpString("close"); //MIME header used in multipart file uploads public static final String CONTENT_TRANSFER_ENCODING_STRING = "Content-Transfer-Encoding"; public static final HttpString CONTENT_TRANSFER_ENCODING = new HttpString(CONTENT_TRANSFER_ENCODING_STRING); // Authentication Schemes public static final HttpString BASIC = new HttpString("Basic"); public static final HttpString DIGEST = new HttpString("Digest"); public static final HttpString NEGOTIATE = new HttpString("Negotiate"); // Digest authentication Token Names public static final HttpString ALGORITHM = new HttpString("algorithm"); public static final HttpString AUTH_PARAM = new HttpString("auth-param"); public static final HttpString CNONCE = new HttpString("cnonce"); public static final HttpString DOMAIN = new HttpString("domain"); public static final HttpString NEXT_NONCE = new HttpString("nextnonce"); public static final HttpString NONCE = new HttpString("nonce"); public static final HttpString NONCE_COUNT = new HttpString("nc"); public static final HttpString OPAQUE = new HttpString("opaque"); public static final HttpString QOP = new HttpString("qop"); public static final HttpString REALM = new HttpString("realm"); public static final HttpString RESPONSE = new HttpString("response"); public static final HttpString RESPONSE_AUTH = new HttpString("rspauth"); public static final HttpString STALE = new HttpString("stale"); public static final HttpString URI = new HttpString("uri"); public static final HttpString USERNAME = new HttpString("username"); private static final Map HTTP_STRING_MAP; static { Map map = AccessController.doPrivileged(new PrivilegedAction>() { @Override public Map run() { Map map = new HashMap<>(); Field[] fields = Headers.class.getDeclaredFields(); for(Field field : fields) { if(Modifier.isStatic(field.getModifiers()) && field.getType() == HttpString.class) { field.setAccessible(true); try { HttpString result = (HttpString) field.get(null); map.put(result.toString(), result); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } } return map; } }); HTTP_STRING_MAP = Collections.unmodifiableMap(map); } public static HttpString fromCache(String string) { return HTTP_STRING_MAP.get(string); } /** * Extracts a token from a header that has a given key. For instance if the header is *

* content-type=multipart/form-data boundary=myboundary * and the key is boundary the myboundary will be returned. * * @param header The header * @param key The key that identifies the token to extract * @return The token, or null if it was not found */ @Deprecated public static String extractTokenFromHeader(final String header, final String key) { int pos = header.indexOf(' ' + key + '='); if (pos == -1) { if(!header.startsWith(key + '=')) { return null; } pos = 0; } else { pos++; } int end; int start = pos + key.length() + 1; for (end = start; end < header.length(); ++end) { char c = header.charAt(end); if (c == ' ' || c == '\t' || c == ';') { break; } } return header.substring(start, end); } /** * Extracts a quoted value from a header that has a given key. For instance if the header is *

* content-disposition=form-data; name="my field" * and the key is name then "my field" will be returned without the quotes. * * * @param header The header * @param key The key that identifies the token to extract * @return The token, or null if it was not found */ public static String extractQuotedValueFromHeader(final String header, final String key) { int keypos = 0; int pos = -1; boolean whiteSpace = true; boolean inQuotes = false; for (int i = 0; i < header.length() - 1; ++i) { //-1 because we need room for the = at the end //TODO: a more efficient matching algorithm char c = header.charAt(i); if (inQuotes) { if (c == '"') { inQuotes = false; } } else { if (key.charAt(keypos) == c && (whiteSpace || keypos > 0)) { keypos++; whiteSpace = false; } else if (c == '"') { keypos = 0; inQuotes = true; whiteSpace = false; } else { keypos = 0; whiteSpace = c == ' ' || c == ';' || c == '\t'; } if (keypos == key.length()) { if (header.charAt(i + 1) == '=') { pos = i + 2; break; } else { keypos = 0; } } } } if (pos == -1) { return null; } int end; int start = pos; if (header.charAt(start) == '"') { start++; for (end = start; end < header.length(); ++end) { char c = header.charAt(end); if (c == '"') { break; } } return header.substring(start, end); } else { //no quotes for (end = start; end < header.length(); ++end) { char c = header.charAt(end); if (c == ' ' || c == '\t' || c == ';') { break; } } return header.substring(start, end); } } /** * Extracts a quoted value from a header that has a given key. For instance if the header is *

* content-disposition=form-data; filename*="utf-8''test.txt" * and the key is filename* then "test.txt" will be returned after extracting character set and language * (following RFC 2231) and performing URL decoding to the value using the specified encoding * * @param header The header * @param key The key that identifies the token to extract * @return The token, or null if it was not found */ public static String extractQuotedValueFromHeaderWithEncoding(final String header, final String key) { String value = extractQuotedValueFromHeader(header, key); if (value != null) { return value; } value = extractQuotedValueFromHeader(header , key + "*"); if(value != null) { int characterSetDelimiter = value.indexOf('\''); int languageDelimiter = value.lastIndexOf('\'', characterSetDelimiter + 1); String characterSet = value.substring(0, characterSetDelimiter); try { String fileNameURLEncoded = value.substring(languageDelimiter + 1); return URLDecoder.decode(fileNameURLEncoded, characterSet); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } return null; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/HexConverter.java000066400000000000000000000117721420065311100266000ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; /** * A utility class for mapping between byte arrays and their hex representation and back again. * * @author Darran Lofthouse */ public class HexConverter { private static final char[] HEX_CHARS = new char[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; private static final byte[] HEX_BYTES = new byte[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; /** * Take the supplied byte array and convert it to a hex encoded String. * * @param toBeConverted - the bytes to be converted. * @return the hex encoded String. */ public static String convertToHexString(byte[] toBeConverted) { if (toBeConverted == null) { throw new NullPointerException("Parameter to be converted can not be null"); } char[] converted = new char[toBeConverted.length * 2]; for (int i = 0; i < toBeConverted.length; i++) { byte b = toBeConverted[i]; converted[i * 2] = HEX_CHARS[b >> 4 & 0x0F]; converted[i * 2 + 1] = HEX_CHARS[b & 0x0F]; } return String.valueOf(converted); } /** * Take the supplied byte array and convert it to to a byte array of the encoded * hex values. *

* Each byte on the incoming array will be converted to two bytes on the return * array. * * @param toBeConverted - the bytes to be encoded. * @return the encoded byte array. */ public static byte[] convertToHexBytes(byte[] toBeConverted) { if (toBeConverted == null) { throw new NullPointerException("Parameter to be converted can not be null"); } byte[] converted = new byte[toBeConverted.length * 2]; for (int i = 0; i < toBeConverted.length; i++) { byte b = toBeConverted[i]; converted[i * 2] = HEX_BYTES[b >> 4 & 0x0F]; converted[i * 2 + 1] = HEX_BYTES[b & 0x0F]; } return converted; } /** * Take the incoming character of hex encoded data and convert to the raw byte values. *

* The characters in the incoming array are processed in pairs with two chars of a pair * being converted to a single byte. * * @param toConvert - the hex encoded String to convert. * @return the raw byte array. */ public static byte[] convertFromHex(final char[] toConvert) { if (toConvert.length % 2 != 0) { throw new IllegalArgumentException("The supplied character array must contain an even number of hex chars."); } byte[] response = new byte[toConvert.length / 2]; for (int i = 0; i < response.length; i++) { int posOne = i * 2; response[i] = (byte)(toByte(toConvert, posOne) << 4 | toByte(toConvert, posOne+1)); } return response; } private static byte toByte(final char[] toConvert, final int pos) { int response = Character.digit(toConvert[pos], 16); if (response < 0 || response > 15) { throw new IllegalArgumentException("Non-hex character '" + toConvert[pos] + "' at index=" + pos); } return (byte) response; } /** * Take the incoming String of hex encoded data and convert to the raw byte values. *

* The characters in the incoming String are processed in pairs with two chars of a pair * being converted to a single byte. * * @param toConvert - the hex encoded String to convert. * @return the raw byte array. */ public static byte[] convertFromHex(final String toConvert) { return convertFromHex(toConvert.toCharArray()); } public static void main(String[] args) { byte[] toConvert = new byte[256]; for (int i = 0; i < toConvert.length; i++) { toConvert[i] = (byte) i; } String hexValue = convertToHexString(toConvert); System.out.println("Converted - " + hexValue); byte[] convertedBack = convertFromHex(hexValue); StringBuilder sb = new StringBuilder(); for (byte current : convertedBack) { sb.append((int)current).append(" "); } System.out.println("Converted Back " + sb.toString()); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/HttpString.java000066400000000000000000000256221420065311100262710ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.io.IOException; import java.io.ObjectInputStream; import java.io.OutputStream; import java.io.Serializable; import java.lang.reflect.Field; import java.nio.ByteBuffer; import static java.lang.Integer.signum; import static java.lang.System.arraycopy; import static java.util.Arrays.copyOfRange; import io.undertow.UndertowMessages; /** * An HTTP case-insensitive Latin-1 string. * * @author David M. Lloyd */ public final class HttpString implements Comparable, Serializable { private static final long serialVersionUID = 1L; private final byte[] bytes; private final transient int hashCode; /** * And integer that is only set for well known header to make * comparison fast */ private final int orderInt; private transient String string; private static final Field hashCodeField; static { try { hashCodeField = HttpString.class.getDeclaredField("hashCode"); hashCodeField.setAccessible(true); } catch (NoSuchFieldException e) { throw new NoSuchFieldError(e.getMessage()); } } /** * Empty HttpString instance. */ public static final HttpString EMPTY = new HttpString(""); /** * Construct a new instance. * * @param bytes the byte array to copy */ public HttpString(final byte[] bytes) { this(bytes.clone(), null); } /** * Construct a new instance. * * @param bytes the byte array to copy * @param offset the offset into the array to start copying * @param length the number of bytes to copy */ public HttpString(final byte[] bytes, int offset, int length) { this(copyOfRange(bytes, offset, offset + length), null); } /** * Construct a new instance by reading the remaining bytes from a buffer. * * @param buffer the buffer to read */ public HttpString(final ByteBuffer buffer) { this(take(buffer), null); } /** * Construct a new instance from a {@code String}. The {@code String} will be used * as the cached {@code toString()} value for this {@code HttpString}. * * @param string the source string */ public HttpString(final String string) { this(string, 0); } HttpString(final String string, int orderInt) { this.orderInt = orderInt; this.bytes = toByteArray(string); this.hashCode = calcHashCode(bytes); this.string = string; checkForNewlines(); } private static byte[] toByteArray(final String string) { final int len = string.length(); final byte[] bytes = new byte[len]; for (int i = 0; i < len; i++) { char c = string.charAt(i); if (c > 0xff) { throw new IllegalArgumentException("Invalid string contents " + string); } bytes[i] = (byte) c; } return bytes; } private void checkForNewlines() { for(byte b : bytes) { if(b == '\r' || b == '\n') { throw UndertowMessages.MESSAGES.newlineNotSupportedInHttpString(string); } } } private HttpString(final byte[] bytes, final String string) { this.bytes = bytes; this.hashCode = calcHashCode(bytes); this.string = string; this.orderInt = 0; checkForNewlines(); } /** * Attempt to convert a {@code String} to an {@code HttpString}. If the string cannot be converted, * {@code null} is returned. * * @param string the string to try * @return the HTTP string, or {@code null} if the string is not in a compatible encoding */ public static HttpString tryFromString(String string) { HttpString cached = Headers.fromCache(string); if(cached != null) { return cached; } final int len = string.length(); final byte[] bytes = new byte[len]; for (int i = 0; i < len; i++) { char c = string.charAt(i); if (c > 0xff) { return null; } bytes[i] = (byte) c; } return new HttpString(bytes, string); } /** * Get the string length. * * @return the string length */ public int length() { return bytes.length; } /** * Get the byte at an index. * * @return the byte at an index */ public byte byteAt(int idx) { return bytes[idx]; } /** * Copy {@code len} bytes from this string at offset {@code srcOffs} to the given array at the given offset. * * @param srcOffs the source offset * @param dst the destination * @param offs the destination offset * @param len the number of bytes to copy */ public void copyTo(int srcOffs, byte[] dst, int offs, int len) { arraycopy(bytes, srcOffs, dst, offs, len); } /** * Copy {@code len} bytes from this string to the given array at the given offset. * * @param dst the destination * @param offs the destination offset * @param len the number of bytes */ public void copyTo(byte[] dst, int offs, int len) { copyTo(0, dst, offs, len); } /** * Copy all the bytes from this string to the given array at the given offset. * * @param dst the destination * @param offs the destination offset */ public void copyTo(byte[] dst, int offs) { copyTo(dst, offs, bytes.length); } /** * Append to a byte buffer. * * @param buffer the buffer to append to */ public void appendTo(ByteBuffer buffer) { buffer.put(bytes); } /** * Append to an output stream. * * @param output the stream to write to * @throws IOException if an error occurs */ public void writeTo(OutputStream output) throws IOException { output.write(bytes); } private static byte[] take(final ByteBuffer buffer) { if (buffer.hasArray()) { // avoid useless array clearing try { return copyOfRange(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); } finally { buffer.position(buffer.limit()); } } else { final byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); return bytes; } } /** * Compare this string to another in a case-insensitive manner. * * @param other the other string * @return -1, 0, or 1 */ public int compareTo(final HttpString other) { if(orderInt != 0 && other.orderInt != 0) { return signum(orderInt - other.orderInt); } final int len = Math.min(bytes.length, other.bytes.length); int res; for (int i = 0; i < len; i++) { res = signum(higher(bytes[i]) - higher(other.bytes[i])); if (res != 0) return res; } // shorter strings sort higher return signum(bytes.length - other.bytes.length); } /** * Get the hash code. * * @return the hash code */ @Override public int hashCode() { return hashCode; } /** * Determine if this {@code HttpString} is equal to another. * * @param other the other object * @return {@code true} if they are equal, {@code false} otherwise */ @Override public boolean equals(final Object other) { if(other == this) { return true; } if(!(other instanceof HttpString)) { return false; } HttpString otherString = (HttpString) other; if(orderInt > 0 && otherString.orderInt > 0) { //if the order int is set for both of them and different then we know they are different strings return false; } return bytesAreEqual(bytes, otherString.bytes); } /** * Determine if this {@code HttpString} is equal to another. * * @param other the other object * @return {@code true} if they are equal, {@code false} otherwise */ public boolean equals(final HttpString other) { return other == this || other != null && bytesAreEqual(bytes, other.bytes); } private static int calcHashCode(final byte[] bytes) { int hc = 17; for (byte b : bytes) { hc = (hc << 4) + hc + higher(b); } return hc; } private static int higher(byte b) { return b & (b >= 'a' && b <= 'z' ? 0xDF : 0xFF); } private static boolean bytesAreEqual(final byte[] a, final byte[] b) { return a.length == b.length && bytesAreEquivalent(a, b); } private static boolean bytesAreEquivalent(final byte[] a, final byte[] b) { assert a.length == b.length; final int len = a.length; for (int i = 0; i < len; i++) { if (higher(a[i]) != higher(b[i])) { return false; } } return true; } /** * Get the {@code String} representation of this {@code HttpString}. * * @return the string */ @Override @SuppressWarnings("deprecation") public String toString() { if (string == null) { string = new String(bytes, java.nio.charset.StandardCharsets.US_ASCII); } return string; } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); try { hashCodeField.setInt(this, calcHashCode(bytes)); } catch (IllegalAccessException e) { throw new IllegalAccessError(e.getMessage()); } } static int hashCodeOf(final String headerName) { final byte[] bytes = toByteArray(headerName); return calcHashCode(bytes); } public boolean equalToString(String headerName) { if(headerName.length() != bytes.length) { return false; } final int len = bytes.length; for (int i = 0; i < len; i++) { if (higher(bytes[i]) != higher((byte)headerName.charAt(i))) { return false; } } return true; } } ImmediateAuthenticationMechanismFactory.java000066400000000000000000000032531420065311100340530ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/util/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.AuthenticationMechanismFactory; import io.undertow.security.idm.IdentityManager; import io.undertow.server.handlers.form.FormParserFactory; import java.util.Map; /** * {@link AuthenticationMechanismFactory} that simply returns a pre configured {@link AuthenticationMechanism} * @author Stuart Douglas */ public class ImmediateAuthenticationMechanismFactory implements AuthenticationMechanismFactory { private final AuthenticationMechanism authenticationMechanism; public ImmediateAuthenticationMechanismFactory(AuthenticationMechanism authenticationMechanism) { this.authenticationMechanism = authenticationMechanism; } @Override public AuthenticationMechanism create(String mechanismName, IdentityManager identityManager, FormParserFactory formParserFactory, Map properties) { return authenticationMechanism; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/ImmediateConduitFactory.java000066400000000000000000000020701420065311100307270ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import org.xnio.conduits.Conduit; /** * @author Stuart Douglas */ public class ImmediateConduitFactory implements ConduitFactory { private final T value; public ImmediateConduitFactory(final T value) { this.value = value; } @Override public T create() { return value; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/ImmediatePooled.java000066400000000000000000000023701420065311100272170ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import org.xnio.Pooled; /** * Wrapper that allows you to use a non-pooed item as a pooled value * * @author Stuart Douglas */ public class ImmediatePooled implements Pooled { private final T value; public ImmediatePooled(T value) { this.value = value; } @Override public void discard() { } @Override public void free() { } @Override public T getResource() throws IllegalStateException { return value; } @Override public void close() { } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/ImmediatePooledByteBuffer.java000066400000000000000000000023401420065311100311720ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.connector.PooledByteBuffer; import java.nio.ByteBuffer; /** * @author Stuart Douglas */ public class ImmediatePooledByteBuffer implements PooledByteBuffer { private ByteBuffer buffer; public ImmediatePooledByteBuffer(ByteBuffer buffer) { this.buffer = buffer; } @Override public ByteBuffer getBuffer() { return buffer; } @Override public void close() { } @Override public boolean isOpen() { return true; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/LegacyCookieSupport.java000066400000000000000000000230751420065311100301160ustar00rootroot00000000000000/* * 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. */ package io.undertow.util; import io.undertow.UndertowMessages; import io.undertow.server.handlers.Cookie; /** * Class that contains static constants and utility methods for legacy Set-Cookie format. * Porting from JBossWeb and Tomcat code. * * Note that in general we do not use system properties for configuration, however as these are * legacy options that are not widely used an exception has been made in this case. * */ public final class LegacyCookieSupport { // --------------------------------------------------------------- Constants /** * If true, separators that are not explicitly dis-allowed by the v0 cookie * spec but are disallowed by the HTTP spec will be allowed in v0 cookie * names and values. These characters are: \"()/:<=>?@[\\]{} Note that the * inclusion of / depend on the value of {@link #FWD_SLASH_IS_SEPARATOR}. * * Defaults to false. */ static final boolean ALLOW_HTTP_SEPARATORS_IN_V0 = Boolean.getBoolean("io.undertow.legacy.cookie.ALLOW_HTTP_SEPARATORS_IN_V0"); /** * If set to true, the / character will be treated as a * separator. Default is false. */ private static final boolean FWD_SLASH_IS_SEPARATOR = Boolean.getBoolean("io.undertow.legacy.cookie.FWD_SLASH_IS_SEPARATOR"); /** * If set to true, the character will be treated as a * separator in Cookie: headers. */ static final boolean COMMA_IS_SEPARATOR = Boolean.getBoolean("io.undertow.legacy.cookie.COMMA_IS_SEPARATOR"); /** * The list of separators that apply to version 0 cookies. To quote the * spec, these are comma, semi-colon and white-space. The HTTP spec * definition of linear white space is [CRLF] 1*( SP | HT ) */ private static final char[] V0_SEPARATORS = {',', ';', ' ', '\t'}; private static final boolean[] V0_SEPARATOR_FLAGS = new boolean[128]; /** * The list of separators that apply to version 1 cookies. This may or may * not include '/' depending on the setting of * {@link #FWD_SLASH_IS_SEPARATOR}. */ private static final char[] HTTP_SEPARATORS; private static final boolean[] HTTP_SEPARATOR_FLAGS = new boolean[128]; static { /* Excluding the '/' char by default violates the RFC, but it looks like a lot of people put '/' in unquoted values: '/': ; //47 '\t':9 ' ':32 '\"':34 '(':40 ')':41 ',':44 ':':58 ';':59 '<':60 '=':61 '>':62 '?':63 '@':64 '[':91 '\\':92 ']':93 '{':123 '}':125 */ if (FWD_SLASH_IS_SEPARATOR) { HTTP_SEPARATORS = new char[]{'\t', ' ', '\"', '(', ')', ',', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '{', '}'}; } else { HTTP_SEPARATORS = new char[]{'\t', ' ', '\"', '(', ')', ',', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '{', '}'}; } for (int i = 0; i < 128; i++) { V0_SEPARATOR_FLAGS[i] = false; HTTP_SEPARATOR_FLAGS[i] = false; } for (char V0_SEPARATOR : V0_SEPARATORS) { V0_SEPARATOR_FLAGS[V0_SEPARATOR] = true; } for (char HTTP_SEPARATOR : HTTP_SEPARATORS) { HTTP_SEPARATOR_FLAGS[HTTP_SEPARATOR] = true; } } // ----------------------------------------------------------------- Methods /** * Returns true if the byte is a separator as defined by V0 of the cookie * spec. */ private static boolean isV0Separator(final char c) { if (c < 0x20 || c >= 0x7f) { if (c != 0x09) { throw UndertowMessages.MESSAGES.invalidControlCharacter(Integer.toString(c)); } } return V0_SEPARATOR_FLAGS[c]; } private static boolean isV0Token(String value) { if( value==null) return false; int i = 0; int len = value.length(); if (alreadyQuoted(value)) { i++; len--; } for (; i < len; i++) { char c = value.charAt(i); if (isV0Separator(c)) return true; } return false; } /** * Returns true if the byte is a separator as defined by V1 of the cookie * spec, RFC2109. * @throws IllegalArgumentException if a control character was supplied as * input */ static boolean isHttpSeparator(final char c) { if (c < 0x20 || c >= 0x7f) { if (c != 0x09) { throw UndertowMessages.MESSAGES.invalidControlCharacter(Integer.toString(c)); } } return HTTP_SEPARATOR_FLAGS[c]; } private static boolean isHttpToken(String value) { if( value==null) return false; int i = 0; int len = value.length(); if (alreadyQuoted(value)) { i++; len--; } for (; i < len; i++) { char c = value.charAt(i); if (isHttpSeparator(c)) return true; } return false; } private static boolean alreadyQuoted(String value) { if (value==null || value.length() < 2) return false; return (value.charAt(0)=='\"' && value.charAt(value.length()-1)=='\"'); } /** * Quotes values if required. * @param buf * @param value */ public static void maybeQuote(StringBuilder buf, String value) { if (value==null || value.length()==0) { buf.append("\"\""); } else if (alreadyQuoted(value)) { buf.append('"'); buf.append(escapeDoubleQuotes(value,1,value.length()-1)); buf.append('"'); } else if ((isHttpToken(value) && !ALLOW_HTTP_SEPARATORS_IN_V0) || (isV0Token(value) && ALLOW_HTTP_SEPARATORS_IN_V0)) { buf.append('"'); buf.append(escapeDoubleQuotes(value,0,value.length())); buf.append('"'); } else { buf.append(value); } } /** * Escapes any double quotes in the given string. * * @param s the input string * @param beginIndex start index inclusive * @param endIndex exclusive * @return The (possibly) escaped string */ private static String escapeDoubleQuotes(String s, int beginIndex, int endIndex) { if (s == null || s.length() == 0 || s.indexOf('"') == -1) { return s; } StringBuilder b = new StringBuilder(); for (int i = beginIndex; i < endIndex; i++) { char c = s.charAt(i); if (c == '\\' ) { b.append(c); //ignore the character after an escape, just append it if (++i>=endIndex) throw UndertowMessages.MESSAGES.invalidEscapeCharacter(); b.append(s.charAt(i)); } else if (c == '"') b.append('\\').append('"'); else b.append(c); } return b.toString(); } public static int adjustedCookieVersion(Cookie cookie) { /* * The spec allows some latitude on when to send the version attribute * with a Set-Cookie header. To be nice to clients, we'll make sure the * version attribute is first. That means checking the various things * that can cause us to switch to a v1 cookie first. *_ * Note that by checking for tokens we will also throw an exception if a * control character is encountered. */ int version = cookie.getVersion(); String value = cookie.getValue(); String path = cookie.getPath(); String domain = cookie.getDomain(); String comment = cookie.getComment(); // If it is v0, check if we need to switch if (version == 0 && (!ALLOW_HTTP_SEPARATORS_IN_V0 && isHttpToken(value) || ALLOW_HTTP_SEPARATORS_IN_V0 && isV0Token(value))) { // HTTP token in value - need to use v1 version = 1; } if (version == 0 && comment != null) { // Using a comment makes it a v1 cookie version = 1; } if (version == 0 && (!ALLOW_HTTP_SEPARATORS_IN_V0 && isHttpToken(path) || ALLOW_HTTP_SEPARATORS_IN_V0 && isV0Token(path))) { // HTTP token in path - need to use v1 version = 1; } if (version == 0 && (!ALLOW_HTTP_SEPARATORS_IN_V0 && isHttpToken(domain) || ALLOW_HTTP_SEPARATORS_IN_V0 && isV0Token(domain))) { // HTTP token in domain - need to use v1 version = 1; } return version; } // ------------------------------------------------------------- Constructor private LegacyCookieSupport() { // Utility class. Don't allow instances to be created. } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/LocaleUtils.java000066400000000000000000000053031420065311100263750ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; /** * Utility methods for getting the locale from a request. * * @author Stuart Douglas */ public class LocaleUtils { public static Locale getLocaleFromString(String localeString) { if (localeString == null) { return null; } return Locale.forLanguageTag(localeString); } /** * Parse a header string and return the list of locales that were found. * * If the header is empty or null then an empty list will be returned. * * @param acceptLanguage The Accept-Language header * @return The list of locales, in order of preference */ public static List getLocalesFromHeader(final String acceptLanguage) { if(acceptLanguage == null) { return Collections.emptyList(); } return getLocalesFromHeader(Collections.singletonList(acceptLanguage)); } /** * Parse a header string and return the list of locales that were found. * * If the header is empty or null then an empty list will be returned. * * @param acceptLanguage The Accept-Language header * @return The list of locales, in order of preference */ public static List getLocalesFromHeader(final List acceptLanguage) { if (acceptLanguage == null || acceptLanguage.isEmpty()) { return Collections.emptyList(); } final List ret = new ArrayList<>(); final List> parsedResults = QValueParser.parse(acceptLanguage); for (List qvalueResult : parsedResults) { for (QValueParser.QValueResult res : qvalueResult) { if (!res.isQValueZero()) { Locale e = LocaleUtils.getLocaleFromString(res.getValue()); ret.add(e); } } } return ret; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/MalformedMessageException.java000066400000000000000000000016561420065311100312560ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.io.IOException; /** * Exception that is thrown when multipart parsing cannot parse a request * * @author Stuart Douglas */ public class MalformedMessageException extends IOException { } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/Methods.java000066400000000000000000000151171420065311100255640ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.util.Collections; import java.util.HashMap; import java.util.Map; import io.undertow.server.Connectors; /** * NOTE: If you add a new method here you must also add it to {@link io.undertow.server.protocol.http.HttpRequestParser} * * @author David M. Lloyd */ public final class Methods { private Methods() { } public static final String OPTIONS_STRING = "OPTIONS"; public static final String GET_STRING = "GET"; public static final String HEAD_STRING = "HEAD"; public static final String POST_STRING = "POST"; public static final String PUT_STRING = "PUT"; public static final String DELETE_STRING = "DELETE"; public static final String TRACE_STRING = "TRACE"; public static final String CONNECT_STRING = "CONNECT"; public static final String PATCH_STRING = "PATCH"; public static final String PROPFIND_STRING = "PROPFIND"; public static final String PROPPATCH_STRING = "PROPPATCH"; public static final String MKCOL_STRING = "MKCOL"; public static final String COPY_STRING = "COPY"; public static final String MOVE_STRING = "MOVE"; public static final String LOCK_STRING = "LOCK"; public static final String UNLOCK_STRING = "UNLOCK"; public static final String ACL_STRING = "ACL"; public static final String REPORT_STRING = "REPORT"; public static final String VERSION_CONTROL_STRING = "VERSION-CONTROL"; public static final String CHECKIN_STRING = "CHECKIN"; public static final String CHECKOUT_STRING = "CHECKOUT"; public static final String UNCHECKOUT_STRING = "UNCHECKOUT"; public static final String SEARCH_STRING = "SEARCH"; public static final String MKWORKSPACE_STRING = "MKWORKSPACE"; public static final String UPDATE_STRING = "UPDATE"; public static final String LABEL_STRING = "LABEL"; public static final String MERGE_STRING = "MERGE"; public static final String BASELINE_CONTROL_STRING = "BASELINE_CONTROL"; public static final String MKACTIVITY_STRING = "MKACTIVITY"; public static final HttpString OPTIONS = new HttpString(OPTIONS_STRING); public static final HttpString GET = new HttpString(GET_STRING); public static final HttpString HEAD = new HttpString(HEAD_STRING); public static final HttpString POST = new HttpString(POST_STRING); public static final HttpString PUT = new HttpString(PUT_STRING); public static final HttpString DELETE = new HttpString(DELETE_STRING); public static final HttpString TRACE = new HttpString(TRACE_STRING); public static final HttpString CONNECT = new HttpString(CONNECT_STRING); public static final HttpString PATCH = new HttpString(PATCH_STRING); public static final HttpString PROPFIND = new HttpString(PROPFIND_STRING); public static final HttpString PROPPATCH = new HttpString(PROPPATCH_STRING); public static final HttpString MKCOL = new HttpString(MKCOL_STRING); public static final HttpString COPY = new HttpString(COPY_STRING); public static final HttpString MOVE = new HttpString(MOVE_STRING); public static final HttpString LOCK = new HttpString(LOCK_STRING); public static final HttpString UNLOCK = new HttpString(UNLOCK_STRING); public static final HttpString ACL = new HttpString(ACL_STRING); public static final HttpString REPORT = new HttpString(REPORT_STRING); public static final HttpString VERSION_CONTROL = new HttpString(VERSION_CONTROL_STRING); public static final HttpString CHECKIN = new HttpString(CHECKIN_STRING); public static final HttpString CHECKOUT = new HttpString(CHECKOUT_STRING); public static final HttpString UNCHECKOUT = new HttpString(UNCHECKOUT_STRING); public static final HttpString SEARCH = new HttpString(SEARCH_STRING); public static final HttpString MKWORKSPACE = new HttpString(MKWORKSPACE_STRING); public static final HttpString UPDATE = new HttpString(UPDATE_STRING); public static final HttpString LABEL = new HttpString(LABEL_STRING); public static final HttpString MERGE = new HttpString(MERGE_STRING); public static final HttpString BASELINE_CONTROL = new HttpString(BASELINE_CONTROL_STRING); public static final HttpString MKACTIVITY = new HttpString(MKACTIVITY_STRING); private static final Map METHODS; static { Map methods = new HashMap<>(); putString(methods, OPTIONS); putString(methods, GET); putString(methods, HEAD); putString(methods, POST); putString(methods, PUT); putString(methods, DELETE); putString(methods, TRACE); putString(methods, CONNECT); putString(methods, PATCH); putString(methods, PROPFIND); putString(methods, PROPPATCH); putString(methods, MKCOL); putString(methods, COPY); putString(methods, MOVE); putString(methods, LOCK); putString(methods, UNLOCK); putString(methods, ACL); putString(methods, REPORT); putString(methods, VERSION_CONTROL); putString(methods, CHECKIN); putString(methods, CHECKOUT); putString(methods, UNCHECKOUT); putString(methods, SEARCH); putString(methods, MKWORKSPACE); putString(methods, UPDATE); putString(methods, LABEL); putString(methods, MERGE); putString(methods, BASELINE_CONTROL); putString(methods, MKACTIVITY); METHODS = Collections.unmodifiableMap(methods); } private static void putString(Map methods, HttpString options) { methods.put(options.toString(), options); } public static HttpString fromString(String method) { HttpString res = METHODS.get(method); if(res == null) { HttpString httpString = new HttpString(method); Connectors.verifyToken(httpString); return httpString; } return res; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/MimeMappings.java000066400000000000000000000203101420065311100265360ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.util.Collections; import java.util.HashMap; import java.util.Locale; import java.util.Map; /** * * @author Stuart Douglas */ public class MimeMappings { private final Map mappings; public static final Map DEFAULT_MIME_MAPPINGS; static { Map defaultMappings = new HashMap<>(101); defaultMappings.put("txt", "text/plain"); defaultMappings.put("css", "text/css"); defaultMappings.put("html", "text/html"); defaultMappings.put("htm", "text/html"); defaultMappings.put("gif", "image/gif"); defaultMappings.put("jpg", "image/jpeg"); defaultMappings.put("jpe", "image/jpeg"); defaultMappings.put("jpeg", "image/jpeg"); defaultMappings.put("bmp", "image/bmp"); defaultMappings.put("js", "application/javascript"); defaultMappings.put("png", "image/png"); defaultMappings.put("java", "text/plain"); defaultMappings.put("body", "text/html"); defaultMappings.put("rtx", "text/richtext"); defaultMappings.put("tsv", "text/tab-separated-values"); defaultMappings.put("etx", "text/x-setext"); defaultMappings.put("json", "application/json"); defaultMappings.put("class", "application/java"); defaultMappings.put("csh", "application/x-csh"); defaultMappings.put("sh", "application/x-sh"); defaultMappings.put("tcl", "application/x-tcl"); defaultMappings.put("tex", "application/x-tex"); defaultMappings.put("texinfo", "application/x-texinfo"); defaultMappings.put("texi", "application/x-texinfo"); defaultMappings.put("t", "application/x-troff"); defaultMappings.put("tr", "application/x-troff"); defaultMappings.put("roff", "application/x-troff"); defaultMappings.put("man", "application/x-troff-man"); defaultMappings.put("me", "application/x-troff-me"); defaultMappings.put("ms", "application/x-wais-source"); defaultMappings.put("src", "application/x-wais-source"); defaultMappings.put("zip", "application/zip"); defaultMappings.put("bcpio", "application/x-bcpio"); defaultMappings.put("cpio", "application/x-cpio"); defaultMappings.put("gtar", "application/x-gtar"); defaultMappings.put("shar", "application/x-shar"); defaultMappings.put("sv4cpio", "application/x-sv4cpio"); defaultMappings.put("sv4crc", "application/x-sv4crc"); defaultMappings.put("tar", "application/x-tar"); defaultMappings.put("ustar", "application/x-ustar"); defaultMappings.put("dvi", "application/x-dvi"); defaultMappings.put("hdf", "application/x-hdf"); defaultMappings.put("latex", "application/x-latex"); defaultMappings.put("bin", "application/octet-stream"); defaultMappings.put("oda", "application/oda"); defaultMappings.put("pdf", "application/pdf"); defaultMappings.put("ps", "application/postscript"); defaultMappings.put("eps", "application/postscript"); defaultMappings.put("ai", "application/postscript"); defaultMappings.put("rtf", "application/rtf"); defaultMappings.put("nc", "application/x-netcdf"); defaultMappings.put("cdf", "application/x-netcdf"); defaultMappings.put("cer", "application/x-x509-ca-cert"); defaultMappings.put("exe", "application/octet-stream"); defaultMappings.put("gz", "application/x-gzip"); defaultMappings.put("Z", "application/x-compress"); defaultMappings.put("z", "application/x-compress"); defaultMappings.put("hqx", "application/mac-binhex40"); defaultMappings.put("mif", "application/x-mif"); defaultMappings.put("ico", "image/x-icon"); defaultMappings.put("ief", "image/ief"); defaultMappings.put("tiff", "image/tiff"); defaultMappings.put("tif", "image/tiff"); defaultMappings.put("ras", "image/x-cmu-raster"); defaultMappings.put("pnm", "image/x-portable-anymap"); defaultMappings.put("pbm", "image/x-portable-bitmap"); defaultMappings.put("pgm", "image/x-portable-graymap"); defaultMappings.put("ppm", "image/x-portable-pixmap"); defaultMappings.put("rgb", "image/x-rgb"); defaultMappings.put("xbm", "image/x-xbitmap"); defaultMappings.put("xpm", "image/x-xpixmap"); defaultMappings.put("xwd", "image/x-xwindowdump"); defaultMappings.put("au", "audio/basic"); defaultMappings.put("snd", "audio/basic"); defaultMappings.put("aif", "audio/x-aiff"); defaultMappings.put("aiff", "audio/x-aiff"); defaultMappings.put("aifc", "audio/x-aiff"); defaultMappings.put("wav", "audio/x-wav"); defaultMappings.put("mp3", "audio/mpeg"); defaultMappings.put("mpeg", "video/mpeg"); defaultMappings.put("mpg", "video/mpeg"); defaultMappings.put("mpe", "video/mpeg"); defaultMappings.put("qt", "video/quicktime"); defaultMappings.put("mov", "video/quicktime"); defaultMappings.put("avi", "video/x-msvideo"); defaultMappings.put("movie", "video/x-sgi-movie"); defaultMappings.put("avx", "video/x-rad-screenplay"); defaultMappings.put("wrl", "x-world/x-vrml"); defaultMappings.put("mpv2", "video/mpeg2"); defaultMappings.put("jnlp", "application/x-java-jnlp-file"); defaultMappings.put("eot", "application/vnd.ms-fontobject"); defaultMappings.put("woff", "application/font-woff"); defaultMappings.put("woff2", "application/font-woff2"); defaultMappings.put("ttf", "application/x-font-ttf"); defaultMappings.put("otf", "application/x-font-opentype"); defaultMappings.put("sfnt", "application/font-sfnt"); /* Add XML related MIMEs */ defaultMappings.put("xml", "application/xml"); defaultMappings.put("xhtml", "application/xhtml+xml"); defaultMappings.put("xsl", "application/xml"); defaultMappings.put("svg", "image/svg+xml"); defaultMappings.put("svgz", "image/svg+xml"); defaultMappings.put("wbmp", "image/vnd.wap.wbmp"); defaultMappings.put("wml", "text/vnd.wap.wml"); defaultMappings.put("wmlc", "application/vnd.wap.wmlc"); defaultMappings.put("wmls", "text/vnd.wap.wmlscript"); defaultMappings.put("wmlscriptc", "application/vnd.wap.wmlscriptc"); DEFAULT_MIME_MAPPINGS = Collections.unmodifiableMap(defaultMappings); } public static final MimeMappings DEFAULT = MimeMappings.builder().build(); MimeMappings(final Map mappings) { this.mappings = mappings; } public static Builder builder() { return new Builder(true); } public static Builder builder(boolean includeDefault) { return new Builder(includeDefault); } public static class Builder { private final Map mappings = new HashMap<>(); private Builder(boolean includeDefault) { if (includeDefault) { mappings.putAll(DEFAULT_MIME_MAPPINGS); } } public Builder addMapping(final String extension, final String contentType) { mappings.put(extension.toLowerCase(Locale.ENGLISH), contentType); return this; } public MimeMappings build() { return new MimeMappings(mappings); } } public String getMimeType(final String extension) { return mappings.get(extension.toLowerCase(Locale.ENGLISH)); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/MultipartParser.java000066400000000000000000000424661420065311100273260ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; /** * @author Stuart Douglas */ public class MultipartParser { /** * The Horizontal Tab ASCII character value; */ public static final byte HTAB = 0x09; /** * The Carriage Return ASCII character value. */ public static final byte CR = 0x0D; /** * The Line Feed ASCII character value. */ public static final byte LF = 0x0A; /** * The Space ASCII character value; */ public static final byte SP = 0x20; /** * The dash (-) ASCII character value. */ public static final byte DASH = 0x2D; /** * A byte sequence that precedes a boundary (CRLF--). */ private static final byte[] BOUNDARY_PREFIX = {CR, LF, DASH, DASH}; public interface PartHandler { void beginPart(final HeaderMap headers); void data(final ByteBuffer buffer) throws IOException; void endPart(); } public static ParseState beginParse(final ByteBufferPool bufferPool, final PartHandler handler, final byte[] boundary, final String requestCharset) { // We prepend CR/LF to the boundary to chop trailing CR/LF from // body-data tokens. byte[] boundaryToken = new byte[boundary.length + BOUNDARY_PREFIX.length]; System.arraycopy(BOUNDARY_PREFIX, 0, boundaryToken, 0, BOUNDARY_PREFIX.length); System.arraycopy(boundary, 0, boundaryToken, BOUNDARY_PREFIX.length, boundary.length); return new ParseState(bufferPool, handler, requestCharset, boundaryToken); } public static class ParseState { private final ByteBufferPool bufferPool; private final PartHandler partHandler; private String requestCharset; /** * The boundary, complete with the initial CRLF-- */ private final byte[] boundary; //0=preamble private int state = 0; private int subState = Integer.MAX_VALUE; // used for preamble parsing private ByteArrayOutputStream currentString = null; private String currentHeaderName = null; private HeaderMap headers; private Encoding encodingHandler; public ParseState(final ByteBufferPool bufferPool, final PartHandler partHandler, String requestCharset, final byte[] boundary) { this.bufferPool = bufferPool; this.partHandler = partHandler; this.requestCharset = requestCharset; this.boundary = boundary; } public void setCharacterEncoding(String encoding) { requestCharset = encoding; } public void parse(ByteBuffer buffer) throws IOException { while (buffer.hasRemaining()) { switch (state) { case 0: { preamble(buffer); break; } case 1: { headerName(buffer); break; } case 2: { headerValue(buffer); break; } case 3: { entity(buffer); break; } case -1: { return; } default: { throw new IllegalStateException("" + state); } } } } private void preamble(final ByteBuffer buffer) { while (buffer.hasRemaining()) { final byte b = buffer.get(); if (subState >= 0) { //handle the case of no preamble. In this case there is no CRLF if (subState == Integer.MAX_VALUE) { if (boundary[2] == b) { subState = 2; } else { subState = 0; } } if (b == boundary[subState]) { subState++; if (subState == boundary.length) { subState = -1; } } else if (b == boundary[0]) { subState = 1; } else { subState = 0; } } else if (subState == -1) { if (b == CR) { subState = -2; } } else if (subState == -2) { if (b == LF) { subState = 0; state = 1;//preamble is done headers = new HeaderMap(); return; } else { subState = -1; } } } } private void headerName(final ByteBuffer buffer) throws MalformedMessageException, UnsupportedEncodingException { while (buffer.hasRemaining()) { final byte b = buffer.get(); if (b == ':') { if (currentString == null || subState != 0) { throw new MalformedMessageException(); } else { currentHeaderName = new String(currentString.toByteArray(), requestCharset); currentString.reset(); subState = 0; state = 2; return; } } else if (b == CR) { if (currentString != null) { throw new MalformedMessageException(); } else { subState = 1; } } else if (b == LF) { if (currentString != null || subState != 1) { throw new MalformedMessageException(); } state = 3; subState = 0; partHandler.beginPart(headers); //select the appropriate encoding String encoding = headers.getFirst(Headers.CONTENT_TRANSFER_ENCODING); if (encoding == null) { encodingHandler = new IdentityEncoding(); } else if (encoding.equalsIgnoreCase("base64")) { encodingHandler = new Base64Encoding(bufferPool); } else if (encoding.equalsIgnoreCase("quoted-printable")) { encodingHandler = new QuotedPrintableEncoding(bufferPool); } else { encodingHandler = new IdentityEncoding(); } headers = null; return; } else { if (subState != 0) { throw new MalformedMessageException(); } else if (currentString == null) { currentString = new ByteArrayOutputStream(); } currentString.write(b); } } } private void headerValue(final ByteBuffer buffer) throws MalformedMessageException, UnsupportedEncodingException { while (buffer.hasRemaining()) { final byte b = buffer.get(); if(subState == 2) { if (b == CR) { //end of headers section headers.put(new HttpString(currentHeaderName.trim()), new String(currentString.toByteArray(), requestCharset).trim()); //set state for headerName to verify end of headers section state = 1; subState = 1; //CR already encountered currentString = null; return; } else if (b == SP || b == HTAB) { //multi-line header currentString.write(b); subState = 0; } else { //next header name headers.put(new HttpString(currentHeaderName.trim()), new String(currentString.toByteArray(), requestCharset).trim()); //set state for headerName to collect next header's name state = 1; subState = 0; //start name collection for headerName to finish currentString = new ByteArrayOutputStream(); currentString.write(b); return; } } else if (b == CR) { subState = 1; } else if (b == LF) { if (subState != 1) { throw new MalformedMessageException(); } subState = 2; } else { if (subState != 0) { throw new MalformedMessageException(); } currentString.write(b); } } } private void entity(final ByteBuffer buffer) throws IOException { int startingSubState = subState; int pos = buffer.position(); while (buffer.hasRemaining()) { final byte b = buffer.get(); if (subState >= 0) { if (b == boundary[subState]) { //if we have a potential boundary match subState++; if (subState == boundary.length) { startingSubState = 0; //we have our data ByteBuffer retBuffer = buffer.duplicate(); retBuffer.position(pos); retBuffer.limit(Math.max(buffer.position() - boundary.length, 0)); encodingHandler.handle(partHandler, retBuffer); partHandler.endPart(); subState = -1; } } else if (b == boundary[0]) { //we started half way through a boundary, but it turns out we did not actually meet the boundary condition //so we call the part handler with our copy of the boundary data if (startingSubState > 0) { encodingHandler.handle(partHandler, ByteBuffer.wrap(boundary, 0, startingSubState)); startingSubState = 0; } subState = 1; } else { //we started half way through a boundary, but it turns out we did not actually meet the boundary condition //so we call the part handler with our copy of the boundary data if (startingSubState > 0) { encodingHandler.handle(partHandler, ByteBuffer.wrap(boundary, 0, startingSubState)); startingSubState = 0; } subState = 0; } } else if (subState == -1) { if (b == CR) { subState = -2; } else if (b == DASH) { subState = -3; } } else if (subState == -2) { if (b == LF) { //ok, we have our data subState = 0; state = 1; headers = new HeaderMap(); return; } else if (b == DASH) { subState = -3; } else { subState = -1; } } else if (subState == -3) { if (b == DASH) { state = -1; //we are done return; } else { subState = -1; } } } //handle the data we read so far ByteBuffer retBuffer = buffer.duplicate(); retBuffer.position(pos); if (subState == 0) { //if we end partially through a boundary we do not handle the data encodingHandler.handle(partHandler, retBuffer); } else if (retBuffer.remaining() > subState && subState > 0) { //we have some data to handle, and the end of the buffer might be a boundary match retBuffer.limit(retBuffer.limit() - subState); encodingHandler.handle(partHandler, retBuffer); } } public boolean isComplete() { return state == -1; } } private interface Encoding { void handle(final PartHandler handler, final ByteBuffer rawData) throws IOException; } private static class IdentityEncoding implements Encoding { @Override public void handle(final PartHandler handler, final ByteBuffer rawData) throws IOException { handler.data(rawData); rawData.clear(); } } private static class Base64Encoding implements Encoding { private final FlexBase64.Decoder decoder = FlexBase64.createDecoder(); private final ByteBufferPool bufferPool; private Base64Encoding(final ByteBufferPool bufferPool) { this.bufferPool = bufferPool; } @Override public void handle(final PartHandler handler, final ByteBuffer rawData) throws IOException { PooledByteBuffer resource = bufferPool.allocate(); ByteBuffer buf = resource.getBuffer(); try { do { buf.clear(); try { decoder.decode(rawData, buf); } catch (IOException e) { throw new RuntimeException(e); } buf.flip(); handler.data(buf); } while (rawData.hasRemaining()); } finally { resource.close(); } } } private static class QuotedPrintableEncoding implements Encoding { private final ByteBufferPool bufferPool; boolean equalsSeen; byte firstCharacter; private QuotedPrintableEncoding(final ByteBufferPool bufferPool) { this.bufferPool = bufferPool; } @Override public void handle(final PartHandler handler, final ByteBuffer rawData) throws IOException { boolean equalsSeen = this.equalsSeen; byte firstCharacter = this.firstCharacter; PooledByteBuffer resource = bufferPool.allocate(); ByteBuffer buf = resource.getBuffer(); try { while (rawData.hasRemaining()) { byte b = rawData.get(); if (equalsSeen) { if (firstCharacter == 0) { if (b == '\n' || b == '\r') { //soft line break //ignore equalsSeen = false; } else { firstCharacter = b; } } else { int result = Character.digit((char) firstCharacter, 16); result <<= 4; //shift it 4 bytes and then add the next value to the end result += Character.digit((char) b, 16); buf.put((byte) result); equalsSeen = false; firstCharacter = 0; } } else if (b == '=') { equalsSeen = true; } else { buf.put(b); if (!buf.hasRemaining()) { buf.flip(); handler.data(buf); buf.clear(); } } } buf.flip(); handler.data(buf); } finally { resource.close(); this.equalsSeen = equalsSeen; this.firstCharacter = firstCharacter; } } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/NetworkUtils.java000066400000000000000000000146701420065311100266360ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.UndertowMessages; import java.io.IOException; import java.net.Inet4Address; import java.net.InetAddress; /** * @author Stuart Douglas * @author baranowb */ public class NetworkUtils { public static final String IP4_EXACT = "(?:\\d{1,3}\\.){3}\\d{1,3}"; /** * IPV6 match. ?: - unnamed groups are used for performance reasons. * Requirements: * - match full or partial IPV6 ( sliding '::') * - match end to start - ^$ to ensure it does not match part of some random (\d:){n,m} * - IPv4-Embedded IPv6 Address * * NO: * - IPv4 mapped/translated into IPv6 * * ^(?:([0-9a-fA-F]{1,4}:){7,7}(?:[0-9a-fA-F]){1,4} - full address * |(?:([0-9a-fA-F]{1,4}:)){1,7}(?:(:)) - last compressed * |(?:([0-9a-fA-F]{1,4}:)){1,6}(?:(:[0-9a-fA-F]){1,4}) - second to last * |(?:([0-9a-fA-F]{1,4}:)){1,5}(?:(:[0-9a-fA-F]{1,4})){1,2} - etc * |(?:([0-9a-fA-F]{1,4}:)){1,4}(?:(:[0-9a-fA-F]{1,4})){1,3} * |(?:([0-9a-fA-F]{1,4}:)){1,3}(?:(:[0-9a-fA-F]{1,4})){1,4} * |(?:([0-9a-fA-F]{1,4}:)){1,2}(?:(:[0-9a-fA-F]{1,4})){1,5} * |(?:([0-9a-fA-F]{1,4}:))(?:(:[0-9a-fA-F]{1,4})){1,6} * |(?:(:))(?:((:[0-9a-fA-F]{1,4}){1,7}|(?:(:)))))$ - all the way compressed */ public static final String IP6_EXACT = "^(?:([0-9a-fA-F]{1,4}:){7,7}(?:[0-9a-fA-F]){1,4}|(?:([0-9a-fA-F]{1,4}:)){1,7}(?:(:))|(?:([0-9a-fA-F]{1,4}:)){1,6}(?:(:[0-9a-fA-F]){1,4})|(?:([0-9a-fA-F]{1,4}:)){1,5}(?:(:[0-9a-fA-F]{1,4})){1,2}|(?:([0-9a-fA-F]{1,4}:)){1,4}(?:(:[0-9a-fA-F]{1,4})){1,3}|(?:([0-9a-fA-F]{1,4}:)){1,3}(?:(:[0-9a-fA-F]{1,4})){1,4}|(?:([0-9a-fA-F]{1,4}:)){1,2}(?:(:[0-9a-fA-F]{1,4})){1,5}|(?:([0-9a-fA-F]{1,4}:))(?:(:[0-9a-fA-F]{1,4})){1,6}|(?:(:))(?:((:[0-9a-fA-F]{1,4}){1,7}|(?:(:)))))$"; public static String formatPossibleIpv6Address(String address) { if (address == null) { return null; } if (!address.contains(":")) { return address; } if (address.startsWith("[") && address.endsWith("]")) { return address; } return "[" + address + "]"; } public static InetAddress parseIpv4Address(String addressString) throws IOException { String[] parts = addressString.split("\\."); if (parts.length != 4) { throw UndertowMessages.MESSAGES.invalidIpAddress(addressString); } byte[] data = new byte[4]; for (int i = 0; i < 4; ++i) { String part = parts[i]; if (part.length() == 0 || (part.charAt(0) == '0' && part.length() > 1)) { //leading zeros are not allowed throw UndertowMessages.MESSAGES.invalidIpAddress(addressString); } data[i] = (byte) Integer.parseInt(part); } return InetAddress.getByAddress(data); } public static InetAddress parseIpv6Address(final String addressString) throws IOException { return InetAddress.getByAddress(parseIpv6AddressToBytes(addressString)); } public static byte[] parseIpv6AddressToBytes(final String addressString) throws IOException { boolean startsWithColon = addressString.startsWith(":"); if (startsWithColon && !addressString.startsWith("::")) { throw UndertowMessages.MESSAGES.invalidIpAddress(addressString); } String[] parts = (startsWithColon ? addressString.substring(1) : addressString).split(":"); //because of the way split works we want to change a leading double colon to a single one. We have already verified that the address does not actually start with a single colon byte[] data = new byte[16]; int partOffset = 0; boolean seenEmpty = false; if (parts.length > 8) { throw UndertowMessages.MESSAGES.invalidIpAddress(addressString); } for (int i = 0; i < parts.length; ++i) { String part = parts[i]; if (part.length() > 4) { throw UndertowMessages.MESSAGES.invalidIpAddress(addressString); } else if (part.isEmpty()) { if (seenEmpty) { throw UndertowMessages.MESSAGES.invalidIpAddress(addressString); } seenEmpty = true; int off = 8 - parts.length;//this works because of the empty part that represents the double colon, so the parts list is one larger than the number of digits if (off < 0) { throw UndertowMessages.MESSAGES.invalidIpAddress(addressString); } partOffset = off * 2; } else if (part.length() > 1 && part.charAt(0) == '0') { //leading zeros are not allowed throw UndertowMessages.MESSAGES.invalidIpAddress(addressString); } else { int num = Integer.parseInt(part, 16); data[i * 2 + partOffset] = (byte) (num >> 8); data[i * 2 + partOffset + 1] = (byte) (num); } } if ((parts.length < 8 && !addressString.endsWith("::")) && !seenEmpty) { //address was too small throw UndertowMessages.MESSAGES.invalidIpAddress(addressString); } return data; } public static String toObfuscatedString(InetAddress address) { if (address == null) { return null; } String s = address.getHostAddress(); if (address instanceof Inet4Address) { // IPv4 addresses: cut off last byte return s.substring(0, s.lastIndexOf(".")+1); } // IPv6 addresses: cut off at second colon return s.substring(0, s.indexOf(":", s.indexOf(":")+1)+1); } private NetworkUtils() { } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/NewInstanceObjectPool.java000066400000000000000000000033351420065311100303570ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.util.function.Consumer; import java.util.function.Supplier; import io.undertow.UndertowMessages; /** * @author ckozak * @author Stuart Douglas */ public class NewInstanceObjectPool implements ObjectPool { private final Supplier supplier; private final Consumer consumer; public NewInstanceObjectPool(Supplier supplier, Consumer consumer) { this.supplier = supplier; this.consumer = consumer; } @Override public PooledObject allocate() { final T obj = supplier.get(); return new PooledObject() { private volatile boolean closed = false; @Override public T getObject() { if(closed) { throw UndertowMessages.MESSAGES.objectIsClosed(); } return obj; } @Override public void close() { closed = true; consumer.accept(obj); } }; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/ObjectPool.java000066400000000000000000000015661420065311100262240ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; /** * A pool of objects. * * @author ckozak * @author Stuart Douglas */ public interface ObjectPool { PooledObject allocate(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/ParameterLimitException.java000066400000000000000000000017511420065311100307560ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; /** * Exception that is thrown if the max query or path parameter limit is exceeded * * @author Stuart Douglas */ public class ParameterLimitException extends Exception { public ParameterLimitException(String message) { super(message); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/PathMatcher.java000066400000000000000000000175751420065311100263730ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import java.util.Collections; import java.util.Comparator; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.ConcurrentMap; /** * Handler that dispatches to a given handler based of a prefix match of the path. *

* This only matches a single level of a request, e.g if you have a request that takes the form: *

* /foo/bar *

* * @author Stuart Douglas */ public class PathMatcher { private static final String STRING_PATH_SEPARATOR = "/"; private volatile T defaultHandler; private final SubstringMap paths = new SubstringMap<>(); private final ConcurrentMap exactPathMatches = new CopyOnWriteMap<>(); /** * lengths of all registered paths */ private volatile int[] lengths = {}; public PathMatcher(final T defaultHandler) { this.defaultHandler = defaultHandler; } public PathMatcher() { } public Set getExactPathMatchesSet(){ return Collections.unmodifiableSet(exactPathMatches.keySet()); } public Set getPathMatchesSet(){ return Collections.unmodifiableSet(paths.toMap().keySet()); } /** * Matches a path against the registered handlers. * @param path The relative path to match * @return The match match. This will never be null, however if none matched its value field will be */ public PathMatch match(String path){ if (!exactPathMatches.isEmpty()) { T match = getExactPath(path); if (match != null) { UndertowLogger.REQUEST_LOGGER.debugf("Matched exact path %s", path); return new PathMatch<>(path, "", match); } } int length = path.length(); final int[] lengths = this.lengths; for (int i = 0; i < lengths.length; ++i) { int pathLength = lengths[i]; if (pathLength == length) { SubstringMap.SubstringMatch next = paths.get(path, length); if (next != null) { UndertowLogger.REQUEST_LOGGER.debugf("Matched prefix path %s for path %s", next.getKey(), path); return new PathMatch<>(path, "", next.getValue()); } } else if (pathLength < length) { char c = path.charAt(pathLength); if (c == '/') { SubstringMap.SubstringMatch next = paths.get(path, pathLength); if (next != null) { UndertowLogger.REQUEST_LOGGER.debugf("Matched prefix path %s for path %s", next.getKey(), path); return new PathMatch<>(next.getKey(), path.substring(pathLength), next.getValue()); } } } } UndertowLogger.REQUEST_LOGGER.debugf("Matched default handler path %s", path); return new PathMatch<>("", path, defaultHandler); } /** * Adds a path prefix and a handler for that path. If the path does not start * with a / then one will be prepended. *

* The match is done on a prefix bases, so registering /foo will also match /foo/bar. Exact * path matches are taken into account first. *

* If / is specified as the path then it will replace the default handler. * * @param path The path * @param handler The handler */ public synchronized PathMatcher addPrefixPath(final String path, final T handler) { if (path.isEmpty()) { throw UndertowMessages.MESSAGES.pathMustBeSpecified(); } final String normalizedPath = URLUtils.normalizeSlashes(path); if (PathMatcher.STRING_PATH_SEPARATOR.equals(normalizedPath)) { this.defaultHandler = handler; return this; } paths.put(normalizedPath, handler); buildLengths(); return this; } public synchronized PathMatcher addExactPath(final String path, final T handler) { if (path.isEmpty()) { throw UndertowMessages.MESSAGES.pathMustBeSpecified(); } exactPathMatches.put(URLUtils.normalizeSlashes(path), handler); return this; } public T getExactPath(final String path) { return exactPathMatches.get(URLUtils.normalizeSlashes(path)); } public T getPrefixPath(final String path) { final String normalizedPath = URLUtils.normalizeSlashes(path); // enable the prefix path mechanism to return the default handler SubstringMap.SubstringMatch match = paths.get(normalizedPath); if (PathMatcher.STRING_PATH_SEPARATOR.equals(normalizedPath) && match == null) { return this.defaultHandler; } if(match == null) { return null; } // return the value for the given path return match.getValue(); } private void buildLengths() { final Set lengths = new TreeSet<>(new Comparator() { @Override public int compare(Integer o1, Integer o2) { return -o1.compareTo(o2); } }); for (String p : paths.keys()) { lengths.add(p.length()); } int[] lengthArray = new int[lengths.size()]; int pos = 0; for (int i : lengths) { lengthArray[pos++] = i; } this.lengths = lengthArray; } @Deprecated public synchronized PathMatcher removePath(final String path) { return removePrefixPath(path); } public synchronized PathMatcher removePrefixPath(final String path) { if (path == null || path.isEmpty()) { throw UndertowMessages.MESSAGES.pathMustBeSpecified(); } final String normalizedPath = URLUtils.normalizeSlashes(path); if (PathMatcher.STRING_PATH_SEPARATOR.equals(normalizedPath)) { defaultHandler = null; return this; } paths.remove(normalizedPath); buildLengths(); return this; } public synchronized PathMatcher removeExactPath(final String path) { if (path == null || path.isEmpty()) { throw UndertowMessages.MESSAGES.pathMustBeSpecified(); } exactPathMatches.remove(URLUtils.normalizeSlashes(path)); return this; } public synchronized PathMatcher clearPaths() { paths.clear(); exactPathMatches.clear(); this.lengths = new int[0]; defaultHandler = null; return this; } public Map getPaths() { return paths.toMap(); } public static final class PathMatch { private final String matched; private final String remaining; private final T value; public PathMatch(String matched, String remaining, T value) { this.matched = matched; this.remaining = remaining; this.value = value; } public String getRemaining() { return remaining; } public String getMatched() { return matched; } public T getValue() { return value; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/PathTemplate.java000066400000000000000000000303341420065311100265470ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.UndertowMessages; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * Represents a parsed web socket path template. *

* This class can be compared to other path templates, with templates that are considered * lower have a higher priority, and should be checked first. *

* This comparison can also be used to check for semantically equal paths, if * a.compareTo(b) == 0 then the two paths are equivalent, which will generally * result in a deployment exception. * * @author Stuart Douglas */ public class PathTemplate implements Comparable { private final String templateString; private final boolean template; private final String base; final List parts; private final Set parameterNames; private final boolean trailingSlash; private PathTemplate(String templateString, final boolean template, final String base, final List parts, Set parameterNames, boolean trailingSlash) { this.templateString = templateString; this.template = template; this.base = base; this.parts = parts; this.parameterNames = Collections.unmodifiableSet(parameterNames); this.trailingSlash = trailingSlash; } public static PathTemplate create(final String inputPath) { // a path is required if(inputPath == null) { throw UndertowMessages.MESSAGES.pathMustBeSpecified(); } // prepend a "/" if none is present if(!inputPath.startsWith("/")) { return PathTemplate.create("/" + inputPath); } // create string from modified string final String path = inputPath; int state = 0; String base = ""; List parts = new ArrayList<>(); int stringStart = 0; //0 parsing base //1 parsing base, last char was / //2 in template part //3 just after template part, expecting / //4 expecting either template or segment //5 in segment for (int i = 0; i < path.length(); ++i) { final int c = path.charAt(i); switch (state) { case 0: { if (c == '/') { state = 1; } else if (c == '*') { base = path.substring(0, i + 1); stringStart = i; state = 5; } else { state = 0; } break; } case 1: { if (c == '{') { base = path.substring(0, i); stringStart = i + 1; state = 2; } else if (c == '*') { base = path.substring(0, i + 1); stringStart = i; state = 5; } else if (c != '/') { state = 0; } break; } case 2: { if (c == '}') { Part part = new Part(true, path.substring(stringStart, i)); parts.add(part); stringStart = i; state = 3; } break; } case 3: { if (c == '/') { state = 4; } else { throw UndertowMessages.MESSAGES.couldNotParseUriTemplate(path, i); } break; } case 4: { if (c == '{') { stringStart = i + 1; state = 2; } else if (c != '/') { stringStart = i; state = 5; } break; } case 5: { if (c == '/') { Part part = new Part(false, path.substring(stringStart, i)); parts.add(part); stringStart = i + 1; state = 4; } break; } } } boolean trailingSlash = false; switch (state) { case 1: trailingSlash = true; //fall through case 0: { base = path; break; } case 2: { throw UndertowMessages.MESSAGES.couldNotParseUriTemplate(path, path.length()); } case 4: { trailingSlash = true; break; } case 5: { Part part = new Part(false, path.substring(stringStart)); parts.add(part); break; } } final Set templates = new HashSet<>(); for(Part part : parts) { if(part.template) { templates.add(part.part); } } return new PathTemplate(path, state > 1 && !base.contains("*"), base, parts, templates, trailingSlash); } /** * Check if the given uri matches the template. If so then it will return true and * place the value of any path parameters into the given map. *

* Note the map may be modified even if the match in unsuccessful, however in this case * it will be emptied before the method returns * * @param path The request path, relative to the context root * @param pathParameters The path parameters map to fill out * @return true if the URI is a match */ public boolean matches(final String path, final Map pathParameters) { if (!template && base.contains("*")) { final int indexOf = base.indexOf("*"); final String startBase = base.substring(0, indexOf); if (!path.startsWith(startBase)) { return false; } pathParameters.put("*", path.substring(indexOf,path.length())); return true; } if (!path.startsWith(base)) { return false; } int baseLength = base.length(); if (!template) { return path.length() == baseLength; } if(trailingSlash) { //the template has a trailing slash //we verify this first as it is cheap //and it simplifies the matching algorithm below if(path.charAt(path.length() -1 ) != '/') { return false; } } int currentPartPosition = 0; PathTemplate.Part current = parts.get(currentPartPosition); int stringStart = baseLength; int i; for (i = baseLength; i < path.length(); ++i) { final char currentChar = path.charAt(i); if (currentChar == '?' || current.part.equals("*")) { break; } else if (currentChar == '/') { String result = path.substring(stringStart, i); if (current.template) { pathParameters.put(current.part, result); } else if (!result.equals(current.part)) { pathParameters.clear(); return false; } ++currentPartPosition; if (currentPartPosition == parts.size()) { //this is a match if this is the last character return i == (path.length() - 1); } current = parts.get(currentPartPosition); stringStart = i + 1; } } if (currentPartPosition + 1 != parts.size()) { pathParameters.clear(); return false; } String result = path.substring(stringStart, i); if (current.part.equals("*")) { pathParameters.put(current.part, path.substring(stringStart,path.length())); return true; } if (current.template) { pathParameters.put(current.part, result); } else if (!result.equals(current.part)) { pathParameters.clear(); return false; } return true; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof PathTemplate)) return false; PathTemplate that = (PathTemplate) o; return this.compareTo(that) == 0; } @Override public int hashCode() { int result = getTemplateString() != null ? getTemplateString().hashCode() : 0; result = 31 * result + (template ? 1 : 0); result = 31 * result + (getBase() != null ? getBase().hashCode() : 0); result = 31 * result + (parts != null ? parts.hashCode() : 0); result = 31 * result + (getParameterNames() != null ? getParameterNames().hashCode() : 0); return result; } @Override public int compareTo(final PathTemplate o) { //we want templates with the highest priority to sort first //so we sort in reverse priority order //templates have lower priority if (template && !o.template) { return 1; } else if (o.template && !template) { return -1; } int res = base.compareTo(o.base); if (res > 0) { //our base is longer return -1; } else if (res < 0) { return 1; } else if (!template) { //they are the same path return 0; } //the first path with a non-template element int i = 0; for (; ; ) { if (parts.size() == i) { if (o.parts.size() == i) { return base.compareTo(o.base); } return 1; } else if (o.parts.size() == i) { //we have more parts, so should be checked first return -1; } Part thisPath = parts.get(i); Part otherPart = o.parts.get(i); if (thisPath.template && !otherPart.template) { //non template part sorts first return 1; } else if (!thisPath.template && otherPart.template) { return -1; } else if (!thisPath.template) { int r = thisPath.part.compareTo(otherPart.part); if (r != 0) { return r; } } ++i; } } public String getBase() { return base; } public String getTemplateString() { return templateString; } public Set getParameterNames() { return parameterNames; } private static class Part { final boolean template; final String part; private Part(final boolean template, final String part) { this.template = template; this.part = part; } @Override public String toString() { return "Part{" + "template=" + template + ", part='" + part + '\'' + '}'; } } @Override public String toString() { return "PathTemplate{" + "template=" + template + ", base='" + base + '\'' + ", parts=" + parts + '}'; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/PathTemplateMatch.java000066400000000000000000000026321420065311100275240ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.util.Map; /** * The result of a path template match. * * @author Stuart Douglas */ public class PathTemplateMatch { public static final AttachmentKey ATTACHMENT_KEY = AttachmentKey.create(PathTemplateMatch.class); private final String matchedTemplate; private final Map parameters; public PathTemplateMatch(String matchedTemplate, Map parameters) { this.matchedTemplate = matchedTemplate; this.parameters = parameters; } public String getMatchedTemplate() { return matchedTemplate; } public Map getParameters() { return parameters; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/PathTemplateMatcher.java000066400000000000000000000221521420065311100300520ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.UndertowMessages; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeSet; /** * Utility class that provides fast path matching of path templates. Templates are stored in a map based on the stem of the template, * and matches longest stem first. *

* TODO: we can probably do this faster using a trie type structure, but I think the current impl should perform ok most of the time * * @author Stuart Douglas */ public class PathTemplateMatcher { /** * Map of path template stem to the path templates that share the same base. */ private Map> pathTemplateMap = new CopyOnWriteMap<>(); /** * lengths of all registered paths */ private volatile int[] lengths = {}; public PathMatchResult match(final String path) { String normalizedPath = "".equals(path) ? "/" : path; if(!normalizedPath.startsWith("/")) normalizedPath = "/"+ normalizedPath; final Map params = new LinkedHashMap<>(); int length = normalizedPath.length(); final int[] lengths = this.lengths; for (int i = 0; i < lengths.length; ++i) { int pathLength = lengths[i]; if (pathLength == length) { Set entry = pathTemplateMap.get(normalizedPath); if (entry != null) { PathMatchResult res = handleStemMatch(entry, normalizedPath, params); if (res != null) { return res; } } } else if (pathLength < length) { String part = normalizedPath.substring(0, pathLength); Set entry = pathTemplateMap.get(part); if (entry != null) { PathMatchResult res = handleStemMatch(entry, normalizedPath, params); if (res != null) { return res; } } } } return null; } private PathMatchResult handleStemMatch(Set entry, final String path, final Map params) { for (PathTemplateHolder val : entry) { if (val.template.matches(path, params)) { return new PathMatchResult<>(params, val.template.getTemplateString(), val.value); } else { params.clear(); } } return null; } public synchronized PathTemplateMatcher add(final PathTemplate template, final T value) { Set values = pathTemplateMap.get(trimBase(template)); Set newValues; if (values == null) { newValues = new TreeSet<>(); } else { newValues = new TreeSet<>(values); } PathTemplateHolder holder = new PathTemplateHolder(value, template); if (newValues.contains(holder)) { PathTemplate equivalent = null; for (PathTemplateHolder item : newValues) { if (item.compareTo(holder) == 0) { equivalent = item.template; break; } } throw UndertowMessages.MESSAGES.matcherAlreadyContainsTemplate(template.getTemplateString(), equivalent.getTemplateString()); } newValues.add(holder); pathTemplateMap.put(trimBase(template), newValues); buildLengths(); return this; } private String trimBase(PathTemplate template) { String retval = template.getBase(); if (template.getBase().endsWith("/") && !template.getParameterNames().isEmpty()) { return retval.substring(0, retval.length() - 1); } if (retval.endsWith("*")) { return retval.substring(0, retval.length() - 1); } return retval; } private void buildLengths() { final Set lengths = new TreeSet<>(new Comparator() { @Override public int compare(Integer o1, Integer o2) { return -o1.compareTo(o2); } }); for (String p : pathTemplateMap.keySet()) { lengths.add(p.length()); } int[] lengthArray = new int[lengths.size()]; int pos = 0; for (int i : lengths) { lengthArray[pos++] = i; //-1 because the base paths end with a / } this.lengths = lengthArray; } public synchronized PathTemplateMatcher add(final String pathTemplate, final T value) { final PathTemplate template = PathTemplate.create(pathTemplate); return add(template, value); } public synchronized PathTemplateMatcher addAll(PathTemplateMatcher pathTemplateMatcher) { for (Entry> entry : pathTemplateMatcher.getPathTemplateMap().entrySet()) { for (PathTemplateHolder pathTemplateHolder : entry.getValue()) { add(pathTemplateHolder.template, pathTemplateHolder.value); } } return this; } Map> getPathTemplateMap() { return pathTemplateMap; } public Set getPathTemplates() { Set templates = new HashSet<>(); for (Set holders : pathTemplateMap.values()) { for (PathTemplateHolder holder: holders) { templates.add(holder.template); } } return templates; } public synchronized PathTemplateMatcher remove(final String pathTemplate) { final PathTemplate template = PathTemplate.create(pathTemplate); return remove(template); } private synchronized PathTemplateMatcher remove(PathTemplate template) { Set values = pathTemplateMap.get(trimBase(template)); Set newValues; if (values == null) { return this; } else { newValues = new TreeSet<>(values); } Iterator it = newValues.iterator(); while (it.hasNext()) { PathTemplateHolder next = it.next(); if (next.template.getTemplateString().equals(template.getTemplateString())) { it.remove(); break; } } if (newValues.size() == 0) { pathTemplateMap.remove(trimBase(template)); } else { pathTemplateMap.put(trimBase(template), newValues); } buildLengths(); return this; } public synchronized T get(String template) { PathTemplate pathTemplate = PathTemplate.create(template); Set values = pathTemplateMap.get(trimBase(pathTemplate)); if(values == null) { return null; } for (PathTemplateHolder next : values) { if (next.template.getTemplateString().equals(template)) { return next.value; } } return null; } public static class PathMatchResult extends PathTemplateMatch { private final T value; public PathMatchResult(Map parameters, String matchedTemplate, T value) { super(matchedTemplate, parameters); this.value = value; } public T getValue() { return value; } } private final class PathTemplateHolder implements Comparable { final T value; final PathTemplate template; private PathTemplateHolder(T value, PathTemplate template) { this.value = value; this.template = template; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null) return false; if (!PathTemplateHolder.class.equals(o.getClass())) return false; PathTemplateHolder that = (PathTemplateHolder) o; return template.equals(that.template); } @Override public int hashCode() { return template.hashCode(); } @Override public int compareTo(PathTemplateHolder o) { return template.compareTo(o.template); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/PipeliningExecutor.java000066400000000000000000000046711420065311100300010ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.UndertowLogger; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Executor; /** * Executor that will continue to re-run tasks in a loop that are submitted from its own thread. * * @author Stuart Douglas */ @Deprecated public class PipeliningExecutor implements Executor { private final Executor executor; private static final ThreadLocal> THREAD_QUEUE = new ThreadLocal<>(); public PipeliningExecutor(Executor executor) { this.executor = executor; } @Override public void execute(final Runnable command) { List queue = THREAD_QUEUE.get(); if (queue != null) { queue.add(command); } else { executor.execute(new Runnable() { @Override public void run() { LinkedList queue = THREAD_QUEUE.get(); if (queue == null) { THREAD_QUEUE.set(queue = new LinkedList<>()); } try { command.run(); } catch (Throwable t) { UndertowLogger.REQUEST_LOGGER.debugf(t, "Task %s failed", command); } Runnable runnable = queue.poll(); while (runnable != null) { try { runnable.run(); } catch (Throwable t) { UndertowLogger.REQUEST_LOGGER.debugf(t, "Task %s failed", command); } runnable = queue.poll(); } } }); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/PooledAdaptor.java000066400000000000000000000027421420065311100267160ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.connector.PooledByteBuffer; import org.xnio.Pooled; import java.nio.ByteBuffer; /** * @author Stuart Douglas */ public class PooledAdaptor implements Pooled { private final PooledByteBuffer buffer; public PooledAdaptor(PooledByteBuffer buffer) { this.buffer = buffer; } @Override public void discard() { buffer.close(); } @Override public void free() { buffer.close(); } @Override public ByteBuffer getResource() throws IllegalStateException { return buffer.getBuffer(); } @Override public void close() { buffer.close(); } @Override public String toString() { return "PooledAdaptor(" + buffer + ")"; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/PooledObject.java000066400000000000000000000016671420065311100265370ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.io.Closeable; /** * Represents a generic pooled object * * @author Stuart Douglas */ public interface PooledObject extends Closeable, AutoCloseable { T getObject(); void close(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/PortableConcurrentDirectDeque.java000066400000000000000000001470501420065311100321150ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ /* * Written by Doug Lea and Martin Buchholz with assistance from members of * JCP JSR-166 Expert Group and released to the public domain, as explained * at http://creativecommons.org/publicdomain/zero/1.0/ */ package io.undertow.util; import java.util.ArrayList; import java.util.Collection; import java.util.Deque; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; /** * A modified version of ConcurrentLinkedDequeue which includes direct * removal and is portable accorss all JVMs. This is only a fallback if * the JVM does not offer access to Unsafe. * * More specifically, an unbounded concurrent {@linkplain java.util.Deque deque} based on linked nodes. * Concurrent insertion, removal, and access operations execute safely * across multiple threads. * A {@code ConcurrentLinkedDeque} is an appropriate choice when * many threads will share access to a common collection. * Like most other concurrent collection implementations, this class * does not permit the use of {@code null} elements. * *

Iterators are weakly consistent, returning elements * reflecting the state of the deque at some point at or since the * creation of the iterator. They do not throw {@link * java.util.ConcurrentModificationException * ConcurrentModificationException}, and may proceed concurrently with * other operations. * *

Beware that, unlike in most collections, the {@code size} method * is NOT a constant-time operation. Because of the * asynchronous nature of these deques, determining the current number * of elements requires a traversal of the elements, and so may report * inaccurate results if this collection is modified during traversal. * Additionally, the bulk operations {@code addAll}, * {@code removeAll}, {@code retainAll}, {@code containsAll}, * {@code equals}, and {@code toArray} are not guaranteed * to be performed atomically. For example, an iterator operating * concurrently with an {@code addAll} operation might view only some * of the added elements. * *

This class and its iterator implement all of the optional * methods of the {@link java.util.Deque} and {@link java.util.Iterator} interfaces. * *

Memory consistency effects: As with other concurrent collections, * actions in a thread prior to placing an object into a * {@code ConcurrentLinkedDeque} * happen-before * actions subsequent to the access or removal of that element from * the {@code ConcurrentLinkedDeque} in another thread. * *

This class is a member of the * * Java Collections Framework. * * @since 1.7 * @author Doug Lea * @author Martin Buchholz * @author Jason T. Grene * @param the type of elements held in this collection */ public class PortableConcurrentDirectDeque extends ConcurrentDirectDeque implements Deque, java.io.Serializable { /* * This is an implementation of a concurrent lock-free deque * supporting interior removes but not interior insertions, as * required to support the entire Deque interface. * * We extend the techniques developed for ConcurrentLinkedQueue and * LinkedTransferQueue (see the internal docs for those classes). * Understanding the ConcurrentLinkedQueue implementation is a * prerequisite for understanding the implementation of this class. * * The data structure is a symmetrical doubly-linked "GC-robust" * linked list of nodes. We minimize the number of volatile writes * using two techniques: advancing multiple hops with a single CAS * and mixing volatile and non-volatile writes of the same memory * locations. * * A node contains the expected E ("item") and links to predecessor * ("prev") and successor ("next") nodes: * * class Node { volatile Node prev, next; volatile E item; } * * A node p is considered "live" if it contains a non-null item * (p.item != null). When an item is CASed to null, the item is * atomically logically deleted from the collection. * * At any time, there is precisely one "first" node with a null * prev reference that terminates any chain of prev references * starting at a live node. Similarly there is precisely one * "last" node terminating any chain of next references starting at * a live node. The "first" and "last" nodes may or may not be live. * The "first" and "last" nodes are always mutually reachable. * * A new element is added atomically by CASing the null prev or * next reference in the first or last node to a fresh node * containing the element. The element's node atomically becomes * "live" at that point. * * A node is considered "active" if it is a live node, or the * first or last node. Active nodes cannot be unlinked. * * A "self-link" is a next or prev reference that is the same node: * p.prev == p or p.next == p * Self-links are used in the node unlinking process. Active nodes * never have self-links. * * A node p is active if and only if: * * p.item != null || * (p.prev == null && p.next != p) || * (p.next == null && p.prev != p) * * The deque object has two node references, "head" and "tail". * The head and tail are only approximations to the first and last * nodes of the deque. The first node can always be found by * following prev pointers from head; likewise for tail. However, * it is permissible for head and tail to be referring to deleted * nodes that have been unlinked and so may not be reachable from * any live node. * * There are 3 stages of node deletion; * "logical deletion", "unlinking", and "gc-unlinking". * * 1. "logical deletion" by CASing item to null atomically removes * the element from the collection, and makes the containing node * eligible for unlinking. * * 2. "unlinking" makes a deleted node unreachable from active * nodes, and thus eventually reclaimable by GC. Unlinked nodes * may remain reachable indefinitely from an iterator. * * Physical node unlinking is merely an optimization (albeit a * critical one), and so can be performed at our convenience. At * any time, the set of live nodes maintained by prev and next * links are identical, that is, the live nodes found via next * links from the first node is equal to the elements found via * prev links from the last node. However, this is not true for * nodes that have already been logically deleted - such nodes may * be reachable in one direction only. * * 3. "gc-unlinking" takes unlinking further by making active * nodes unreachable from deleted nodes, making it easier for the * GC to reclaim future deleted nodes. This step makes the data * structure "gc-robust", as first described in detail by Boehm * (http://portal.acm.org/citation.cfm?doid=503272.503282). * * GC-unlinked nodes may remain reachable indefinitely from an * iterator, but unlike unlinked nodes, are never reachable from * head or tail. * * Making the data structure GC-robust will eliminate the risk of * unbounded memory retention with conservative GCs and is likely * to improve performance with generational GCs. * * When a node is dequeued at either end, e.g. via poll(), we would * like to break any references from the node to active nodes. We * develop further the use of self-links that was very effective in * other concurrent collection classes. The idea is to replace * prev and next pointers with special values that are interpreted * to mean off-the-list-at-one-end. These are approximations, but * good enough to preserve the properties we want in our * traversals, e.g. we guarantee that a traversal will never visit * the same element twice, but we don't guarantee whether a * traversal that runs out of elements will be able to see more * elements later after enqueues at that end. Doing gc-unlinking * safely is particularly tricky, since any node can be in use * indefinitely (for example by an iterator). We must ensure that * the nodes pointed at by head/tail never get gc-unlinked, since * head/tail are needed to get "back on track" by other nodes that * are gc-unlinked. gc-unlinking accounts for much of the * implementation complexity. * * Since neither unlinking nor gc-unlinking are necessary for * correctness, there are many implementation choices regarding * frequency (eagerness) of these operations. Since volatile * reads are likely to be much cheaper than CASes, saving CASes by * unlinking multiple adjacent nodes at a time may be a win. * gc-unlinking can be performed rarely and still be effective, * since it is most important that long chains of deleted nodes * are occasionally broken. * * The actual representation we use is that p.next == p means to * goto the first node (which in turn is reached by following prev * pointers from head), and p.next == null && p.prev == p means * that the iteration is at an end and that p is a (static final) * dummy node, NEXT_TERMINATOR, and not the last active node. * Finishing the iteration when encountering such a TERMINATOR is * good enough for read-only traversals, so such traversals can use * p.next == null as the termination condition. When we need to * find the last (active) node, for enqueueing a new node, we need * to check whether we have reached a TERMINATOR node; if so, * restart traversal from tail. * * The implementation is completely directionally symmetrical, * except that most public methods that iterate through the list * follow next pointers ("forward" direction). * * We believe (without full proof) that all single-element deque * operations (e.g., addFirst, peekLast, pollLast) are linearizable * (see Herlihy and Shavit's book). However, some combinations of * operations are known not to be linearizable. In particular, * when an addFirst(A) is racing with pollFirst() removing B, it is * possible for an observer iterating over the elements to observe * A B C and subsequently observe A C, even though no interior * removes are ever performed. Nevertheless, iterators behave * reasonably, providing the "weakly consistent" guarantees. * * Empirically, microbenchmarks suggest that this class adds about * 40% overhead relative to ConcurrentLinkedQueue, which feels as * good as we can hope for. */ private static final long serialVersionUID = 876323262645176354L; /** * A node from which the first node on list (that is, the unique node p * with p.prev == null && p.next != p) can be reached in O(1) time. * Invariants: * - the first node is always O(1) reachable from head via prev links * - all live nodes are reachable from the first node via succ() * - head != null * - (tmp = head).next != tmp || tmp != head * - head is never gc-unlinked (but may be unlinked) * Non-invariants: * - head.item may or may not be null * - head may not be reachable from the first or last node, or from tail */ private transient volatile Node head; /** * A node from which the last node on list (that is, the unique node p * with p.next == null && p.prev != p) can be reached in O(1) time. * Invariants: * - the last node is always O(1) reachable from tail via next links * - all live nodes are reachable from the last node via pred() * - tail != null * - tail is never gc-unlinked (but may be unlinked) * Non-invariants: * - tail.item may or may not be null * - tail may not be reachable from the first or last node, or from head */ private transient volatile Node tail; private static final AtomicReferenceFieldUpdater headUpdater = AtomicReferenceFieldUpdater.newUpdater(PortableConcurrentDirectDeque.class, Node.class, "head"); private static final AtomicReferenceFieldUpdater tailUpdater = AtomicReferenceFieldUpdater.newUpdater(PortableConcurrentDirectDeque.class, Node.class, "tail"); private static final Node PREV_TERMINATOR, NEXT_TERMINATOR; @SuppressWarnings("unchecked") Node prevTerminator() { return (Node) PREV_TERMINATOR; } @SuppressWarnings("unchecked") Node nextTerminator() { return (Node) NEXT_TERMINATOR; } static final class Node { private static final AtomicReferenceFieldUpdater prevUpdater = AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "prev"); private static final AtomicReferenceFieldUpdater nextUpdater = AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "next"); private static final AtomicReferenceFieldUpdater itemUpdater = AtomicReferenceFieldUpdater.newUpdater(Node.class, Object.class, "item"); volatile Node prev; volatile E item; volatile Node next; Node() { // default constructor for NEXT_TERMINATOR, PREV_TERMINATOR } /** * Constructs a new node. Uses relaxed write because item can * only be seen after publication via casNext or casPrev. */ Node(E item) { this.item = item; } boolean casItem(E cmp, E val) { return itemUpdater.compareAndSet(this, cmp, val); } void lazySetNext(Node val) { next = val; } boolean casNext(Node cmp, Node val) { return nextUpdater.compareAndSet(this, cmp, val); } void lazySetPrev(Node val) { prev = val; } boolean casPrev(Node cmp, Node val) { return prevUpdater.compareAndSet(this, cmp, val); } } /** * Links e as first element. */ private Node linkFirst(E e) { checkNotNull(e); final Node newNode = new Node<>(e); restartFromHead: for (;;) for (Node h = head, p = h, q;;) { if ((q = p.prev) != null && (q = (p = q).prev) != null) // Check for head updates every other hop. // If p == q, we are sure to follow head instead. p = (h != (h = head)) ? h : q; else if (p.next == p) // PREV_TERMINATOR continue restartFromHead; else { // p is first node newNode.lazySetNext(p); // CAS piggyback if (p.casPrev(null, newNode)) { // Successful CAS is the linearization point // for e to become an element of this deque, // and for newNode to become "live". if (p != h) // hop two nodes at a time casHead(h, newNode); // Failure is OK. return newNode; } // Lost CAS race to another thread; re-read prev } } } /** * Links e as last element. */ private Node linkLast(E e) { checkNotNull(e); final Node newNode = new Node<>(e); restartFromTail: for (;;) for (Node t = tail, p = t, q;;) { if ((q = p.next) != null && (q = (p = q).next) != null) // Check for tail updates every other hop. // If p == q, we are sure to follow tail instead. p = (t != (t = tail)) ? t : q; else if (p.prev == p) // NEXT_TERMINATOR continue restartFromTail; else { // p is last node newNode.lazySetPrev(p); // CAS piggyback if (p.casNext(null, newNode)) { // Successful CAS is the linearization point // for e to become an element of this deque, // and for newNode to become "live". if (p != t) // hop two nodes at a time casTail(t, newNode); // Failure is OK. return newNode; } // Lost CAS race to another thread; re-read next } } } private static final int HOPS = 2; /** * Unlinks non-null node x. */ void unlink(Node x) { final Node prev = x.prev; final Node next = x.next; if (prev == null) { unlinkFirst(x, next); } else if (next == null) { unlinkLast(x, prev); } else { // Unlink interior node. // // This is the common case, since a series of polls at the // same end will be "interior" removes, except perhaps for // the first one, since end nodes cannot be unlinked. // // At any time, all active nodes are mutually reachable by // following a sequence of either next or prev pointers. // // Our strategy is to find the unique active predecessor // and successor of x. Try to fix up their links so that // they point to each other, leaving x unreachable from // active nodes. If successful, and if x has no live // predecessor/successor, we additionally try to gc-unlink, // leaving active nodes unreachable from x, by rechecking // that the status of predecessor and successor are // unchanged and ensuring that x is not reachable from // tail/head, before setting x's prev/next links to their // logical approximate replacements, self/TERMINATOR. Node activePred, activeSucc; boolean isFirst, isLast; int hops = 1; // Find active predecessor for (Node p = prev; ; ++hops) { if (p.item != null) { activePred = p; isFirst = false; break; } Node q = p.prev; if (q == null) { if (p.next == p) return; activePred = p; isFirst = true; break; } else if (p == q) return; else p = q; } // Find active successor for (Node p = next; ; ++hops) { if (p.item != null) { activeSucc = p; isLast = false; break; } Node q = p.next; if (q == null) { if (p.prev == p) return; activeSucc = p; isLast = true; break; } else if (p == q) return; else p = q; } // TODO: better HOP heuristics if (hops < HOPS // always squeeze out interior deleted nodes && (isFirst || isLast)) return; // Squeeze out deleted nodes between activePred and // activeSucc, including x. skipDeletedSuccessors(activePred); skipDeletedPredecessors(activeSucc); // Try to gc-unlink, if possible if ((isFirst || isLast) && // Recheck expected state of predecessor and successor (activePred.next == activeSucc) && (activeSucc.prev == activePred) && (isFirst ? activePred.prev == null : activePred.item != null) && (isLast ? activeSucc.next == null : activeSucc.item != null)) { updateHead(); // Ensure x is not reachable from head updateTail(); // Ensure x is not reachable from tail // Finally, actually gc-unlink x.lazySetPrev(isFirst ? prevTerminator() : x); x.lazySetNext(isLast ? nextTerminator() : x); } } } /** * Unlinks non-null first node. */ private void unlinkFirst(Node first, Node next) { for (Node o = null, p = next, q;;) { if (p.item != null || (q = p.next) == null) { if (o != null && p.prev != p && first.casNext(next, p)) { skipDeletedPredecessors(p); if (first.prev == null && (p.next == null || p.item != null) && p.prev == first) { updateHead(); // Ensure o is not reachable from head updateTail(); // Ensure o is not reachable from tail // Finally, actually gc-unlink o.lazySetNext(o); o.lazySetPrev(prevTerminator()); } } return; } else if (p == q) return; else { o = p; p = q; } } } /** * Unlinks non-null last node. */ private void unlinkLast(Node last, Node prev) { for (Node o = null, p = prev, q;;) { if (p.item != null || (q = p.prev) == null) { if (o != null && p.next != p && last.casPrev(prev, p)) { skipDeletedSuccessors(p); if (last.next == null && (p.prev == null || p.item != null) && p.next == last) { updateHead(); // Ensure o is not reachable from head updateTail(); // Ensure o is not reachable from tail // Finally, actually gc-unlink o.lazySetPrev(o); o.lazySetNext(nextTerminator()); } } return; } else if (p == q) return; else { o = p; p = q; } } } /** * Guarantees that any node which was unlinked before a call to * this method will be unreachable from head after it returns. * Does not guarantee to eliminate slack, only that head will * point to a node that was active while this method was running. */ private void updateHead() { // Either head already points to an active node, or we keep // trying to cas it to the first node until it does. Node h, p, q; restartFromHead: while ((h = head).item == null && (p = h.prev) != null) { for (;;) { if ((q = p.prev) == null || (q = (p = q).prev) == null) { // It is possible that p is PREV_TERMINATOR, // but if so, the CAS is guaranteed to fail. if (casHead(h, p)) return; else continue restartFromHead; } else if (h != head) continue restartFromHead; else p = q; } } } /** * Guarantees that any node which was unlinked before a call to * this method will be unreachable from tail after it returns. * Does not guarantee to eliminate slack, only that tail will * point to a node that was active while this method was running. */ private void updateTail() { // Either tail already points to an active node, or we keep // trying to cas it to the last node until it does. Node t, p, q; restartFromTail: while ((t = tail).item == null && (p = t.next) != null) { for (;;) { if ((q = p.next) == null || (q = (p = q).next) == null) { // It is possible that p is NEXT_TERMINATOR, // but if so, the CAS is guaranteed to fail. if (casTail(t, p)) return; else continue restartFromTail; } else if (t != tail) continue restartFromTail; else p = q; } } } private void skipDeletedPredecessors(Node x) { whileActive: do { Node prev = x.prev; Node p = prev; findActive: for (;;) { if (p.item != null) break findActive; Node q = p.prev; if (q == null) { if (p.next == p) continue whileActive; break findActive; } else if (p == q) continue whileActive; else p = q; } // found active CAS target if (prev == p || x.casPrev(prev, p)) return; } while (x.item != null || x.next == null); } private void skipDeletedSuccessors(Node x) { whileActive: do { Node next = x.next; Node p = next; findActive: for (;;) { if (p.item != null) break findActive; Node q = p.next; if (q == null) { if (p.prev == p) continue whileActive; break findActive; } else if (p == q) continue whileActive; else p = q; } // found active CAS target if (next == p || x.casNext(next, p)) return; } while (x.item != null || x.prev == null); } /** * Returns the successor of p, or the first node if p.next has been * linked to self, which will only be true if traversing with a * stale pointer that is now off the list. */ final Node succ(Node p) { // TODO: should we skip deleted nodes here? Node q = p.next; return (p == q) ? first() : q; } /** * Returns the predecessor of p, or the last node if p.prev has been * linked to self, which will only be true if traversing with a * stale pointer that is now off the list. */ final Node pred(Node p) { Node q = p.prev; return (p == q) ? last() : q; } /** * Returns the first node, the unique node p for which: * p.prev == null && p.next != p * The returned node may or may not be logically deleted. * Guarantees that head is set to the returned node. */ Node first() { restartFromHead: for (;;) for (Node h = head, p = h, q;;) { if ((q = p.prev) != null && (q = (p = q).prev) != null) // Check for head updates every other hop. // If p == q, we are sure to follow head instead. p = (h != (h = head)) ? h : q; else if (p == h // It is possible that p is PREV_TERMINATOR, // but if so, the CAS is guaranteed to fail. || casHead(h, p)) return p; else continue restartFromHead; } } /** * Returns the last node, the unique node p for which: * p.next == null && p.prev != p * The returned node may or may not be logically deleted. * Guarantees that tail is set to the returned node. */ Node last() { restartFromTail: for (;;) for (Node t = tail, p = t, q;;) { if ((q = p.next) != null && (q = (p = q).next) != null) // Check for tail updates every other hop. // If p == q, we are sure to follow tail instead. p = (t != (t = tail)) ? t : q; else if (p == t // It is possible that p is NEXT_TERMINATOR, // but if so, the CAS is guaranteed to fail. || casTail(t, p)) return p; else continue restartFromTail; } } // Minor convenience utilities /** * Throws NullPointerException if argument is null. * * @param v the element */ private static void checkNotNull(Object v) { if (v == null) throw new NullPointerException(); } /** * Returns element unless it is null, in which case throws * NoSuchElementException. * * @param v the element * @return the element */ private E screenNullResult(E v) { if (v == null) throw new NoSuchElementException(); return v; } /** * Creates an array list and fills it with elements of this list. * Used by toArray. * * @return the arrayList */ private ArrayList toArrayList() { ArrayList list = new ArrayList<>(); for (Node p = first(); p != null; p = succ(p)) { E item = p.item; if (item != null) list.add(item); } return list; } /** * Constructs an empty deque. */ public PortableConcurrentDirectDeque() { head = tail = new Node<>(null); } /** * Constructs a deque initially containing the elements of * the given collection, added in traversal order of the * collection's iterator. * * @param c the collection of elements to initially contain * @throws NullPointerException if the specified collection or any * of its elements are null */ public PortableConcurrentDirectDeque(Collection c) { // Copy c into a private chain of Nodes Node h = null, t = null; for (E e : c) { checkNotNull(e); Node newNode = new Node<>(e); if (h == null) h = t = newNode; else { t.lazySetNext(newNode); newNode.lazySetPrev(t); t = newNode; } } initHeadTail(h, t); } /** * Initializes head and tail, ensuring invariants hold. */ private void initHeadTail(Node h, Node t) { if (h == t) { if (h == null) h = t = new Node<>(null); else { // Avoid edge case of a single Node with non-null item. Node newNode = new Node<>(null); t.lazySetNext(newNode); newNode.lazySetPrev(t); t = newNode; } } head = h; tail = t; } /** * Inserts the specified element at the front of this deque. * As the deque is unbounded, this method will never throw * {@link IllegalStateException}. * * @throws NullPointerException if the specified element is null */ public void addFirst(E e) { linkFirst(e); } /** * Inserts the specified element at the end of this deque. * As the deque is unbounded, this method will never throw * {@link IllegalStateException}. * *

This method is equivalent to {@link #add}. * * @throws NullPointerException if the specified element is null */ public void addLast(E e) { linkLast(e); } /** * Inserts the specified element at the front of this deque. * As the deque is unbounded, this method will never return {@code false}. * * @return {@code true} (as specified by {@link java.util.Deque#offerFirst}) * @throws NullPointerException if the specified element is null */ public boolean offerFirst(E e) { linkFirst(e); return true; } public Object offerFirstAndReturnToken(E e) { return linkFirst(e); } public Object offerLastAndReturnToken(E e) { return linkLast(e); } public void removeToken(Object token) { if (!(token instanceof Node)) { throw new IllegalArgumentException(); } Node node = (Node) (token); while (! node.casItem(node.item, null)) {} unlink(node); } /** * Inserts the specified element at the end of this deque. * As the deque is unbounded, this method will never return {@code false}. * *

This method is equivalent to {@link #add}. * * @return {@code true} (as specified by {@link java.util.Deque#offerLast}) * @throws NullPointerException if the specified element is null */ public boolean offerLast(E e) { linkLast(e); return true; } public E peekFirst() { for (Node p = first(); p != null; p = succ(p)) { E item = p.item; if (item != null) return item; } return null; } public E peekLast() { for (Node p = last(); p != null; p = pred(p)) { E item = p.item; if (item != null) return item; } return null; } /** * @throws java.util.NoSuchElementException {@inheritDoc} */ public E getFirst() { return screenNullResult(peekFirst()); } /** * @throws java.util.NoSuchElementException {@inheritDoc} */ public E getLast() { return screenNullResult(peekLast()); } public E pollFirst() { for (Node p = first(); p != null; p = succ(p)) { E item = p.item; if (item != null && p.casItem(item, null)) { unlink(p); return item; } } return null; } public E pollLast() { for (Node p = last(); p != null; p = pred(p)) { E item = p.item; if (item != null && p.casItem(item, null)) { unlink(p); return item; } } return null; } /** * @throws java.util.NoSuchElementException {@inheritDoc} */ public E removeFirst() { return screenNullResult(pollFirst()); } /** * @throws java.util.NoSuchElementException {@inheritDoc} */ public E removeLast() { return screenNullResult(pollLast()); } // *** Queue and stack methods *** /** * Inserts the specified element at the tail of this deque. * As the deque is unbounded, this method will never return {@code false}. * * @return {@code true} (as specified by {@link java.util.Queue#offer}) * @throws NullPointerException if the specified element is null */ public boolean offer(E e) { return offerLast(e); } /** * Inserts the specified element at the tail of this deque. * As the deque is unbounded, this method will never throw * {@link IllegalStateException} or return {@code false}. * * @return {@code true} (as specified by {@link java.util.Collection#add}) * @throws NullPointerException if the specified element is null */ public boolean add(E e) { return offerLast(e); } public E poll() { return pollFirst(); } public E remove() { return removeFirst(); } public E peek() { return peekFirst(); } public E element() { return getFirst(); } public void push(E e) { addFirst(e); } public E pop() { return removeFirst(); } /** * Removes the first element {@code e} such that * {@code o.equals(e)}, if such an element exists in this deque. * If the deque does not contain the element, it is unchanged. * * @param o element to be removed from this deque, if present * @return {@code true} if the deque contained the specified element * @throws NullPointerException if the specified element is null */ public boolean removeFirstOccurrence(Object o) { checkNotNull(o); for (Node p = first(); p != null; p = succ(p)) { E item = p.item; if (item != null && o.equals(item) && p.casItem(item, null)) { unlink(p); return true; } } return false; } /** * Removes the last element {@code e} such that * {@code o.equals(e)}, if such an element exists in this deque. * If the deque does not contain the element, it is unchanged. * * @param o element to be removed from this deque, if present * @return {@code true} if the deque contained the specified element * @throws NullPointerException if the specified element is null */ public boolean removeLastOccurrence(Object o) { checkNotNull(o); for (Node p = last(); p != null; p = pred(p)) { E item = p.item; if (item != null && o.equals(item) && p.casItem(item, null)) { unlink(p); return true; } } return false; } /** * Returns {@code true} if this deque contains at least one * element {@code e} such that {@code o.equals(e)}. * * @param o element whose presence in this deque is to be tested * @return {@code true} if this deque contains the specified element */ public boolean contains(Object o) { if (o == null) return false; for (Node p = first(); p != null; p = succ(p)) { E item = p.item; if (item != null && o.equals(item)) return true; } return false; } /** * Returns {@code true} if this collection contains no elements. * * @return {@code true} if this collection contains no elements */ public boolean isEmpty() { return peekFirst() == null; } /** * Returns the number of elements in this deque. If this deque * contains more than {@code Integer.MAX_VALUE} elements, it * returns {@code Integer.MAX_VALUE}. * *

Beware that, unlike in most collections, this method is * NOT a constant-time operation. Because of the * asynchronous nature of these deques, determining the current * number of elements requires traversing them all to count them. * Additionally, it is possible for the size to change during * execution of this method, in which case the returned result * will be inaccurate. Thus, this method is typically not very * useful in concurrent applications. * * @return the number of elements in this deque */ public int size() { int count = 0; for (Node p = first(); p != null; p = succ(p)) if (p.item != null) // Collection.size() spec says to max out if (++count == Integer.MAX_VALUE) break; return count; } /** * Removes the first element {@code e} such that * {@code o.equals(e)}, if such an element exists in this deque. * If the deque does not contain the element, it is unchanged. * * @param o element to be removed from this deque, if present * @return {@code true} if the deque contained the specified element * @throws NullPointerException if the specified element is null */ public boolean remove(Object o) { return removeFirstOccurrence(o); } /** * Appends all of the elements in the specified collection to the end of * this deque, in the order that they are returned by the specified * collection's iterator. Attempts to {@code addAll} of a deque to * itself result in {@code IllegalArgumentException}. * * @param c the elements to be inserted into this deque * @return {@code true} if this deque changed as a result of the call * @throws NullPointerException if the specified collection or any * of its elements are null * @throws IllegalArgumentException if the collection is this deque */ public boolean addAll(Collection c) { if (c == this) // As historically specified in AbstractQueue#addAll throw new IllegalArgumentException(); // Copy c into a private chain of Nodes Node beginningOfTheEnd = null, last = null; for (E e : c) { checkNotNull(e); Node newNode = new Node<>(e); if (beginningOfTheEnd == null) beginningOfTheEnd = last = newNode; else { last.lazySetNext(newNode); newNode.lazySetPrev(last); last = newNode; } } if (beginningOfTheEnd == null) return false; // Atomically append the chain at the tail of this collection restartFromTail: for (;;) for (Node t = tail, p = t, q;;) { if ((q = p.next) != null && (q = (p = q).next) != null) // Check for tail updates every other hop. // If p == q, we are sure to follow tail instead. p = (t != (t = tail)) ? t : q; else if (p.prev == p) // NEXT_TERMINATOR continue restartFromTail; else { // p is last node beginningOfTheEnd.lazySetPrev(p); // CAS piggyback if (p.casNext(null, beginningOfTheEnd)) { // Successful CAS is the linearization point // for all elements to be added to this deque. if (!casTail(t, last)) { // Try a little harder to update tail, // since we may be adding many elements. t = tail; if (last.next == null) casTail(t, last); } return true; } // Lost CAS race to another thread; re-read next } } } /** * Removes all of the elements from this deque. */ public void clear() { while (pollFirst() != null) { } } /** * Returns an array containing all of the elements in this deque, in * proper sequence (from first to last element). * *

The returned array will be "safe" in that no references to it are * maintained by this deque. (In other words, this method must allocate * a new array). The caller is thus free to modify the returned array. * *

This method acts as bridge between array-based and collection-based * APIs. * * @return an array containing all of the elements in this deque */ public Object[] toArray() { return toArrayList().toArray(); } /** * Returns an array containing all of the elements in this deque, * in proper sequence (from first to last element); the runtime * type of the returned array is that of the specified array. If * the deque fits in the specified array, it is returned therein. * Otherwise, a new array is allocated with the runtime type of * the specified array and the size of this deque. * *

If this deque fits in the specified array with room to spare * (i.e., the array has more elements than this deque), the element in * the array immediately following the end of the deque is set to * {@code null}. * *

Like the {@link #toArray()} method, this method acts as * bridge between array-based and collection-based APIs. Further, * this method allows precise control over the runtime type of the * output array, and may, under certain circumstances, be used to * save allocation costs. * *

Suppose {@code x} is a deque known to contain only strings. * The following code can be used to dump the deque into a newly * allocated array of {@code String}: * *

 {@code String[] y = x.toArray(new String[0]);}
* * Note that {@code toArray(new Object[0])} is identical in function to * {@code toArray()}. * * @param a the array into which the elements of the deque are to * be stored, if it is big enough; otherwise, a new array of the * same runtime type is allocated for this purpose * @return an array containing all of the elements in this deque * @throws ArrayStoreException if the runtime type of the specified array * is not a supertype of the runtime type of every element in * this deque * @throws NullPointerException if the specified array is null */ public T[] toArray(T[] a) { return toArrayList().toArray(a); } /** * Returns an iterator over the elements in this deque in proper sequence. * The elements will be returned in order from first (head) to last (tail). * *

The returned iterator is a "weakly consistent" iterator that * will never throw {@link java.util.ConcurrentModificationException * ConcurrentModificationException}, and guarantees to traverse * elements as they existed upon construction of the iterator, and * may (but is not guaranteed to) reflect any modifications * subsequent to construction. * * @return an iterator over the elements in this deque in proper sequence */ public Iterator iterator() { return new Itr(); } /** * Returns an iterator over the elements in this deque in reverse * sequential order. The elements will be returned in order from * last (tail) to first (head). * *

The returned iterator is a "weakly consistent" iterator that * will never throw {@link java.util.ConcurrentModificationException * ConcurrentModificationException}, and guarantees to traverse * elements as they existed upon construction of the iterator, and * may (but is not guaranteed to) reflect any modifications * subsequent to construction. * * @return an iterator over the elements in this deque in reverse order */ public Iterator descendingIterator() { return new DescendingItr(); } private abstract class AbstractItr implements Iterator { /** * Next node to return item for. */ private Node nextNode; /** * nextItem holds on to item fields because once we claim * that an element exists in hasNext(), we must return it in * the following next() call even if it was in the process of * being removed when hasNext() was called. */ private E nextItem; /** * Node returned by most recent call to next. Needed by remove. * Reset to null if this element is deleted by a call to remove. */ private Node lastRet; abstract Node startNode(); abstract Node nextNode(Node p); AbstractItr() { advance(); } /** * Sets nextNode and nextItem to next valid node, or to null * if no such. */ private void advance() { lastRet = nextNode; Node p = (nextNode == null) ? startNode() : nextNode(nextNode); for (;; p = nextNode(p)) { if (p == null) { // p might be active end or TERMINATOR node; both are OK nextNode = null; nextItem = null; break; } E item = p.item; if (item != null) { nextNode = p; nextItem = item; break; } } } public boolean hasNext() { return nextItem != null; } public E next() { E item = nextItem; if (item == null) throw new NoSuchElementException(); advance(); return item; } public void remove() { Node l = lastRet; if (l == null) throw new IllegalStateException(); l.item = null; unlink(l); lastRet = null; } } /** Forward iterator */ private class Itr extends AbstractItr { Node startNode() { return first(); } Node nextNode(Node p) { return succ(p); } } /** * Descending iterator */ private class DescendingItr extends AbstractItr { Node startNode() { return last(); } Node nextNode(Node p) { return pred(p); } } /** * Saves this deque to a stream (that is, serializes it). * * @serialData All of the elements (each an {@code E}) in * the proper order, followed by a null */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { // Write out any hidden stuff s.defaultWriteObject(); // Write out all elements in the proper order. for (Node p = first(); p != null; p = succ(p)) { E item = p.item; if (item != null) s.writeObject(item); } // Use trailing null as sentinel s.writeObject(null); } /** * Reconstitutes this deque from a stream (that is, deserializes it). */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); // Read in elements until trailing null sentinel found Node h = null, t = null; Object item; while ((item = s.readObject()) != null) { @SuppressWarnings("unchecked") Node newNode = new Node<>((E) item); if (h == null) h = t = newNode; else { t.lazySetNext(newNode); newNode.lazySetPrev(t); t = newNode; } } initHeadTail(h, t); } private boolean casHead(Node cmp, Node val) { return headUpdater.compareAndSet(this, cmp, val); } private boolean casTail(Node cmp, Node val) { return tailUpdater.compareAndSet(this, cmp, val); } // Unsafe mechanics static { PREV_TERMINATOR = new Node<>(); PREV_TERMINATOR.next = PREV_TERMINATOR; NEXT_TERMINATOR = new Node<>(); NEXT_TERMINATOR.prev = NEXT_TERMINATOR; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/Protocols.java000066400000000000000000000032571420065311100261470ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; /** * Protocol version strings. * * @author David M. Lloyd */ public final class Protocols { private Protocols() { } /** * HTTP 0.9. */ public static final String HTTP_0_9_STRING = "HTTP/0.9"; /** * HTTP 1.0. */ public static final String HTTP_1_0_STRING = "HTTP/1.0"; /** * HTTP 1.1. */ public static final String HTTP_1_1_STRING = "HTTP/1.1"; /** * HTTP 1.1. */ public static final String HTTP_2_0_STRING = "HTTP/2.0"; public static final HttpString HTTP_0_9 = new HttpString(HTTP_0_9_STRING); /** * HTTP 1.0. */ public static final HttpString HTTP_1_0 = new HttpString(HTTP_1_0_STRING); /** * HTTP 1.1. */ public static final HttpString HTTP_1_1 = new HttpString(HTTP_1_1_STRING); /** * HTTP 2.0. */ public static final HttpString HTTP_2_0 = new HttpString(HTTP_2_0_STRING); } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/QValueParser.java000066400000000000000000000204151420065311100265300ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Utility class for parsing headers that accept q values * * @author Stuart Douglas */ public class QValueParser { private QValueParser() { } /** * Parses a set of headers that take q values to determine the most preferred one. * * It returns the result in the form of a sorted list of list, with every element in * the list having the same q value. This means the highest priority items are at the * front of the list. The container should use its own internal preferred ordering * to determinately pick the correct item to use * * @param headers The headers * @return The q value results */ public static List> parse(List headers) { final List found = new ArrayList<>(); QValueResult current = null; for (final String header : headers) { final int l = header.length(); //we do not use a string builder //we just keep track of where the current string starts and call substring() int stringStart = 0; for (int i = 0; i < l; ++i) { char c = header.charAt(i); switch (c) { case ',': { if (current != null && (i - stringStart > 2 && header.charAt(stringStart) == 'q' && header.charAt(stringStart + 1) == '=')) { //if this is a valid qvalue current.qvalue = header.substring(stringStart + 2, i); current = null; } else if (stringStart != i) { current = handleNewEncoding(found, header, stringStart, i); } stringStart = i + 1; break; } case ';': { if (stringStart != i) { current = handleNewEncoding(found, header, stringStart, i); stringStart = i + 1; } break; } case ' ': { if (stringStart != i) { if (current != null && (i - stringStart > 2 && header.charAt(stringStart) == 'q' && header.charAt(stringStart + 1) == '=')) { //if this is a valid qvalue current.qvalue = header.substring(stringStart + 2, i); } else { current = handleNewEncoding(found, header, stringStart, i); } } stringStart = i + 1; } } } if (stringStart != l) { if (current != null && (l - stringStart > 2 && header.charAt(stringStart) == 'q' && header.charAt(stringStart + 1) == '=')) { //if this is a valid qvalue current.qvalue = header.substring(stringStart + 2, l); } else { current = handleNewEncoding(found, header, stringStart, l); } } } Collections.sort(found, Collections.reverseOrder()); String currentQValue = null; List> values = new ArrayList<>(); List currentSet = null; for(QValueResult val : found) { if(!val.qvalue.equals(currentQValue)) { currentQValue = val.qvalue; currentSet = new ArrayList<>(); values.add(currentSet); } currentSet.add(val); } return values; } private static QValueResult handleNewEncoding(final List found, final String header, final int stringStart, final int i) { final QValueResult current = new QValueResult(); current.value = header.substring(stringStart, i); found.add(current); return current; } public static class QValueResult implements Comparable { /** * The string value of the result */ private String value; /** * we keep the qvalue as a string to avoid parsing the double. *

* This should give both performance and also possible security improvements */ private String qvalue = "1"; public String getValue() { return value; } public String getQvalue() { return qvalue; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof QValueResult)) return false; QValueResult that = (QValueResult) o; if (getValue() != null ? !getValue().equals(that.getValue()) : that.getValue() != null) return false; return getQvalue() != null ? getQvalue().equals(that.getQvalue()) : that.getQvalue() == null; } @Override public int hashCode() { int result = getValue() != null ? getValue().hashCode() : 0; result = 31 * result + (getQvalue() != null ? getQvalue().hashCode() : 0); return result; } @Override public int compareTo(final QValueResult other) { //we compare the strings as if they were decimal values. //we know they can only be final String t = qvalue; final String o = other.qvalue; if (t == null && o == null) { //neither of them has a q value //we compare them via the server specified default precedence //note that encoding is never null here, a * without a q value is meaningless //and will be discarded before this return 0; } if (o == null) { return 1; } else if (t == null) { return -1; } final int tl = t.length(); final int ol = o.length(); //we only compare the first 5 characters as per spec for (int i = 0; i < 5; ++i) { if (tl == i || ol == i) { return ol - tl; //longer one is higher } if (i == 1) continue; // this is just the decimal point final int tc = t.charAt(i); final int oc = o.charAt(i); int res = tc - oc; if (res != 0) { return res; } } return 0; } public boolean isQValueZero() { //we ignore * without a qvalue if (qvalue != null) { int length = Math.min(5, qvalue.length()); //we need to find out if this is prohibiting identity //encoding (q=0). Otherwise we just treat it as the identity encoding boolean zero = true; for (int j = 0; j < length; ++j) { if (j == 1) continue;//decimal point if (qvalue.charAt(j) != '0') { zero = false; break; } } return zero; } return false; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/QueryParameterUtils.java000066400000000000000000000146651420065311100301570ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.ArrayDeque; import java.util.Deque; import java.util.LinkedHashMap; import java.util.Map; import org.xnio.OptionMap; import io.undertow.UndertowOptions; import io.undertow.server.HttpServerExchange; /** * Methods for dealing with the query string * * @author Stuart Douglas */ public class QueryParameterUtils { private QueryParameterUtils() { } public static String buildQueryString(final Map> params) { StringBuilder sb = new StringBuilder(); boolean first = true; for (Map.Entry> entry : params.entrySet()) { if (entry.getValue().isEmpty()) { if (first) { first = false; } else { sb.append('&'); } sb.append(entry.getKey()); sb.append('='); } else { for (String val : entry.getValue()) { if (first) { first = false; } else { sb.append('&'); } sb.append(entry.getKey()); sb.append('='); sb.append(val); } } } return sb.toString(); } /** * Parses a query string into a map * @param newQueryString The query string * @return The map of key value parameters */ @Deprecated public static Map> parseQueryString(final String newQueryString) { return parseQueryString(newQueryString, null); } /** * Parses a query string into a map * @param newQueryString The query string * @return The map of key value parameters */ public static Map> parseQueryString(final String newQueryString, final String encoding) { Map> newQueryParameters = new LinkedHashMap<>(); int startPos = 0; int equalPos = -1; boolean needsDecode = false; for(int i = 0; i < newQueryString.length(); ++i) { char c = newQueryString.charAt(i); if(c == '=' && equalPos == -1) { equalPos = i; } else if(c == '&') { handleQueryParameter(newQueryString, newQueryParameters, startPos, equalPos, i, encoding, needsDecode); needsDecode = false; startPos = i + 1; equalPos = -1; } else if((c == '%' || c == '+') && encoding != null) { needsDecode = true; } } if(startPos != newQueryString.length()) { handleQueryParameter(newQueryString, newQueryParameters, startPos, equalPos, newQueryString.length(), encoding, needsDecode); } return newQueryParameters; } private static void handleQueryParameter(String newQueryString, Map> newQueryParameters, int startPos, int equalPos, int i, final String encoding, boolean needsDecode) { String key; String value = ""; if(equalPos == -1) { key = decodeParam(newQueryString, startPos, i, encoding, needsDecode); } else { key = decodeParam(newQueryString, startPos, equalPos, encoding, needsDecode); value = decodeParam(newQueryString, equalPos + 1, i, encoding, needsDecode); } Deque queue = newQueryParameters.get(key); if (queue == null) { newQueryParameters.put(key, queue = new ArrayDeque<>(1)); } if(value != null) { queue.add(value); } } private static String decodeParam(String newQueryString, int startPos, int equalPos, String encoding, boolean needsDecode) { String key; if (needsDecode) { try { key = URLDecoder.decode(newQueryString.substring(startPos, equalPos), encoding); } catch (UnsupportedEncodingException e) { key = newQueryString.substring(startPos, equalPos); } } else { key = newQueryString.substring(startPos, equalPos); } return key; } @Deprecated public static Map> mergeQueryParametersWithNewQueryString(final Map> queryParameters, final String newQueryString) { return mergeQueryParametersWithNewQueryString(queryParameters, newQueryString, StandardCharsets.UTF_8.name()); } public static Map> mergeQueryParametersWithNewQueryString(final Map> queryParameters, final String newQueryString, final String encoding) { Map> newQueryParameters = parseQueryString(newQueryString, encoding); //according to the spec the new query parameters have to 'take precedence' for (Map.Entry> entry : queryParameters.entrySet()) { if (!newQueryParameters.containsKey(entry.getKey())) { newQueryParameters.put(entry.getKey(), new ArrayDeque<>(entry.getValue())); } else { newQueryParameters.get(entry.getKey()).addAll(entry.getValue()); } } return newQueryParameters; } public static String getQueryParamEncoding(HttpServerExchange exchange) { String encoding = null; OptionMap undertowOptions = exchange.getConnection().getUndertowOptions(); if(undertowOptions.get(UndertowOptions.DECODE_URL, true)) { encoding = undertowOptions.get(UndertowOptions.URL_CHARSET, StandardCharsets.UTF_8.name()); } return encoding; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/RedirectBuilder.java000066400000000000000000000133011420065311100272220ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.server.HttpServerExchange; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Deque; import java.util.Map; /** * Utility class for building redirects. * * @author Stuart Douglas */ public class RedirectBuilder { public static final String UTF_8 = StandardCharsets.UTF_8.name(); /** * Redirects to a new relative path. All other data from the exchange is preserved. * * @param exchange The HTTP server exchange * @param newRelativePath The new relative path * @return */ public static String redirect(final HttpServerExchange exchange, final String newRelativePath) { return redirect(exchange, newRelativePath, true); } /** * Redirects to a new relative path. All other data from the exchange is preserved. * * @param exchange The HTTP server exchange * @param newRelativePath The new relative path * @param includeParameters If query and path parameters from the exchange should be included * @return */ public static String redirect(final HttpServerExchange exchange, final String newRelativePath, final boolean includeParameters) { try { StringBuilder uri = new StringBuilder(exchange.getRequestScheme()); uri.append("://"); uri.append(exchange.getHostAndPort()); uri.append(encodeUrlPart(exchange.getResolvedPath())); if (exchange.getResolvedPath().endsWith("/")) { if (newRelativePath.startsWith("/")) { uri.append(encodeUrlPart(newRelativePath.substring(1))); } else { uri.append(encodeUrlPart(newRelativePath)); } } else { if (!newRelativePath.startsWith("/")) { uri.append('/'); } uri.append(encodeUrlPart(newRelativePath)); } if (includeParameters) { if (!exchange.getPathParameters().isEmpty()) { boolean first = true; uri.append(';'); for (Map.Entry> param : exchange.getPathParameters().entrySet()) { for (String value : param.getValue()) { if (first) { first = false; } else { uri.append('&'); } uri.append(URLEncoder.encode(param.getKey(), UTF_8)); uri.append('='); uri.append(URLEncoder.encode(value, UTF_8)); } } } if (!exchange.getQueryString().isEmpty()) { uri.append('?'); uri.append(exchange.getQueryString()); } } return uri.toString(); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } /** * perform URL encoding *

* TODO: this whole thing is kinda crappy. * * @return */ private static String encodeUrlPart(final String part) throws UnsupportedEncodingException { //we need to go through and check part by part that a section does not need encoding int pos = 0; for (int i = 0; i < part.length(); ++i) { char c = part.charAt(i); if(c == '?') { break; } else if (c == '/') { if (pos != i) { String original = part.substring(pos, i); String encoded = URLEncoder.encode(original, UTF_8); if (!encoded.equals(original)) { return realEncode(part, pos); } } pos = i + 1; } else if (c == ' ') { return realEncode(part, pos); } } return part; } private static String realEncode(String part, int startPos) throws UnsupportedEncodingException { StringBuilder sb = new StringBuilder(); sb.append(part.substring(0, startPos)); int pos = startPos; for (int i = startPos; i < part.length(); ++i) { char c = part.charAt(i); if(c == '?') { break; } else if (c == '/') { if (pos != i) { String original = part.substring(pos, i); String encoded = URLEncoder.encode(original, UTF_8); sb.append(encoded); sb.append('/'); pos = i + 1; } } } String original = part.substring(pos); String encoded = URLEncoder.encode(original, UTF_8); sb.append(encoded); return sb.toString(); } private RedirectBuilder() { } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/ReferenceCountedPooled.java000066400000000000000000000146501420065311100305450ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.UndertowMessages; import io.undertow.connector.PooledByteBuffer; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; /** * A reference counted pooled implementation, that basically consists of a main buffer, that can be sliced off into smaller buffers, * and the underlying buffer will not be freed until all the slices and the main buffer itself have also been freed. * * This also supports the notion of un-freeing the main buffer. Basically this allows the buffer be re-used, so if only a small slice of the * buffer was used for read operations the main buffer can potentially be re-used. This prevents buffer exhaustion attacks where content * is sent in many small packets, and you end up allocating a large number of buffers to hold a small amount of data. * * @author Stuart Douglas */ public class ReferenceCountedPooled implements PooledByteBuffer { private final PooledByteBuffer underlying; @SuppressWarnings("unused") private volatile int referenceCount; boolean mainFreed = false; private ByteBuffer slice = null; private final FreeNotifier freeNotifier; private static final AtomicIntegerFieldUpdater referenceCountUpdater = AtomicIntegerFieldUpdater.newUpdater(ReferenceCountedPooled.class, "referenceCount"); public ReferenceCountedPooled(PooledByteBuffer underlying, int referenceCount) { this(underlying, referenceCount, null); } public ReferenceCountedPooled(PooledByteBuffer underlying, int referenceCount, FreeNotifier freeNotifier) { this.underlying = underlying; this.referenceCount = referenceCount; this.freeNotifier = freeNotifier; } @Override public void close() { if(mainFreed) { return; } mainFreed = true; freeInternal(); } @Override public boolean isOpen() { return !mainFreed; } public boolean isFreed() { return mainFreed; } public boolean tryUnfree() { int refs; do { refs = referenceCountUpdater.get(this); if(refs <= 0) { return false; } } while (!referenceCountUpdater.compareAndSet(this, refs, refs + 1)); ByteBuffer resource = slice != null ? slice : underlying.getBuffer(); resource.position(resource.limit()); resource.limit(resource.capacity()); slice = resource.slice(); mainFreed = false; return true; } private void freeInternal() { if(referenceCountUpdater.decrementAndGet(this) == 0) { underlying.close(); if(freeNotifier != null) { freeNotifier.freed(); } } } @Override public ByteBuffer getBuffer() throws IllegalStateException { if(mainFreed) { throw UndertowMessages.MESSAGES.bufferAlreadyFreed(); } if(slice != null) { return slice; } return underlying.getBuffer(); } public PooledByteBuffer createView(int viewSize) { ByteBuffer newView = getBuffer().duplicate(); newView.limit(newView.position() + viewSize); final ByteBuffer newValue = newView.slice(); ByteBuffer newUnderlying = getBuffer().duplicate(); newUnderlying.position(newUnderlying.position() + viewSize); int oldRemaining = newUnderlying.remaining(); newUnderlying.limit(newUnderlying.capacity()); newUnderlying = newUnderlying.slice(); newUnderlying.limit(newUnderlying.position() + oldRemaining); slice = newUnderlying; increaseReferenceCount(); return new PooledByteBuffer() { boolean free = false; @Override public void close() { //make sure that a given view can only be freed once if(!free) { free = true; ReferenceCountedPooled.this.freeInternal(); } } @Override public boolean isOpen() { return !free; } @Override public ByteBuffer getBuffer() throws IllegalStateException { if(free) { throw UndertowMessages.MESSAGES.bufferAlreadyFreed(); } return newValue; } @Override public String toString() { return "ReferenceCountedPooled$view{" + "buffer=" + newValue + "free=" + free + "underlying=" + underlying + ", referenceCount=" + referenceCount + ", mainFreed=" + mainFreed + ", slice=" + slice + '}'; } }; } public PooledByteBuffer createView() { return createView(getBuffer().remaining()); } public void increaseReferenceCount() { int val; do { val = referenceCountUpdater.get(this); if(val == 0) { //should never happen, as this should only be called from //code that already has a reference throw UndertowMessages.MESSAGES.objectWasFreed(); } } while (!referenceCountUpdater.compareAndSet(this, val, val + 1)); } public interface FreeNotifier { void freed(); } @Override public String toString() { return "ReferenceCountedPooled{" + "underlying=" + underlying + ", referenceCount=" + referenceCount + ", mainFreed=" + mainFreed + ", slice=" + slice + '}'; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/Rfc6265CookieSupport.java000066400000000000000000000064241420065311100277460ustar00rootroot00000000000000/* * 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. */ package io.undertow.util; import io.undertow.UndertowMessages; import java.util.BitSet; /** * Class that contains utility methods for dealing with RFC6265 Cookies. * */ public final class Rfc6265CookieSupport { private static final BitSet domainValid = new BitSet(128); static { for (char c = '0'; c <= '9'; c++) { domainValid.set(c); } for (char c = 'a'; c <= 'z'; c++) { domainValid.set(c); } for (char c = 'A'; c <= 'Z'; c++) { domainValid.set(c); } domainValid.set('.'); domainValid.set('-'); } public static void validateCookieValue(String value) { int start = 0; int end = value.length(); if (end > 1 && value.charAt(0) == '"' && value.charAt(end - 1) == '"') { start = 1; end--; } char[] chars = value.toCharArray(); for (int i = start; i < end; i++) { char c = chars[i]; if (c < 0x21 || c == 0x22 || c == 0x2c || c == 0x3b || c == 0x5c || c == 0x7f) { throw UndertowMessages.MESSAGES.invalidCookieValue(Integer.toString(c)); } } } public static void validateDomain(String domain) { int i = 0; int prev = -1; int cur = -1; char[] chars = domain.toCharArray(); while (i < chars.length) { prev = cur; cur = chars[i]; if (!domainValid.get(cur)) { throw UndertowMessages.MESSAGES.invalidCookieDomain(domain); } // labels must start with a letter or number if ((prev == '.' || prev == -1) && (cur == '.' || cur == '-')) { throw UndertowMessages.MESSAGES.invalidCookieDomain(domain); } // labels must end with a letter or number if (prev == '-' && cur == '.') { throw UndertowMessages.MESSAGES.invalidCookieDomain(domain); } i++; } // domain must end with a label if (cur == '.' || cur == '-') { throw UndertowMessages.MESSAGES.invalidCookieDomain(domain); } } public static void validatePath(String path) { char[] chars = path.toCharArray(); for (int i = 0; i < chars.length; i++) { char ch = chars[i]; if (ch < 0x20 || ch > 0x7E || ch == ';') { throw UndertowMessages.MESSAGES.invalidCookiePath(path); } } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/SameSiteNoneIncompatibleClientChecker.java000066400000000000000000000153001420065311100334600ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * A utility class that can check known user agents which are known to be incompatible with SameSite=None attribute. *

*

    *
  • Versions of Chrome from Chrome 51 to Chrome 66 (inclusive on both ends). * These Chrome versions will reject a cookie with `SameSite=None`. This also * affects older versions of Chromium-derived browsers, as well as Android WebView. * This behavior was correct according to the version of the cookie specification * at that time, but with the addition of the new "None" value to the specification, * this behavior has been updated in Chrome 67 and newer. (Prior to Chrome 51, * the SameSite attribute was ignored entirely and all cookies were treated as if * they were `SameSite=None`.)
  • *
  • Versions of UC Browser on Android prior to version 12.13.2. Older versions * will reject a cookie with `SameSite=None`. This behavior was correct according * to the version of the cookie specification at that time, but with the addition of * the new "None" value to the specification, this behavior has been updated in newer * versions of UC Browser. *
  • Versions of Safari and embedded browsers on MacOS 10.14 and all browsers on iOS 12. * These versions will erroneously treat cookies marked with `SameSite=None` as if they * were marked `SameSite=Strict`. This bug has been fixed on newer versions of iOS and MacOS. *
*

* @see SameSite=None: Known Incompatible Clients. */ public final class SameSiteNoneIncompatibleClientChecker { /** * User Agents Regex Patterns */ private static final Pattern IOS_PATTERN = Pattern.compile("\\(iP.+; CPU .*OS (\\d+)[_\\d]*.*\\) AppleWebKit\\/"); private static final Pattern MACOSX_PATTERN = Pattern.compile("\\(Macintosh;.*Mac OS X (\\d+)_(\\d+)[_\\d]*.*\\) AppleWebKit\\/"); private static final Pattern SAFARI_PATTERN = Pattern.compile("Version\\/.* Safari\\/"); private static final Pattern MAC_EMBEDDED_BROWSER_PATTERN = Pattern.compile("^Mozilla\\/[\\.\\d]+ \\(Macintosh;.*Mac OS X [_\\d]+\\) AppleWebKit\\/[\\.\\d]+ \\(KHTML, like Gecko\\)$"); private static final Pattern CHROMIUM_PATTERN = Pattern.compile("Chrom(e|ium)"); private static final Pattern CHROMIUM_VERSION_PATTERN = Pattern.compile("Chrom[^ \\/]+\\/(\\d+)[\\.\\d]* "); // private static final Pattern UC_BROWSER_PATTERN = Pattern.compile("UCBrowser\\/"); private static final Pattern UC_BROWSER_VERSION_PATTERN = Pattern.compile("UCBrowser\\/(\\d+)\\.(\\d+)\\.(\\d+)[\\.\\d]* "); public static boolean shouldSendSameSiteNone(String useragent) { return !isSameSiteNoneIncompatible(useragent); } // browsers known to be incompatible. public static boolean isSameSiteNoneIncompatible(String useragent) { if (useragent == null || useragent.isEmpty()) { return false; } return hasWebKitSameSiteBug(useragent) || dropsUnrecognizedSameSiteCookies(useragent); } private static boolean hasWebKitSameSiteBug(String useragent) { return isIosVersion(12, useragent) || (isMacosxVersion(10, 14, useragent) && (isSafari(useragent) || isMacEmbeddedBrowser(useragent))); } private static boolean dropsUnrecognizedSameSiteCookies(String useragent) { if (isUcBrowser(useragent)) { return !isUcBrowserVersionAtLeast(12, 13, 2, useragent); } return isChromiumBased(useragent) && isChromiumVersionAtLeast(51, useragent) && !isChromiumVersionAtLeast(67, useragent); } // Regex parsing of User-Agent String. (See note above!) private static boolean isIosVersion(int major, String useragent) { Matcher m = IOS_PATTERN.matcher(useragent); if (m.find()) { // Extract digits from first capturing group. return String.valueOf(major).equals(m.group(1)); } return false; } private static boolean isMacosxVersion(int major, int minor, String useragent) { Matcher m = MACOSX_PATTERN.matcher(useragent); if (m.find()) { // Extract digits from first and second capturing groups. return String.valueOf(major).equals(m.group(1)) && String.valueOf(minor).equals(m.group(2)); } return false; } private static boolean isSafari(String useragent) { return SAFARI_PATTERN.matcher(useragent).find() && !isChromiumBased(useragent); } private static boolean isMacEmbeddedBrowser(String useragent) { return MAC_EMBEDDED_BROWSER_PATTERN.matcher(useragent).find(); } private static boolean isChromiumBased(String useragent) { return CHROMIUM_PATTERN.matcher(useragent).find(); } private static boolean isChromiumVersionAtLeast(int major, String useragent) { Matcher m = CHROMIUM_VERSION_PATTERN.matcher(useragent); if (m.find()) { // Extract digits from first capturing group. int version = Integer.parseInt(m.group(1)); return version >= major; } return false; } static boolean isUcBrowser(String useragent) { return useragent.contains("UCBrowser/"); } private static boolean isUcBrowserVersionAtLeast(int major, int minor, int build, String useragent) { Matcher m = UC_BROWSER_VERSION_PATTERN.matcher(useragent); if (m.find()) { // Extract digits from three capturing groups. int major_version = Integer.parseInt(m.group(1)); int minor_version = Integer.parseInt(m.group(2)); int build_version = Integer.parseInt(m.group(3)); if (major_version != major) { return major_version > major; } if (minor_version != minor) { return minor_version > minor; } return build_version >= build; } return false; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/SameThreadExecutor.java000066400000000000000000000020641420065311100277120ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.util.concurrent.Executor; /** * @author Stuart Douglas */ public class SameThreadExecutor implements Executor { public static final Executor INSTANCE = new SameThreadExecutor(); private SameThreadExecutor() { } @Override public void execute(final Runnable command) { command.run(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/Sessions.java000066400000000000000000000043631420065311100257700ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.UndertowMessages; import io.undertow.server.HttpServerExchange; import io.undertow.server.session.Session; import io.undertow.server.session.SessionConfig; import io.undertow.server.session.SessionManager; /** * Utility class for working with sessions. * * @author Stuart Douglas */ public class Sessions { /** * Gets the active session, returning null if one is not present. * @param exchange The exchange * @return The session */ public static Session getSession(final HttpServerExchange exchange) { return getSession(exchange, false); } /** * Gets the active session, creating a new one if one does not exist * @param exchange The exchange * @return The session */ public static Session getOrCreateSession(final HttpServerExchange exchange) { return getSession(exchange, true); } private static Session getSession(final HttpServerExchange exchange, boolean create) { SessionManager sessionManager = exchange.getAttachment(SessionManager.ATTACHMENT_KEY); SessionConfig sessionConfig = exchange.getAttachment(SessionConfig.ATTACHMENT_KEY); if(sessionManager == null) { throw UndertowMessages.MESSAGES.sessionManagerNotFound(); } Session session = sessionManager.getSession(exchange, sessionConfig); if(session == null && create) { session = sessionManager.createSession(exchange, sessionConfig); } return session; } private Sessions () {} } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/SimpleAttachmentKey.java000066400000000000000000000026331420065311100300730ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; /** * @author David M. Lloyd */ class SimpleAttachmentKey extends AttachmentKey { private final Class valueClass; SimpleAttachmentKey(final Class valueClass) { this.valueClass = valueClass; } public T cast(final Object value) { return valueClass.cast(value); } @Override public String toString() { if (valueClass != null) { StringBuilder sb = new StringBuilder(getClass().getName()); sb.append("<"); sb.append(valueClass.getName()); sb.append(">"); return sb.toString(); } return super.toString(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/SimpleObjectPool.java000066400000000000000000000061161420065311100273720ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.function.Consumer; import java.util.function.Supplier; import io.undertow.UndertowMessages; /** * Simple pool that attempts to maintain a specified number of objects in the pool. If more objects are created new ones * are created on the fly, and then destroyed once the pool is full. * * @author ckozak * @author Stuart Douglas */ public class SimpleObjectPool implements ObjectPool { private final Supplier supplier; private final Consumer recycler; private final Consumer consumer; private final LinkedBlockingDeque pool; public SimpleObjectPool(int poolSize, Supplier supplier, Consumer recycler, Consumer consumer) { this.supplier = supplier; this.recycler = recycler; this.consumer = consumer; pool = new LinkedBlockingDeque(poolSize); } public SimpleObjectPool(int poolSize, Supplier supplier, Consumer consumer) { this(poolSize, supplier, object -> {}, consumer); } @Override public PooledObject allocate() { T obj = pool.poll(); if(obj == null) { obj = supplier.get(); } return new SimplePooledObject<>(obj, this); } private static final class SimplePooledObject implements PooledObject { private static final AtomicIntegerFieldUpdater closedUpdater = AtomicIntegerFieldUpdater.newUpdater(SimplePooledObject.class, "closed"); private volatile int closed; private final T object; private final SimpleObjectPool objectPool; SimplePooledObject(T object, SimpleObjectPool objectPool) { this.object = object; this.objectPool = objectPool; } @Override public T getObject() { if (closedUpdater.get(this) != 0) { throw UndertowMessages.MESSAGES.objectIsClosed(); } return object; } @Override public void close() { if (closedUpdater.compareAndSet(this, 0, 1)) { objectPool.recycler.accept(object); if (!objectPool.pool.offer(object)) { objectPool.consumer.accept(object); } } } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/StatusCodes.java000066400000000000000000000303351420065311100264210ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; /** * @author Stuart Douglas */ public class StatusCodes { //chosen simply because it gives no collisions //if more codes are added this will need to be re-evaluated private static final int SIZE = 0x2df; private static final Entry[] TABLE = new Entry[SIZE]; public static final int CONTINUE = 100; public static final int SWITCHING_PROTOCOLS = 101; public static final int PROCESSING = 102; public static final int OK = 200; public static final int CREATED = 201; public static final int ACCEPTED = 202; public static final int NON_AUTHORITATIVE_INFORMATION = 203; public static final int NO_CONTENT = 204; public static final int RESET_CONTENT = 205; public static final int PARTIAL_CONTENT = 206; public static final int MULTI_STATUS = 207; public static final int ALREADY_REPORTED = 208; public static final int IM_USED = 226; public static final int MULTIPLE_CHOICES = 300; public static final int MOVED_PERMANENTLY = 301; @Deprecated //typo, but left in for now due to backwards compat public static final int MOVED_PERMENANTLY = MOVED_PERMANENTLY; public static final int FOUND = 302; public static final int SEE_OTHER = 303; public static final int NOT_MODIFIED = 304; public static final int USE_PROXY = 305; public static final int TEMPORARY_REDIRECT = 307; public static final int PERMANENT_REDIRECT = 308; public static final int BAD_REQUEST = 400; public static final int UNAUTHORIZED = 401; public static final int PAYMENT_REQUIRED = 402; public static final int FORBIDDEN = 403; public static final int NOT_FOUND = 404; public static final int METHOD_NOT_ALLOWED = 405; public static final int NOT_ACCEPTABLE = 406; public static final int PROXY_AUTHENTICATION_REQUIRED = 407; public static final int REQUEST_TIME_OUT = 408; public static final int CONFLICT = 409; public static final int GONE = 410; public static final int LENGTH_REQUIRED = 411; public static final int PRECONDITION_FAILED = 412; public static final int REQUEST_ENTITY_TOO_LARGE = 413; public static final int REQUEST_URI_TOO_LARGE = 414; public static final int UNSUPPORTED_MEDIA_TYPE = 415; public static final int REQUEST_RANGE_NOT_SATISFIABLE = 416; public static final int EXPECTATION_FAILED = 417; public static final int UNPROCESSABLE_ENTITY = 422; public static final int LOCKED = 423; public static final int FAILED_DEPENDENCY = 424; public static final int UPGRADE_REQUIRED = 426; public static final int PRECONDITION_REQUIRED = 428; public static final int TOO_MANY_REQUESTS = 429; public static final int REQUEST_HEADER_FIELDS_TOO_LARGE = 431; public static final int INTERNAL_SERVER_ERROR = 500; public static final int NOT_IMPLEMENTED = 501; public static final int BAD_GATEWAY = 502; public static final int SERVICE_UNAVAILABLE = 503; public static final int GATEWAY_TIME_OUT = 504; public static final int HTTP_VERSION_NOT_SUPPORTED = 505; public static final int INSUFFICIENT_STORAGE = 507; public static final int LOOP_DETECTED = 508; public static final int NOT_EXTENDED = 510; public static final int NETWORK_AUTHENTICATION_REQUIRED = 511; public static final String CONTINUE_STRING = "Continue"; public static final String SWITCHING_PROTOCOLS_STRING = "Switching Protocols"; public static final String PROCESSING_STRING = "Processing"; public static final String OK_STRING = "OK"; public static final String CREATED_STRING = "Created"; public static final String ACCEPTED_STRING = "Accepted"; public static final String NON_AUTHORITATIVE_INFORMATION_STRING = "Non-Authoritative Information"; public static final String NO_CONTENT_STRING = "No Content"; public static final String RESET_CONTENT_STRING = "Reset Content"; public static final String PARTIAL_CONTENT_STRING = "Partial Content"; public static final String MULTI_STATUS_STRING = "Multi-Status"; public static final String ALREADY_REPORTED_STRING = "Already Reported"; public static final String IM_USED_STRING = "IM Used"; public static final String MULTIPLE_CHOICES_STRING = "Multiple Choices"; public static final String MOVED_PERMANENTLY_STRING = "Moved Permanently"; public static final String FOUND_STRING = "Found"; public static final String SEE_OTHER_STRING = "See Other"; public static final String NOT_MODIFIED_STRING = "Not Modified"; public static final String USE_PROXY_STRING = "Use Proxy"; public static final String TEMPORARY_REDIRECT_STRING = "Temporary Redirect"; public static final String PERMANENT_REDIRECT_STRING = "Permanent Redirect"; public static final String BAD_REQUEST_STRING = "Bad Request"; public static final String UNAUTHORIZED_STRING = "Unauthorized"; public static final String PAYMENT_REQUIRED_STRING = "Payment Required"; public static final String FORBIDDEN_STRING = "Forbidden"; public static final String NOT_FOUND_STRING = "Not Found"; public static final String METHOD_NOT_ALLOWED_STRING = "Method Not Allowed"; public static final String NOT_ACCEPTABLE_STRING = "Not Acceptable"; public static final String PROXY_AUTHENTICATION_REQUIRED_STRING = "Proxy Authentication Required"; public static final String REQUEST_TIME_OUT_STRING = "Request Time-out"; public static final String CONFLICT_STRING = "Conflict"; public static final String GONE_STRING = "Gone"; public static final String LENGTH_REQUIRED_STRING = "Length Required"; public static final String PRECONDITION_FAILED_STRING = "Precondition Failed"; public static final String REQUEST_ENTITY_TOO_LARGE_STRING = "Request Entity Too Large"; public static final String REQUEST_URI_TOO_LARGE_STRING = "Request-URI Too Large"; public static final String UNSUPPORTED_MEDIA_TYPE_STRING = "Unsupported Media Type"; public static final String REQUEST_RANGE_NOT_SATISFIABLE_STRING = "Requested range not satisfiable"; public static final String EXPECTATION_FAILED_STRING = "Expectation Failed"; public static final String UNPROCESSABLE_ENTITY_STRING = "Unprocessable Entity"; public static final String LOCKED_STRING = "Locked"; public static final String FAILED_DEPENDENCY_STRING = "Failed Dependency"; public static final String UPGRADE_REQUIRED_STRING = "Upgrade Required"; public static final String PRECONDITION_REQUIRED_STRING = "Precondition Required"; public static final String TOO_MANY_REQUESTS_STRING = "Too Many Requests"; public static final String REQUEST_HEADER_FIELDS_TOO_LARGE_STRING = "Request Header Fields Too Large"; public static final String INTERNAL_SERVER_ERROR_STRING = "Internal Server Error"; public static final String NOT_IMPLEMENTED_STRING = "Not Implemented"; public static final String BAD_GATEWAY_STRING = "Bad Gateway"; public static final String SERVICE_UNAVAILABLE_STRING = "Service Unavailable"; public static final String GATEWAY_TIME_OUT_STRING = "Gateway Time-out"; public static final String HTTP_VERSION_NOT_SUPPORTED_STRING = "HTTP Version not supported"; public static final String INSUFFICIENT_STORAGE_STRING = "Insufficient Storage"; public static final String LOOP_DETECTED_STRING = "Loop Detected"; public static final String NOT_EXTENDED_STRING = "Not Extended"; public static final String NETWORK_AUTHENTICATION_REQUIRED_STRING = "Network Authentication Required"; static { putCode(CONTINUE, CONTINUE_STRING); putCode(SWITCHING_PROTOCOLS, SWITCHING_PROTOCOLS_STRING); putCode(PROCESSING, PROCESSING_STRING); putCode(OK, OK_STRING); putCode(CREATED, CREATED_STRING); putCode(ACCEPTED, ACCEPTED_STRING); putCode(NON_AUTHORITATIVE_INFORMATION, NON_AUTHORITATIVE_INFORMATION_STRING); putCode(NO_CONTENT, NO_CONTENT_STRING); putCode(RESET_CONTENT, RESET_CONTENT_STRING); putCode(PARTIAL_CONTENT, PARTIAL_CONTENT_STRING); putCode(MULTI_STATUS, MULTI_STATUS_STRING); putCode(ALREADY_REPORTED, ALREADY_REPORTED_STRING); putCode(IM_USED, IM_USED_STRING); putCode(MULTIPLE_CHOICES, MULTIPLE_CHOICES_STRING); putCode(MOVED_PERMANENTLY, MOVED_PERMANENTLY_STRING); putCode(FOUND, FOUND_STRING); putCode(SEE_OTHER, SEE_OTHER_STRING); putCode(NOT_MODIFIED, NOT_MODIFIED_STRING); putCode(USE_PROXY, USE_PROXY_STRING); putCode(TEMPORARY_REDIRECT, TEMPORARY_REDIRECT_STRING); putCode(PERMANENT_REDIRECT, PERMANENT_REDIRECT_STRING); putCode(BAD_REQUEST, BAD_REQUEST_STRING); putCode(UNAUTHORIZED, UNAUTHORIZED_STRING); putCode(PAYMENT_REQUIRED, PAYMENT_REQUIRED_STRING); putCode(FORBIDDEN, FORBIDDEN_STRING); putCode(NOT_FOUND, NOT_FOUND_STRING); putCode(METHOD_NOT_ALLOWED, METHOD_NOT_ALLOWED_STRING); putCode(NOT_ACCEPTABLE, NOT_ACCEPTABLE_STRING); putCode(PROXY_AUTHENTICATION_REQUIRED, PROXY_AUTHENTICATION_REQUIRED_STRING); putCode(REQUEST_TIME_OUT, REQUEST_TIME_OUT_STRING); putCode(CONFLICT, CONFLICT_STRING); putCode(GONE, GONE_STRING); putCode(LENGTH_REQUIRED, LENGTH_REQUIRED_STRING); putCode(PRECONDITION_FAILED, PRECONDITION_FAILED_STRING); putCode(REQUEST_ENTITY_TOO_LARGE, REQUEST_ENTITY_TOO_LARGE_STRING); putCode(REQUEST_URI_TOO_LARGE, REQUEST_URI_TOO_LARGE_STRING); putCode(UNSUPPORTED_MEDIA_TYPE, UNSUPPORTED_MEDIA_TYPE_STRING); putCode(REQUEST_RANGE_NOT_SATISFIABLE, REQUEST_RANGE_NOT_SATISFIABLE_STRING); putCode(EXPECTATION_FAILED, EXPECTATION_FAILED_STRING); putCode(UNPROCESSABLE_ENTITY, UNPROCESSABLE_ENTITY_STRING); putCode(LOCKED, LOCKED_STRING); putCode(FAILED_DEPENDENCY, FAILED_DEPENDENCY_STRING); putCode(UPGRADE_REQUIRED, UPGRADE_REQUIRED_STRING); putCode(PRECONDITION_REQUIRED, PRECONDITION_REQUIRED_STRING); putCode(TOO_MANY_REQUESTS, TOO_MANY_REQUESTS_STRING); putCode(REQUEST_HEADER_FIELDS_TOO_LARGE, REQUEST_HEADER_FIELDS_TOO_LARGE_STRING); putCode(INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR_STRING); putCode(NOT_IMPLEMENTED, NOT_IMPLEMENTED_STRING); putCode(BAD_GATEWAY, BAD_GATEWAY_STRING); putCode(SERVICE_UNAVAILABLE, SERVICE_UNAVAILABLE_STRING); putCode(GATEWAY_TIME_OUT, GATEWAY_TIME_OUT_STRING); putCode(HTTP_VERSION_NOT_SUPPORTED, HTTP_VERSION_NOT_SUPPORTED_STRING); putCode(INSUFFICIENT_STORAGE, INSUFFICIENT_STORAGE_STRING); putCode(LOOP_DETECTED, LOOP_DETECTED_STRING); putCode(NOT_EXTENDED, NOT_EXTENDED_STRING); putCode(NETWORK_AUTHENTICATION_REQUIRED, NETWORK_AUTHENTICATION_REQUIRED_STRING); } private static void putCode(int code, String reason) { Entry e = new Entry(reason, code); int h = code & SIZE; if(TABLE[h] != null) { throw new IllegalArgumentException("hash collision"); } TABLE[h] = e; } private StatusCodes() { } public static final String getReason(final int code) { final int hash = code & SIZE; if (hash == SIZE) { return "Unknown"; } final Entry result = TABLE[hash]; if (result == null || result.code != code) { return "Unknown"; } else { return result.reason; } } private static final class Entry { final String reason; final int code; private Entry(final String reason, final int code) { this.reason = reason; this.code = code; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/StringReadChannelListener.java000066400000000000000000000064461420065311100312270ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.io.IOException; import java.nio.ByteBuffer; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.XnioByteBufferPool; import io.undertow.websockets.core.UTF8Output; import org.xnio.ChannelListener; import org.xnio.IoUtils; import io.undertow.connector.ByteBufferPool; import org.xnio.Pool; import org.xnio.channels.StreamSourceChannel; /** * Simple utility class for reading a string *

* todo: handle unicode properly * * @author Stuart Douglas */ public abstract class StringReadChannelListener implements ChannelListener { private final UTF8Output string = new UTF8Output(); private final ByteBufferPool bufferPool; public StringReadChannelListener(final ByteBufferPool bufferPool) { this.bufferPool = bufferPool; } @Deprecated public StringReadChannelListener(final Pool bufferPool) { this.bufferPool = new XnioByteBufferPool(bufferPool); } public void setup(final StreamSourceChannel channel) { PooledByteBuffer resource = bufferPool.allocate(); ByteBuffer buffer = resource.getBuffer(); try { int r = 0; do { r = channel.read(buffer); if (r == 0) { channel.getReadSetter().set(this); channel.resumeReads(); } else if (r == -1) { stringDone(string.extract()); IoUtils.safeClose(channel); } else { buffer.flip(); string.write(buffer); } } while (r > 0); } catch (IOException e) { error(e); } finally { resource.close(); } } @Override public void handleEvent(final StreamSourceChannel channel) { PooledByteBuffer resource = bufferPool.allocate(); ByteBuffer buffer = resource.getBuffer(); try { int r = 0; do { r = channel.read(buffer); if (r == 0) { return; } else if (r == -1) { stringDone(string.extract()); IoUtils.safeClose(channel); } else { buffer.flip(); string.write(buffer); } } while (r > 0); } catch (IOException e) { error(e); } finally { resource.close(); } } protected abstract void stringDone(String string); protected abstract void error(IOException e); } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/StringWriteChannelListener.java000066400000000000000000000071421420065311100314400ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.Charset; import io.undertow.UndertowLogger; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.channels.StreamSinkChannel; /** * A simple write listener that can be used to write out the contents of a String. When the string is written * out it closes the channel. * * This should not be added directly to the channel, instead {@link #setup(org.xnio.channels.StreamSinkChannel)} * should be called, which will attempt a write, and only add the listener if required. * * @author Stuart Douglas */ public class StringWriteChannelListener implements ChannelListener { private final ByteBuffer buffer; public StringWriteChannelListener( final String string) { this(string, Charset.defaultCharset()); } public StringWriteChannelListener( final String string, Charset charset) { buffer = ByteBuffer.wrap(string.getBytes(charset)); } public void setup(final StreamSinkChannel channel) { try { int c; do { c = channel.write(buffer); } while (buffer.hasRemaining() && c > 0); if (buffer.hasRemaining()) { channel.getWriteSetter().set(this); channel.resumeWrites(); } else { writeDone(channel); } } catch (IOException e) { handleError(channel, e); } } protected void handleError(StreamSinkChannel channel, IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); IoUtils.safeClose(channel); } @Override public void handleEvent(final StreamSinkChannel channel) { try { int c; do { c = channel.write(buffer); } while (buffer.hasRemaining() && c > 0); if (buffer.hasRemaining()) { channel.resumeWrites(); return; } else { writeDone(channel); } } catch (IOException e) { handleError(channel, e); } } public boolean hasRemaining() { return buffer.hasRemaining(); } protected void writeDone(final StreamSinkChannel channel) { try { channel.shutdownWrites(); if (!channel.flush()) { channel.getWriteSetter().set(ChannelListeners.flushingChannelListener(new ChannelListener() { @Override public void handleEvent(StreamSinkChannel o) { IoUtils.safeClose(channel); } }, ChannelListeners.closingChannelExceptionHandler())); channel.resumeWrites(); } } catch (IOException e) { handleError(channel, e); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/SubstringMap.java000066400000000000000000000165111420065311100265760ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; /** * A string keyed map that can be accessed as a substring, eliminating the need to allocate a new string * to do a key comparison against. *

* This class uses linear probing and is thread safe due to copy on write semantics. As such it is not recomended * for data that changes frequently. *

* This class does not actually implement the map interface to avoid implementing unnecessary operations. * * @author Stuart Douglas */ public class SubstringMap { private static final int ALL_BUT_LAST_BIT = ~1; private volatile Object[] table = new Object[16]; private int size; public SubstringMatch get(String key, int length) { return get(key, length, false); } public SubstringMatch get(String key) { return get(key, key.length(), false); } private SubstringMatch get(String key, int length, boolean exact) { if(key.length() < length) { throw new IllegalArgumentException(); } Object[] table = this.table; int hash = hash(key, length); int pos = tablePos(table, hash); int start = pos; while (table[pos] != null) { if(exact) { if(table[pos].equals(key)) { return (SubstringMatch) table[pos + 1]; } } else { if (doEquals((String) table[pos], key, length)) { return (SubstringMatch) table[pos + 1]; } } pos += 2; if(pos >= table.length) { pos = 0; } if(pos == start) { return null; } } return null; } private int tablePos(Object[] table, int hash) { return (hash & (table.length - 1)) & ALL_BUT_LAST_BIT; } private boolean doEquals(String s1, String s2, int length) { if(s1.length() != length || s2.length() < length) { return false; } for(int i = 0; i < length; ++i) { if(s1.charAt(i) != s2.charAt(i)) { return false; } } return true; } public synchronized void put(String key, V value) { if (key == null) { throw new NullPointerException(); } Object[] newTable; if (table.length / (double) size < 4 && table.length != Integer.MAX_VALUE) { newTable = new Object[table.length << 1]; for (int i = 0; i < table.length; i += 2) { if (table[i] != null) { doPut(newTable, (String) table[i], table[i + 1]); } } } else { newTable = new Object[table.length]; System.arraycopy(table, 0, newTable, 0, table.length); } doPut(newTable, key, new SubstringMap.SubstringMatch<>(key, value)); this.table = newTable; size++; } public synchronized V remove(String key) { if (key == null) { throw new NullPointerException(); } //we just assume it is present, and always do a copy //for this maps intended use cases as a path matcher it won't be called when //the value is not present anyway V value = null; Object[] newTable = new Object[table.length]; for (int i = 0; i < table.length; i += 2) { if (table[i] != null && !table[i].equals(key)) { doPut(newTable, (String) table[i], table[i + 1]); } else if (table[i] != null) { value = (V) table[i + 1]; size--; } } this.table = newTable; if(value == null) { return null; } return ((SubstringMatch)value).getValue(); } private void doPut(Object[] newTable, String key, Object value) { int hash = hash(key, key.length()); int pos = tablePos(newTable, hash); while (newTable[pos] != null && !newTable[pos].equals(key)) { pos += 2; if (pos >= newTable.length) { pos = 0; } } newTable[pos] = key; newTable[pos + 1] = value; } public Map toMap() { Map map = new HashMap<>(); Object[] t = this.table; for(int i = 0; i < t.length; i += 2) { if(t[i] != null) { map.put((String)t[i], ((SubstringMatch)t[i+1]).value); } } return map; } public synchronized void clear() { size = 0; table = new Object[16]; } private static int hash(String value, int length) { if (length == 0) { return 0; } int h = 0; for (int i = 0; i < length; i++) { h = 31 * h + value.charAt(i); } return h; } public Iterable keys() { return new Iterable() { @Override public Iterator iterator() { final Object[] tMap = table; int i = 0; while (i < table.length && tMap[i] == null) { i += 2; } final int startPos = i; return new Iterator() { private Object[] map = tMap; private int pos = startPos; @Override public boolean hasNext() { return pos < table.length; } @Override public String next() { if (!hasNext()) { throw new NoSuchElementException(); } String ret = (String) map[pos]; pos += 2; while (pos < table.length && tMap[pos] == null) { pos += 2; } return ret; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } }; } public static final class SubstringMatch { private final String key; private final V value; public SubstringMatch(String key, V value) { this.key = key; this.value = value; } public String getKey() { return key; } public V getValue() { return value; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/Transfer.java000066400000000000000000000260771420065311100257540ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.UndertowMessages; import io.undertow.connector.ByteBufferPool; import org.xnio.ChannelExceptionHandler; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import io.undertow.connector.PooledByteBuffer; import org.xnio.channels.Channels; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Channel; /** * @author Stuart Douglas */ public class Transfer { /** * Initiate a low-copy transfer between two stream channels. The pool should be a direct buffer pool for best * performance. * * @param source the source channel * @param sink the target channel * @param sourceListener the source listener to set and call when the transfer is complete, or {@code null} to clear the listener at that time * @param sinkListener the target listener to set and call when the transfer is complete, or {@code null} to clear the listener at that time * @param readExceptionHandler the read exception handler to call if an error occurs during a read operation * @param writeExceptionHandler the write exception handler to call if an error occurs during a write operation * @param pool the pool from which the transfer buffer should be allocated */ public static void initiateTransfer(final I source, final O sink, final ChannelListener sourceListener, final ChannelListener sinkListener, final ChannelExceptionHandler readExceptionHandler, final ChannelExceptionHandler writeExceptionHandler, ByteBufferPool pool) { if (pool == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("pool"); } final PooledByteBuffer allocated = pool.allocate(); boolean free = true; try { final ByteBuffer buffer = allocated.getBuffer(); long read; for(;;) { try { read = source.read(buffer); buffer.flip(); } catch (IOException e) { ChannelListeners.invokeChannelExceptionHandler(source, readExceptionHandler, e); return; } if (read == 0 && !buffer.hasRemaining()) { break; } if (read == -1 && !buffer.hasRemaining()) { done(source, sink, sourceListener, sinkListener); return; } while (buffer.hasRemaining()) { final int res; try { res = sink.write(buffer); } catch (IOException e) { ChannelListeners.invokeChannelExceptionHandler(sink, writeExceptionHandler, e); return; } if (res == 0) { break; } } if(buffer.hasRemaining()) { break; } buffer.clear(); } PooledByteBuffer current = null; if(buffer.hasRemaining()) { current = allocated; free = false; } final TransferListener listener = new TransferListener(pool, current, source, sink, sourceListener, sinkListener, writeExceptionHandler, readExceptionHandler, read == -1); sink.getWriteSetter().set(listener); source.getReadSetter().set(listener); //we resume both reads and writes, as we want to keep trying to fill the buffer if(current == null || buffer.capacity() != buffer.remaining()) { //we don't resume if the buffer is 100% full source.resumeReads(); } if(current != null) { //we don't resume writes if we have nothing to write sink.resumeWrites(); } } finally { if (free) { allocated.close(); } } } private static void done(I source, O sink, ChannelListener sourceListener, ChannelListener sinkListener) { Channels.setReadListener(source, sourceListener); if (sourceListener == null) { source.suspendReads(); } else { source.wakeupReads(); } Channels.setWriteListener(sink, sinkListener); if (sinkListener == null) { sink.suspendWrites(); } else { sink.wakeupWrites(); } } static final class TransferListener implements ChannelListener { private PooledByteBuffer pooledBuffer; private final ByteBufferPool pool; private final I source; private final O sink; private final ChannelListener sourceListener; private final ChannelListener sinkListener; private final ChannelExceptionHandler writeExceptionHandler; private final ChannelExceptionHandler readExceptionHandler; private boolean sourceDone; private boolean done = false; TransferListener(ByteBufferPool pool, final PooledByteBuffer pooledBuffer, final I source, final O sink, final ChannelListener sourceListener, final ChannelListener sinkListener, final ChannelExceptionHandler writeExceptionHandler, final ChannelExceptionHandler readExceptionHandler, boolean sourceDone) { this.pool = pool; this.pooledBuffer = pooledBuffer; this.source = source; this.sink = sink; this.sourceListener = sourceListener; this.sinkListener = sinkListener; this.writeExceptionHandler = writeExceptionHandler; this.readExceptionHandler = readExceptionHandler; this.sourceDone = sourceDone; } public void handleEvent(final Channel channel) { if(done) { if(channel instanceof StreamSinkChannel) { ((StreamSinkChannel) channel).suspendWrites(); } else if(channel instanceof StreamSourceChannel) { ((StreamSourceChannel)channel).suspendReads(); } return; } boolean noWrite = false; if (pooledBuffer == null) { pooledBuffer = pool.allocate(); noWrite = true; } else if(channel instanceof StreamSourceChannel) { noWrite = true; //attempt a read first, as this is a read notification pooledBuffer.getBuffer().compact(); } final ByteBuffer buffer = pooledBuffer.getBuffer(); try { long read; for(;;) { boolean writeFailed = false; //always attempt to write first if we have the buffer if(!noWrite) { while (buffer.hasRemaining()) { final int res; try { res = sink.write(buffer); } catch (IOException e) { pooledBuffer.close(); pooledBuffer = null; done = true; ChannelListeners.invokeChannelExceptionHandler(sink, writeExceptionHandler, e); return; } if (res == 0) { writeFailed = true; break; } } if(sourceDone && !buffer.hasRemaining()) { done = true; done(source, sink, sourceListener, sinkListener); return; } buffer.compact(); } noWrite = false; if(buffer.hasRemaining() && !sourceDone) { try { read = source.read(buffer); buffer.flip(); } catch (IOException e) { pooledBuffer.close(); pooledBuffer = null; done = true; ChannelListeners.invokeChannelExceptionHandler(source, readExceptionHandler, e); return; } if (read == 0) { break; } else if(read == -1) { sourceDone = true; if (!buffer.hasRemaining()) { done = true; done(source, sink, sourceListener, sinkListener); return; } } } else { buffer.flip(); if(writeFailed) { break; } } } //suspend writes if there is nothing to write if(!buffer.hasRemaining()) { sink.suspendWrites(); } else if(!sink.isWriteResumed()) { sink.resumeWrites(); } //suspend reads if there is nothing to read if(buffer.remaining() == buffer.capacity()) { source.suspendReads(); } else if(!source.isReadResumed()){ source.resumeReads(); } } finally { if (pooledBuffer != null && !buffer.hasRemaining()) { pooledBuffer.close(); pooledBuffer = null; } } } public String toString() { return "Transfer channel listener (" + source + " to " + sink + ") -> (" + sourceListener + " and " + sinkListener + ")"; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/URLUtils.java000066400000000000000000000353711420065311100256500ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.io.UnsupportedEncodingException; import java.util.regex.Pattern; import io.undertow.UndertowMessages; import io.undertow.server.HttpServerExchange; /** * Utilities for dealing with URLs * * @author Stuart Douglas * @author Andre Schaefer */ public class URLUtils { private static final char PATH_SEPARATOR = '/'; private static final QueryStringParser QUERY_STRING_PARSER = new QueryStringParser('&', false) { @Override void handle(HttpServerExchange exchange, String key, String value) { exchange.addQueryParam(key, value); } }; private static final QueryStringParser PATH_PARAM_PARSER = new QueryStringParser(';', true) { @Override void handle(HttpServerExchange exchange, String key, String value) { exchange.addPathParam(key, value); } }; // RFC-3986 (URI Generic Syntax) states: // URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) // "The scheme and path components are required, though the path may be empty (no characters)." private static final Pattern SCHEME_PATTERN = Pattern.compile("^[a-zA-Z][a-zA-Z0-9+-.]*:.*"); private URLUtils() { } public static void parseQueryString(final String string, final HttpServerExchange exchange, final String charset, final boolean doDecode, int maxParameters) throws ParameterLimitException { QUERY_STRING_PARSER.parse(string, exchange, charset, doDecode, maxParameters); } @Deprecated public static void parsePathParms(final String string, final HttpServerExchange exchange, final String charset, final boolean doDecode, int maxParameters) throws ParameterLimitException { parsePathParams(string, exchange, charset, doDecode, maxParameters); } public static int parsePathParams(final String string, final HttpServerExchange exchange, final String charset, final boolean doDecode, int maxParameters) throws ParameterLimitException { return PATH_PARAM_PARSER.parse(string, exchange, charset, doDecode, maxParameters); } /** * Decodes a URL. If the decoding fails for any reason then an IllegalArgumentException will be thrown. * * @param s The string to decode * @param enc The encoding * @param decodeSlash If slash characters should be decoded * @param buffer The string builder to use as a buffer. * @return The decoded URL */ public static String decode(String s, String enc, boolean decodeSlash, StringBuilder buffer) { return decode(s, enc, decodeSlash, true, buffer); } /** * Decodes a URL. If the decoding fails for any reason then an IllegalArgumentException will be thrown. * * @param s The string to decode * @param enc The encoding * @param decodeSlash If slash characters should be decoded * @param buffer The string builder to use as a buffer. * @return The decoded URL */ public static String decode(String s, String enc, boolean decodeSlash, boolean formEncoding, StringBuilder buffer) { buffer.setLength(0); boolean needToChange = false; int numChars = s.length(); int i = 0; while (i < numChars) { char c = s.charAt(i); if (c == '+') { if (formEncoding) { buffer.append(' '); i++; needToChange = true; } else { i++; buffer.append(c); } } else if (c == '%' || c > 127) { /* * Starting with this instance of a character * that needs to be encoded, process all * consecutive substrings of the form %xy. Each * substring %xy will yield a byte. Convert all * consecutive bytes obtained this way to whatever * character(s) they represent in the provided * encoding. * * Note that we need to decode the whole rest of the value, we can't just decode * three characters. For multi code point characters there if the code point can be * represented as an alphanumeric */ try { // guess the size of the remaining bytes // of remaining bytes // this works for percent encoded characters, // not so much for unencoded bytes byte[] bytes = new byte[numChars - i + 1]; int pos = 0; while ((i < numChars)) { if (c == '%') { // we need 2 more characters to decode the % construct if ((i + 2) >= s.length()) { throw UndertowMessages.MESSAGES.failedToDecodeURL(s, enc, null); } char p1 = Character.toLowerCase(s.charAt(i + 1)); char p2 = Character.toLowerCase(s.charAt(i + 2)); if (!decodeSlash && ((p1 == '2' && p2 == 'f') || (p1 == '5' && p2 == 'c'))) { if(pos + 2 >= bytes.length) { bytes = expandBytes(bytes); } bytes[pos++] = (byte) c; // should be copied with preserved upper/lower case bytes[pos++] = (byte) s.charAt(i + 1); bytes[pos++] = (byte) s.charAt(i + 2); i += 3; if (i < numChars) { c = s.charAt(i); } continue; } int v = 0; if (p1 >= '0' && p1 <= '9') { v = (p1 - '0') << 4; } else if (p1 >= 'a' && p1 <= 'f') { v = (p1 - 'a' + 10) << 4; } else { throw UndertowMessages.MESSAGES.failedToDecodeURL(s, enc, null); } if (p2 >= '0' && p2 <= '9') { v += (p2 - '0'); } else if (p2 >= 'a' && p2 <= 'f') { v += (p2 - 'a' + 10); } else { throw UndertowMessages.MESSAGES.failedToDecodeURL(s, enc, null); } if (v < 0) { throw UndertowMessages.MESSAGES.failedToDecodeURL(s, enc, null); } if(pos == bytes.length) { bytes = expandBytes(bytes); } bytes[pos++] = (byte) v; i += 3; if (i < numChars) { c = s.charAt(i); } } else if (c == '+' && formEncoding) { if(pos == bytes.length) { bytes = expandBytes(bytes); } bytes[pos++] = (byte) ' '; ++i; if (i < numChars) { c = s.charAt(i); } } else { if (pos == bytes.length) { bytes = expandBytes(bytes); } ++i; if(c >> 8 != 0) { bytes[pos++] = (byte) (c >> 8); if (pos == bytes.length) { bytes = expandBytes(bytes); } bytes[pos++] = (byte) c; } else { bytes[pos++] = (byte) c; } if (i < numChars) { c = s.charAt(i); } } } String decoded = new String(bytes, 0, pos, enc); buffer.append(decoded); } catch (NumberFormatException e) { throw UndertowMessages.MESSAGES.failedToDecodeURL(s, enc, e); } catch (UnsupportedEncodingException e) { throw UndertowMessages.MESSAGES.failedToDecodeURL(s, enc, e); } needToChange = true; break; } else { buffer.append(c); i++; } } return (needToChange ? buffer.toString() : s); } private static byte[] expandBytes(byte[] bytes) { byte[] newBytes = new byte[bytes.length + 10]; System.arraycopy(bytes, 0, newBytes, 0, bytes.length); return newBytes; } private abstract static class QueryStringParser { private final char separator; private final boolean parseUntilSeparator; QueryStringParser(final char separator, final boolean parseUntilSeparator) { this.separator = separator; this.parseUntilSeparator = parseUntilSeparator; } int parse(final String string, final HttpServerExchange exchange, final String charset, final boolean doDecode, int max) throws ParameterLimitException { int count = 0; int i = 0; try { int stringStart = 0; String attrName = null; for (i = 0; i < string.length(); ++i) { char c = string.charAt(i); if (c == '=' && attrName == null) { attrName = string.substring(stringStart, i); stringStart = i + 1; } else if (c == separator) { if (attrName != null) { handle(exchange, decode(charset, attrName, doDecode), decode(charset, string.substring(stringStart, i), doDecode)); if(++count > max) { throw UndertowMessages.MESSAGES.tooManyParameters(max); } } else if (stringStart != i) { // Ignore if attrName == null and stringStart == i because it means both key and value are empty. handle(exchange, decode(charset, string.substring(stringStart, i), doDecode), ""); if(++count > max) { throw UndertowMessages.MESSAGES.tooManyParameters(max); } } stringStart = i + 1; attrName = null; } else if (parseUntilSeparator && (c == '?' || c == '/')) { break; } } if (attrName != null) { handle(exchange, decode(charset, attrName, doDecode), decode(charset, string.substring(stringStart, i), doDecode)); if(++count > max) { throw UndertowMessages.MESSAGES.tooManyParameters(max); } } else if (string.length() != stringStart) { handle(exchange, decode(charset, string.substring(stringStart, i), doDecode), ""); if(++count > max) { throw UndertowMessages.MESSAGES.tooManyParameters(max); } } } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } return i; } private String decode(String charset, String attrName, final boolean doDecode) throws UnsupportedEncodingException { if (doDecode) { return URLUtils.decode(attrName, charset, true, true, new StringBuilder()); } return attrName; } abstract void handle(final HttpServerExchange exchange, final String key, final String value); } /** * Adds a '/' prefix to the beginning of a path if one isn't present * and removes trailing slashes if any are present. * * @param path the path to normalize * @return a normalized (with respect to slashes) result */ public static String normalizeSlashes(final String path) { // prepare final StringBuilder builder = new StringBuilder(path); boolean modified = false; // remove all trailing '/'s except the first one while (builder.length() > 0 && builder.length() != 1 && PATH_SEPARATOR == builder.charAt(builder.length() - 1)) { builder.deleteCharAt(builder.length() - 1); modified = true; } // add a slash at the beginning if one isn't present if (builder.length() == 0 || PATH_SEPARATOR != builder.charAt(0)) { builder.insert(0, PATH_SEPARATOR); modified = true; } // only create string when it was modified if (modified) { return builder.toString(); } return path; } /** * Test if provided location is an absolute URI or not. * * @param location location to check, null = relative, having scheme = absolute * @return true if location is considered absolute */ public static boolean isAbsoluteUrl(String location) { if (location != null && location.length() > 0 && location.contains(":")) { // consider it absolute URL if location contains valid scheme part return SCHEME_PATTERN.matcher(location).matches(); } return false; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/UrlDecodeException.java000066400000000000000000000017221420065311100277030ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; /** * Created by Marek Marusic on 7/25/19. */ public class UrlDecodeException extends IllegalArgumentException { public UrlDecodeException(String message, Throwable cause) { super(message, cause); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/util/WorkerUtils.java000066400000000000000000000041301420065311100264440ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import org.xnio.XnioExecutor; import org.xnio.XnioIoThread; import io.undertow.UndertowLogger; /** * @author Stuart Douglas */ public class WorkerUtils { private WorkerUtils() { } /** * Schedules a task for future execution. If the execution is rejected because the worker is shutting * down then it is logged at debug level and the exception is not re-thrown * @param thread The IO thread * @param task The task to execute * @param timeout The timeout * @param timeUnit The time unit */ public static XnioExecutor.Key executeAfter(XnioIoThread thread, Runnable task, long timeout, TimeUnit timeUnit) { try { return thread.executeAfter(task, timeout, timeUnit); } catch (RejectedExecutionException e) { if(thread.getWorker().isShutdown()) { UndertowLogger.ROOT_LOGGER.debugf(e, "Failed to schedule task %s as worker is shutting down", task); //we just return a bogus key in this case return new XnioExecutor.Key() { @Override public boolean remove() { return false; } }; } else { throw e; } } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/000077500000000000000000000000001420065311100245055ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/WebSocketConnectionCallback.java000066400000000000000000000023151420065311100326740ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.spi.WebSocketHttpExchange; /** * Interface that is used on the client side to accept web socket connections * * @author Stuart Douglas */ public interface WebSocketConnectionCallback { /** * Is called once the WebSocket connection is established, which means the handshake was successful. * */ void onConnect(WebSocketHttpExchange exchange, WebSocketChannel channel); } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/WebSocketExtension.java000066400000000000000000000113121420065311100311310ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; /** * @author Stuart Douglas */ public class WebSocketExtension { private final String name; private final List parameters; public WebSocketExtension(String name) { this.name = name; this.parameters = new ArrayList<>(); } public WebSocketExtension(String name, List parameters) { this.name = name; this.parameters = Collections.unmodifiableList(new ArrayList<>(parameters)); } public String getName() { return name; } public List getParameters() { return parameters; } public static final class Parameter { private final String name; private final String value; public Parameter(String name, String value) { this.name = name; this.value = value; } public String getName() { return name; } public String getValue() { return value; } @Override public String toString() { return "{'" + name + '\'' + ": '" + value + '\'' + '}'; } } @Override public String toString() { return "WebSocketExtension{" + "name='" + name + '\'' + ", parameters=" + parameters + '}'; } public static List parse(final String extensionHeader) { if(extensionHeader == null || extensionHeader.isEmpty()) { return Collections.emptyList(); } List extensions = new ArrayList<>(); //TODO: more efficient parsing algorithm String[] parts = extensionHeader.split(","); for (String part : parts) { String[] items = part.split(";"); if (items.length > 0) { final List params = new ArrayList<>(items.length - 1); String name = items[0].trim(); for (int i = 1; i < items.length; ++i) { /* Extensions can have parameters without values */ if (items[i].contains("=")) { String[] param = items[i].split("="); if (param.length == 2) { params.add(new Parameter(param[0].trim(), param[1].trim())); } } else { params.add(new Parameter(items[i].trim(), null)); } } extensions.add(new WebSocketExtension(name, params)); } } return extensions; } /** * Compose a String from a list of extensions to be used in the response of a protocol negotiation. * * @see io.undertow.util.Headers * * @param extensions list of {@link WebSocketExtension} * @return a string representation of the extensions */ public static String toExtensionHeader(final List extensions) { StringBuilder extensionsHeader = new StringBuilder(); if (extensions != null && extensions.size() > 0) { Iterator it = extensions.iterator(); while (it.hasNext()) { WebSocketExtension extension = it.next(); extensionsHeader.append(extension.getName()); for (Parameter param : extension.getParameters()) { extensionsHeader.append("; ").append(param.getName()); if (param.getValue() != null && param.getValue().length() > 0) { extensionsHeader.append("=").append(param.getValue()); } } if (it.hasNext()) { extensionsHeader.append(", "); } } } return extensionsHeader.toString(); } } WebSocketProtocolHandshakeHandler.java000066400000000000000000000213221420065311100340060ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpUpgradeListener; import io.undertow.server.handlers.ResponseCodeHandler; import io.undertow.util.Methods; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketLogger; import io.undertow.websockets.core.protocol.Handshake; import io.undertow.websockets.core.protocol.version07.Hybi07Handshake; import io.undertow.websockets.core.protocol.version08.Hybi08Handshake; import io.undertow.websockets.core.protocol.version13.Hybi13Handshake; import io.undertow.websockets.extensions.ExtensionHandshake; import io.undertow.websockets.spi.AsyncWebSocketHttpServerExchange; import org.xnio.StreamConnection; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * {@link HttpHandler} which will process the {@link HttpServerExchange} and do the actual handshake/upgrade * to WebSocket. * * @author Norman Maurer */ public class WebSocketProtocolHandshakeHandler implements HttpHandler { private final Set handshakes; /** * The upgrade listener. This will only be used if another web socket implementation is being layered on top. */ private final HttpUpgradeListener upgradeListener; private final WebSocketConnectionCallback callback; private final Set peerConnections = Collections.newSetFromMap(new ConcurrentHashMap()); /** * The handler that is invoked if there are no web socket headers */ private final HttpHandler next; /** * Create a new {@link WebSocketProtocolHandshakeHandler} * * @param callback The {@link WebSocketConnectionCallback} which will be executed once the handshake was * established */ public WebSocketProtocolHandshakeHandler(final WebSocketConnectionCallback callback) { this(callback, ResponseCodeHandler.HANDLE_404); } /** * Create a new {@link WebSocketProtocolHandshakeHandler} * * @param callback The {@link WebSocketConnectionCallback} which will be executed once the handshake was * established */ public WebSocketProtocolHandshakeHandler(final WebSocketConnectionCallback callback, final HttpHandler next) { this.callback = callback; Set handshakes = new HashSet<>(); handshakes.add(new Hybi13Handshake()); handshakes.add(new Hybi08Handshake()); handshakes.add(new Hybi07Handshake()); this.handshakes = handshakes; this.next = next; this.upgradeListener = null; } /** * Create a new {@link WebSocketProtocolHandshakeHandler} * * @param handshakes The supported handshake methods * @param callback The {@link WebSocketConnectionCallback} which will be executed once the handshake was * established */ public WebSocketProtocolHandshakeHandler(Collection handshakes, final WebSocketConnectionCallback callback) { this(handshakes, callback, ResponseCodeHandler.HANDLE_404); } /** * Create a new {@link WebSocketProtocolHandshakeHandler} * * @param handshakes The supported handshake methods * @param callback The {@link WebSocketConnectionCallback} which will be executed once the handshake was * established */ public WebSocketProtocolHandshakeHandler(Collection handshakes, final WebSocketConnectionCallback callback, final HttpHandler next) { this.callback = callback; this.handshakes = new HashSet<>(handshakes); this.next = next; this.upgradeListener = null; } /** * Create a new {@link WebSocketProtocolHandshakeHandler} * * @param callback The {@link WebSocketConnectionCallback} which will be executed once the handshake was * established */ public WebSocketProtocolHandshakeHandler(final HttpUpgradeListener callback) { this(callback, ResponseCodeHandler.HANDLE_404); } /** * Create a new {@link WebSocketProtocolHandshakeHandler} * * @param callback The {@link WebSocketConnectionCallback} which will be executed once the handshake was * established */ public WebSocketProtocolHandshakeHandler(final HttpUpgradeListener callback, final HttpHandler next) { this.callback = null; Set handshakes = new HashSet<>(); handshakes.add(new Hybi13Handshake()); handshakes.add(new Hybi08Handshake()); handshakes.add(new Hybi07Handshake()); this.handshakes = handshakes; this.next = next; this.upgradeListener = callback; } /** * Create a new {@link WebSocketProtocolHandshakeHandler} * * @param handshakes The supported handshake methods * @param callback The {@link WebSocketConnectionCallback} which will be executed once the handshake was * established */ public WebSocketProtocolHandshakeHandler(Collection handshakes, final HttpUpgradeListener callback) { this(handshakes, callback, ResponseCodeHandler.HANDLE_404); } /** * Create a new {@link WebSocketProtocolHandshakeHandler} * * @param handshakes The supported handshake methods * @param callback The {@link WebSocketConnectionCallback} which will be executed once the handshake was * established */ public WebSocketProtocolHandshakeHandler(Collection handshakes, final HttpUpgradeListener callback, final HttpHandler next) { this.callback = null; this.handshakes = new HashSet<>(handshakes); this.next = next; this.upgradeListener = callback; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { if (!exchange.getRequestMethod().equals(Methods.GET)) { // Only GET is supported to start the handshake next.handleRequest(exchange); return; } final AsyncWebSocketHttpServerExchange facade = new AsyncWebSocketHttpServerExchange(exchange, peerConnections); Handshake handshaker = null; for (Handshake method : handshakes) { if (method.matches(facade)) { handshaker = method; break; } } if (handshaker == null) { next.handleRequest(exchange); } else { WebSocketLogger.REQUEST_LOGGER.debugf("Attempting websocket handshake with %s on %s", handshaker, exchange); final Handshake selected = handshaker; if (upgradeListener == null) { exchange.upgradeChannel(new HttpUpgradeListener() { @Override public void handleUpgrade(StreamConnection streamConnection, HttpServerExchange exchange) { WebSocketChannel channel = selected.createChannel(facade, streamConnection, facade.getBufferPool()); peerConnections.add(channel); callback.onConnect(facade, channel); } }); } else { exchange.upgradeChannel(upgradeListener); } handshaker.handshake(facade); } } public Set getPeerConnections() { return peerConnections; } /** * Add a new WebSocket Extension into the handshakes defined in this handler. * * @param extension a new {@code ExtensionHandshake} instance * @return current handler */ public WebSocketProtocolHandshakeHandler addExtension(ExtensionHandshake extension) { if (extension != null) { for (Handshake handshake : handshakes) { handshake.addExtension(extension); } } return this; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/client/000077500000000000000000000000001420065311100257635ustar00rootroot00000000000000WebSocket13ClientHandshake.java000066400000000000000000000237131420065311100335550ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/client/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.client; import io.undertow.util.FlexBase64; import io.undertow.util.Headers; import io.undertow.websockets.WebSocketExtension; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketMessages; import io.undertow.websockets.core.WebSocketVersion; import io.undertow.websockets.core.protocol.version13.WebSocket13Channel; import io.undertow.websockets.extensions.CompositeExtensionFunction; import io.undertow.websockets.extensions.ExtensionFunction; import io.undertow.websockets.extensions.ExtensionHandshake; import io.undertow.websockets.extensions.NoopExtensionFunction; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import org.xnio.StreamConnection; import org.xnio.http.ExtendedHandshakeChecker; import java.io.IOException; import java.net.URI; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; /** * @author Stuart Douglas */ public class WebSocket13ClientHandshake extends WebSocketClientHandshake { public static final String MAGIC_NUMBER = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; private final WebSocketClientNegotiation negotiation; private final Set extensions; public WebSocket13ClientHandshake(final URI url, WebSocketClientNegotiation negotiation, Set extensions) { super(url); this.negotiation = negotiation; this.extensions = extensions == null ? Collections.emptySet() : extensions; } public WebSocket13ClientHandshake(final URI url) { this(url, null, null); } @Override public WebSocketChannel createChannel(final StreamConnection channel, final String wsUri, final ByteBufferPool bufferPool, OptionMap options) { if (negotiation != null && negotiation.getSelectedExtensions() != null && !negotiation.getSelectedExtensions().isEmpty()) { List selected = negotiation.getSelectedExtensions(); List negotiated = new ArrayList<>(); if (selected != null && !selected.isEmpty()) { for (WebSocketExtension ext : selected) { for (ExtensionHandshake extHandshake : extensions) { if (ext.getName().equals(extHandshake.getName())) { negotiated.add(extHandshake.create()); } } } } return new WebSocket13Channel(channel, bufferPool, wsUri, negotiation.getSelectedSubProtocol(), true, !negotiated.isEmpty(), CompositeExtensionFunction.compose(negotiated), new HashSet(), options); } else { return new WebSocket13Channel(channel, bufferPool, wsUri, negotiation != null ? negotiation.getSelectedSubProtocol() : "", true, false, NoopExtensionFunction.INSTANCE, new HashSet(), options); } } public Map createHeaders() { Map headers = new HashMap<>(); headers.put(Headers.UPGRADE_STRING, "websocket"); headers.put(Headers.CONNECTION_STRING, "upgrade"); String key = createSecKey(); headers.put(Headers.SEC_WEB_SOCKET_KEY_STRING, key); headers.put(Headers.SEC_WEB_SOCKET_VERSION_STRING, getVersion().toHttpHeaderValue()); if (negotiation != null) { List subProtocols = negotiation.getSupportedSubProtocols(); if (subProtocols != null && !subProtocols.isEmpty()) { StringBuilder sb = new StringBuilder(); Iterator it = subProtocols.iterator(); while (it.hasNext()) { sb.append(it.next()); if (it.hasNext()) { sb.append(", "); } } headers.put(Headers.SEC_WEB_SOCKET_PROTOCOL_STRING, sb.toString()); } List extensions = negotiation.getSupportedExtensions(); if (extensions != null && !extensions.isEmpty()) { StringBuilder sb = new StringBuilder(); Iterator it = extensions.iterator(); while (it.hasNext()) { WebSocketExtension next = it.next(); sb.append(next.getName()); for (WebSocketExtension.Parameter param : next.getParameters()) { sb.append("; "); sb.append(param.getName()); /* Extensions can have parameters without values */ if (param.getValue() != null && param.getValue().length() > 0) { sb.append("="); sb.append(param.getValue()); } } if (it.hasNext()) { sb.append(", "); } } headers.put(Headers.SEC_WEB_SOCKET_EXTENSIONS_STRING, sb.toString()); } } return headers; } protected String createSecKey() { SecureRandom random = new SecureRandom(); byte[] data = new byte[16]; for (int i = 0; i < 4; ++i) { int val = random.nextInt(); data[i * 4] = (byte) val; data[i * 4 + 1] = (byte) ((val >> 8) & 0xFF); data[i * 4 + 2] = (byte) ((val >> 16) & 0xFF); data[i * 4 + 3] = (byte) ((val >> 24) & 0xFF); } return FlexBase64.encodeString(data, false); } @Override public ExtendedHandshakeChecker handshakeChecker(final URI uri, final Map> requestHeaders) { final String sentKey = requestHeaders.containsKey(Headers.SEC_WEB_SOCKET_KEY_STRING) ? requestHeaders.get(Headers.SEC_WEB_SOCKET_KEY_STRING).get(0) : null; return new ExtendedHandshakeChecker() { @Override public void checkHandshakeExtended(Map> headers) throws IOException { try { if (negotiation != null) { negotiation.afterRequest(headers); } String upgrade = getFirst(Headers.UPGRADE_STRING, headers); if (upgrade == null || !upgrade.trim().equalsIgnoreCase("websocket")) { throw WebSocketMessages.MESSAGES.noWebSocketUpgradeHeader(); } String connHeader = getFirst(Headers.CONNECTION_STRING, headers); if (connHeader == null || !connHeader.trim().equalsIgnoreCase("upgrade")) { throw WebSocketMessages.MESSAGES.noWebSocketConnectionHeader(); } String acceptKey = getFirst(Headers.SEC_WEB_SOCKET_ACCEPT_STRING, headers); final String dKey = solve(sentKey); if (!dKey.equals(acceptKey)) { throw WebSocketMessages.MESSAGES.webSocketAcceptKeyMismatch(dKey, acceptKey); } if (negotiation != null) { String subProto = getFirst(Headers.SEC_WEB_SOCKET_PROTOCOL_STRING, headers); if (subProto != null && !subProto.isEmpty() && !negotiation.getSupportedSubProtocols().contains(subProto)) { throw WebSocketMessages.MESSAGES.unsupportedProtocol(subProto, negotiation.getSupportedSubProtocols()); } List extensions = Collections.emptyList(); String extHeader = getFirst(Headers.SEC_WEB_SOCKET_EXTENSIONS_STRING, headers); if (extHeader != null) { extensions = WebSocketExtension.parse(extHeader); } negotiation.handshakeComplete(subProto, extensions); } } catch (IOException e) { throw e; } catch (Exception e) { throw new IOException(e); } } }; } private String getFirst(String key, Map> map) { List list = map.get(key.toLowerCase(Locale.ENGLISH)); if(list == null || list.isEmpty()) { return null; } return list.get(0); } protected final String solve(final String nonceBase64) { try { final String concat = nonceBase64 + MAGIC_NUMBER; final MessageDigest digest = MessageDigest.getInstance("SHA1"); digest.update(concat.getBytes(StandardCharsets.UTF_8)); final byte[] bytes = digest.digest(); return FlexBase64.encodeString(bytes, false); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } public WebSocketVersion getVersion() { return WebSocketVersion.V13; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/client/WebSocketClient.java000066400000000000000000000437021420065311100316610ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.client; import io.undertow.UndertowMessages; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientExchange; import io.undertow.client.ClientRequest; import io.undertow.client.UndertowClient; import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.util.Headers; import io.undertow.util.Methods; import io.undertow.util.Protocols; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketLogger; import io.undertow.websockets.core.WebSocketVersion; import io.undertow.websockets.extensions.ExtensionHandshake; import org.xnio.Cancellable; import org.xnio.ChannelListener; import org.xnio.FutureResult; import org.xnio.IoFuture; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import org.xnio.StreamConnection; import org.xnio.XnioWorker; import org.xnio.http.HttpUpgrade; import org.xnio.http.RedirectException; import org.xnio.ssl.XnioSsl; import java.io.IOException; import java.net.InetSocketAddress; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * The Web socket client. * * @author Stuart Douglas */ public class WebSocketClient { public static final String BIND_PROPERTY = "io.undertow.websockets.BIND_ADDRESS"; private static final int MAX_REDIRECTS = Integer.getInteger("io.undertow.websockets.max-redirects", 5); @Deprecated public static IoFuture connect(XnioWorker worker, final ByteBufferPool bufferPool, final OptionMap optionMap, final URI uri, WebSocketVersion version) { return connect(worker, bufferPool, optionMap, uri, version, null); } @Deprecated public static IoFuture connect(XnioWorker worker, XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap optionMap, final URI uri, WebSocketVersion version) { return connect(worker, ssl, bufferPool, optionMap, uri, version, null); } @Deprecated public static IoFuture connect(XnioWorker worker, final ByteBufferPool bufferPool, final OptionMap optionMap, final URI uri, WebSocketVersion version, WebSocketClientNegotiation clientNegotiation) { return connect(worker, null, bufferPool, optionMap, uri, version, clientNegotiation); } @Deprecated public static IoFuture connect(XnioWorker worker, XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap optionMap, final URI uri, WebSocketVersion version, WebSocketClientNegotiation clientNegotiation) { return connect(worker, ssl, bufferPool, optionMap, uri, version, clientNegotiation, null); } @Deprecated public static IoFuture connect(XnioWorker worker, XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap optionMap, final URI uri, WebSocketVersion version, WebSocketClientNegotiation clientNegotiation, Set clientExtensions) { return connect(worker, ssl, bufferPool, optionMap, null, uri, version, clientNegotiation, clientExtensions); } @Deprecated public static IoFuture connect(XnioWorker worker, XnioSsl ssl, final ByteBufferPool bufferPool, final OptionMap optionMap, InetSocketAddress bindAddress, final URI uri, WebSocketVersion version, WebSocketClientNegotiation clientNegotiation, Set clientExtensions) { return connectionBuilder(worker, bufferPool, uri) .setSsl(ssl) .setOptionMap(optionMap) .setBindAddress(bindAddress) .setVersion(version) .setClientNegotiation(clientNegotiation) .setClientExtensions(clientExtensions) .connect(); } public static class ConnectionBuilder { private final XnioWorker worker; private final ByteBufferPool bufferPool; private final URI uri; private XnioSsl ssl; private OptionMap optionMap = OptionMap.EMPTY; private InetSocketAddress bindAddress; private WebSocketVersion version = WebSocketVersion.V13; private WebSocketClientNegotiation clientNegotiation; private Set clientExtensions; private URI proxyUri; private XnioSsl proxySsl; public ConnectionBuilder(XnioWorker worker, ByteBufferPool bufferPool, URI uri) { this.worker = worker; this.bufferPool = bufferPool; this.uri = uri; } public XnioWorker getWorker() { return worker; } public URI getUri() { return uri; } public XnioSsl getSsl() { return ssl; } public ConnectionBuilder setSsl(XnioSsl ssl) { this.ssl = ssl; return this; } public ByteBufferPool getBufferPool() { return bufferPool; } public OptionMap getOptionMap() { return optionMap; } public ConnectionBuilder setOptionMap(OptionMap optionMap) { this.optionMap = optionMap; return this; } public InetSocketAddress getBindAddress() { return bindAddress; } public ConnectionBuilder setBindAddress(InetSocketAddress bindAddress) { this.bindAddress = bindAddress; return this; } public WebSocketVersion getVersion() { return version; } public ConnectionBuilder setVersion(WebSocketVersion version) { this.version = version; return this; } public WebSocketClientNegotiation getClientNegotiation() { return clientNegotiation; } public ConnectionBuilder setClientNegotiation(WebSocketClientNegotiation clientNegotiation) { this.clientNegotiation = clientNegotiation; return this; } public Set getClientExtensions() { return clientExtensions; } public ConnectionBuilder setClientExtensions(Set clientExtensions) { this.clientExtensions = clientExtensions; return this; } public URI getProxyUri() { return proxyUri; } public ConnectionBuilder setProxyUri(URI proxyUri) { this.proxyUri = proxyUri; return this; } public XnioSsl getProxySsl() { return proxySsl; } public ConnectionBuilder setProxySsl(XnioSsl proxySsl) { this.proxySsl = proxySsl; return this; } public IoFuture connect() { return connectImpl(uri, new FutureResult(), 0); } private IoFuture connectImpl(final URI uri, final FutureResult ioFuture, final int redirectCount) { WebSocketLogger.REQUEST_LOGGER.debugf("Opening websocket connection to %s", uri); final String scheme = isSecure(uri) ? "https" : "http"; final URI newUri; try { newUri = new URI(scheme, uri.getUserInfo(), uri.getHost(), uri.getPort() == -1 ? (isSecure(uri) ? 443 : 80) : uri.getPort(), uri.getPath().isEmpty() ? "/" : uri.getPath(), uri.getQuery(), uri.getFragment()); } catch (URISyntaxException e) { throw new RuntimeException(e); } final WebSocketClientHandshake handshake = WebSocketClientHandshake.create(version, newUri, clientNegotiation, clientExtensions); final Map originalHeaders = handshake.createHeaders(); originalHeaders.put(Headers.HOST_STRING, uri.getHost() + ":" + newUri.getPort()); final Map> headers = new HashMap<>(); for(Map.Entry entry : originalHeaders.entrySet()) { List list = new ArrayList<>(); list.add(entry.getValue()); headers.put(entry.getKey(), list); } if (clientNegotiation != null) { clientNegotiation.beforeRequest(headers); } InetSocketAddress toBind = bindAddress; String sysBind = System.getProperty(BIND_PROPERTY); if(toBind == null && sysBind != null) { toBind = new InetSocketAddress(sysBind, 0); } if(proxyUri != null) { UndertowClient.getInstance().connect(new ClientCallback() { @Override public void completed(final ClientConnection connection) { int port = uri.getPort() > 0 ? uri.getPort() : isSecure(uri) ? 443 : 80; ClientRequest cr = new ClientRequest() .setMethod(Methods.CONNECT) .setPath(uri.getHost() + ":" + port) .setProtocol(Protocols.HTTP_1_1); cr.getRequestHeaders().put(Headers.HOST, proxyUri.getHost() + ":" + (proxyUri.getPort() > 0 ? proxyUri.getPort() : 80)); connection.sendRequest(cr, new ClientCallback() { @Override public void completed(ClientExchange result) { result.setResponseListener(new ClientCallback() { @Override public void completed(ClientExchange response) { try { if (response.getResponse().getResponseCode() == 200) { try { StreamConnection targetConnection = connection.performUpgrade(); WebSocketLogger.REQUEST_LOGGER.debugf("Established websocket connection to %s", uri); if (isSecure(uri)) { handleConnectionWithExistingConnection(((UndertowXnioSsl) ssl).wrapExistingConnection(targetConnection, optionMap, uri)); } else { handleConnectionWithExistingConnection(targetConnection); } } catch (IOException e) { ioFuture.setException(e); } catch (Exception e) { ioFuture.setException(new IOException(e)); } } else { ioFuture.setException(UndertowMessages.MESSAGES.proxyConnectionFailed(response.getResponse().getResponseCode())); } } catch (Exception e) { ioFuture.setException(new IOException(e)); } } private void handleConnectionWithExistingConnection(StreamConnection targetConnection) { final IoFuture result; result = HttpUpgrade.performUpgrade(targetConnection, newUri, headers, new WebsocketConnectionListener(optionMap, handshake, newUri, ioFuture), handshake.handshakeChecker(newUri, headers)); result.addNotifier(new IoFuture.Notifier() { @Override public void notify(IoFuture res, Object attachment) { if (res.getStatus() == IoFuture.Status.FAILED) { ioFuture.setException(res.getException()); } } }, null); ioFuture.addCancelHandler(new Cancellable() { @Override public Cancellable cancel() { result.cancel(); return null; } }); } @Override public void failed(IOException e) { ioFuture.setException(e); } }); } @Override public void failed(IOException e) { ioFuture.setException(e); } }); } @Override public void failed(IOException e) { ioFuture.setException(e); } }, bindAddress, proxyUri, worker, proxySsl, bufferPool, optionMap); } else { final IoFuture result; if (ssl != null) { result = HttpUpgrade.performUpgrade(worker, ssl, toBind, newUri, headers, new WebsocketConnectionListener(optionMap, handshake, newUri, ioFuture), null, optionMap, handshake.handshakeChecker(newUri, headers)); } else { result = HttpUpgrade.performUpgrade(worker, toBind, newUri, headers, new WebsocketConnectionListener(optionMap, handshake, newUri, ioFuture), null, optionMap, handshake.handshakeChecker(newUri, headers)); } result.addNotifier(new IoFuture.Notifier() { @Override public void notify(IoFuture res, Object attachment) { if (res.getStatus() == IoFuture.Status.FAILED) { IOException exception = res.getException(); if(exception instanceof RedirectException) { if(redirectCount == MAX_REDIRECTS) { ioFuture.setException(UndertowMessages.MESSAGES.tooManyRedirects(exception)); } else { String path = ((RedirectException) exception).getLocation(); try { connectImpl(new URI(path), ioFuture, redirectCount + 1); } catch (URISyntaxException e) { ioFuture.setException(new IOException(e)); } } } else { ioFuture.setException(exception); } } } }, null); ioFuture.addCancelHandler(new Cancellable() { @Override public Cancellable cancel() { result.cancel(); return null; } }); } return ioFuture.getIoFuture(); } private boolean isSecure(final URI uri) { return uri.getScheme().equals("wss") || uri.getScheme().equals("https"); } private class WebsocketConnectionListener implements ChannelListener { private final OptionMap options; private final WebSocketClientHandshake handshake; private final URI newUri; private final FutureResult ioFuture; WebsocketConnectionListener(OptionMap options, WebSocketClientHandshake handshake, URI newUri, FutureResult ioFuture) { this.options = options; this.handshake = handshake; this.newUri = newUri; this.ioFuture = ioFuture; } @Override public void handleEvent(StreamConnection channel) { WebSocketChannel result = handshake.createChannel(channel, newUri.toString(), bufferPool, options); ioFuture.setResult(result); } } } /** * Creates a new connection builder that can be used to create a web socket connection. * @param worker The XnioWorker to use for the connection * @param bufferPool The buffer pool * @param uri The connection URI * @return The connection builder */ public static ConnectionBuilder connectionBuilder(XnioWorker worker, ByteBufferPool bufferPool, URI uri) { return new ConnectionBuilder(worker, bufferPool, uri); } private WebSocketClient() { } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/client/WebSocketClientHandshake.java000066400000000000000000000043051420065311100334640ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.client; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketVersion; import io.undertow.websockets.extensions.ExtensionHandshake; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import org.xnio.StreamConnection; import org.xnio.http.ExtendedHandshakeChecker; import java.net.URI; import java.util.List; import java.util.Map; import java.util.Set; /** * @author Stuart Douglas */ public abstract class WebSocketClientHandshake { protected final URI url; public static WebSocketClientHandshake create(final WebSocketVersion version, final URI uri) { return create(version, uri, null, null); } public static WebSocketClientHandshake create(final WebSocketVersion version, final URI uri, WebSocketClientNegotiation clientNegotiation, Set extensions) { switch (version) { case V13: return new WebSocket13ClientHandshake(uri, clientNegotiation, extensions); } throw new IllegalArgumentException(); } public WebSocketClientHandshake(final URI url) { this.url = url; } public abstract WebSocketChannel createChannel(final StreamConnection channel, final String wsUri, final ByteBufferPool bufferPool, OptionMap options); public abstract Map createHeaders(); public abstract ExtendedHandshakeChecker handshakeChecker(final URI uri, final Map> requestHeaders); } WebSocketClientNegotiation.java000066400000000000000000000042071420065311100340000ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/client/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.client; import io.undertow.websockets.WebSocketExtension; import java.util.List; import java.util.Map; /** * @author Stuart Douglas */ public class WebSocketClientNegotiation { private final List supportedSubProtocols; private final List supportedExtensions; private volatile String selectedSubProtocol; private volatile List selectedExtensions; public WebSocketClientNegotiation(List supportedSubProtocols, List supportedExtensions) { this.supportedSubProtocols = supportedSubProtocols; this.supportedExtensions = supportedExtensions; } public List getSupportedSubProtocols() { return supportedSubProtocols; } public List getSupportedExtensions() { return supportedExtensions; } public String getSelectedSubProtocol() { return selectedSubProtocol; } public List getSelectedExtensions() { return selectedExtensions; } public void beforeRequest(final Map> headers) { } public void afterRequest(final Map> headers) { } public void handshakeComplete(String selectedProtocol, List selectedExtensions) { this.selectedExtensions = selectedExtensions; this.selectedSubProtocol = selectedProtocol; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/000077500000000000000000000000001420065311100254355ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/AbstractReceiveListener.java000066400000000000000000000206731420065311100330640ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; import org.xnio.ChannelListener; import org.xnio.IoUtils; import org.xnio.Pooled; import java.io.IOException; import java.nio.ByteBuffer; /** * A receive listener that performs a callback when it receives a message * * @author Stuart Douglas */ public abstract class AbstractReceiveListener implements ChannelListener { @Override public void handleEvent(WebSocketChannel channel) { try { final StreamSourceFrameChannel result = channel.receive(); if (result == null) { return; } else if (result.getType() == WebSocketFrameType.BINARY) { onBinary(channel, result); } else if (result.getType() == WebSocketFrameType.TEXT) { onText(channel, result); } else if (result.getType() == WebSocketFrameType.PONG) { onPong(channel, result); } else if (result.getType() == WebSocketFrameType.PING) { onPing(channel, result); } else if (result.getType() == WebSocketFrameType.CLOSE) { onClose(channel, result); } } catch (IOException e) { onError(channel, e); } } protected void onPing(WebSocketChannel webSocketChannel, StreamSourceFrameChannel channel) throws IOException { bufferFullMessage(channel); } protected void onClose(WebSocketChannel webSocketChannel, StreamSourceFrameChannel channel) throws IOException { bufferFullMessage(channel); } protected void onPong(WebSocketChannel webSocketChannel, StreamSourceFrameChannel messageChannel) throws IOException { bufferFullMessage(messageChannel); } protected void onText(WebSocketChannel webSocketChannel, StreamSourceFrameChannel messageChannel) throws IOException { bufferFullMessage(messageChannel); } protected void onBinary(WebSocketChannel webSocketChannel, StreamSourceFrameChannel messageChannel) throws IOException { bufferFullMessage(messageChannel); } protected void onError(WebSocketChannel channel, Throwable error) { IoUtils.safeClose(channel); } /** * Utility method that reads a full text or binary message, including all fragmented parts. Once the full message is * read then the {@link #onFullTextMessage(WebSocketChannel, BufferedTextMessage)} or * {@link #onFullBinaryMessage(WebSocketChannel, BufferedBinaryMessage)} method will be invoked. * * @param messageChannel The message channel */ protected final void bufferFullMessage(StreamSourceFrameChannel messageChannel) { if (messageChannel.getType() == WebSocketFrameType.TEXT) { readBufferedText(messageChannel, new BufferedTextMessage(getMaxTextBufferSize(), true)); } else if (messageChannel.getType() == WebSocketFrameType.BINARY) { readBufferedBinary(messageChannel, false, new BufferedBinaryMessage(getMaxBinaryBufferSize(), true)); } else if (messageChannel.getType() == WebSocketFrameType.PONG) { readBufferedBinary(messageChannel, true, new BufferedBinaryMessage(getMaxPongBufferSize(), true)); } else if (messageChannel.getType() == WebSocketFrameType.PING) { readBufferedBinary(messageChannel, true, new BufferedBinaryMessage(getMaxPingBufferSize(), true)); } else if (messageChannel.getType() == WebSocketFrameType.CLOSE) { readBufferedBinary(messageChannel, true, new BufferedBinaryMessage(getMaxCloseBufferSize(), true)); } } protected long getMaxBinaryBufferSize() { return -1; } protected long getMaxPongBufferSize() { return -1; } protected long getMaxCloseBufferSize() { return -1; } protected long getMaxPingBufferSize() { return -1; } protected long getMaxTextBufferSize() { return -1; } private void readBufferedBinary(final StreamSourceFrameChannel messageChannel, final boolean controlFrame, final BufferedBinaryMessage buffer) { buffer.read(messageChannel, new WebSocketCallback() { @Override public void complete(WebSocketChannel channel, BufferedBinaryMessage context) { try { WebSocketFrameType type = messageChannel.getType(); if (!controlFrame) { onFullBinaryMessage(channel, buffer); } else if (type == WebSocketFrameType.PONG) { onFullPongMessage(channel, buffer); } else if (type == WebSocketFrameType.PING) { onFullPingMessage(channel, buffer); } else if (type == WebSocketFrameType.CLOSE) { onFullCloseMessage(channel, buffer); } } catch (IOException e) { AbstractReceiveListener.this.onError(channel, e); } } @Override public void onError(WebSocketChannel channel, BufferedBinaryMessage context, Throwable throwable) { context.getData().close(); AbstractReceiveListener.this.onError(channel, throwable); } }); } private void readBufferedText(StreamSourceFrameChannel messageChannel, final BufferedTextMessage textMessage) { textMessage.read(messageChannel, new WebSocketCallback() { @Override public void complete(WebSocketChannel channel, BufferedTextMessage context) { try { onFullTextMessage(channel, textMessage); } catch (IOException e) { AbstractReceiveListener.this.onError(channel, e); } } @Override public void onError(WebSocketChannel channel, BufferedTextMessage context, Throwable throwable) { AbstractReceiveListener.this.onError(channel, throwable); } }); } protected void onFullTextMessage(final WebSocketChannel channel, BufferedTextMessage message) throws IOException { } protected void onFullBinaryMessage(final WebSocketChannel channel, BufferedBinaryMessage message) throws IOException { message.getData().free(); } protected void onFullPingMessage(final WebSocketChannel channel, BufferedBinaryMessage message) throws IOException { final Pooled data = message.getData(); WebSockets.sendPong(data.getResource(), channel, new FreeDataCallback(data)); } protected void onFullPongMessage(final WebSocketChannel channel, BufferedBinaryMessage message) throws IOException { message.getData().free(); } protected void onFullCloseMessage(final WebSocketChannel channel, BufferedBinaryMessage message) throws IOException { Pooled data = message.getData(); try { CloseMessage cm = new CloseMessage(data.getResource()); onCloseMessage(cm, channel); if (!channel.isCloseFrameSent()) { WebSockets.sendClose(cm, channel, null); } } finally { data.close(); } } protected void onCloseMessage(CloseMessage cm, WebSocketChannel channel) { } private static class FreeDataCallback implements WebSocketCallback { private final Pooled data; FreeDataCallback(Pooled data) { this.data = data; } @Override public void complete(WebSocketChannel channel, Void context) { data.close(); } @Override public void onError(WebSocketChannel channel, Void context, Throwable throwable) { data.close(); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/BinaryOutputStream.java000066400000000000000000000051751420065311100321310ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; import io.undertow.UndertowMessages; import org.xnio.channels.Channels; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; /** * {@link OutputStream} implementation which buffers all the data until {@link #close()} is called and then will * try to send it in a blocking fashion with the provided {@link StreamSinkFrameChannel}. * * @author Norman Maurer */ public final class BinaryOutputStream extends OutputStream { private final StreamSinkFrameChannel sender; private boolean closed; public BinaryOutputStream(StreamSinkFrameChannel sender) { this.sender = sender; } @Override public void write(byte[] b, int off, int len) throws IOException { checkClosed(); if(Thread.currentThread() == sender.getIoThread()) { throw UndertowMessages.MESSAGES.awaitCalledFromIoThread(); } Channels.writeBlocking(sender, ByteBuffer.wrap(b, off, len)); } @Override public void write(int b) throws IOException { checkClosed(); if(Thread.currentThread() == sender.getIoThread()) { throw UndertowMessages.MESSAGES.awaitCalledFromIoThread(); } Channels.writeBlocking(sender, ByteBuffer.wrap(new byte[]{(byte) b})); } @Override public void flush() throws IOException { checkClosed(); if(Thread.currentThread() == sender.getIoThread()) { throw UndertowMessages.MESSAGES.awaitCalledFromIoThread(); } sender.flush(); } @Override public void close() throws IOException { if (!closed) { closed = true; sender.shutdownWrites(); Channels.flushBlocking(sender); } } private void checkClosed() throws IOException { if (closed) { throw UndertowMessages.MESSAGES.streamIsClosed(); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/BufferedBinaryMessage.java000066400000000000000000000202251420065311100324750ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; import io.undertow.util.ImmediatePooled; import org.xnio.ChannelListener; import io.undertow.connector.PooledByteBuffer; import org.xnio.Pooled; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * A buffered binary message. * * @author Stuart Douglas */ public class BufferedBinaryMessage { private final boolean bufferFullMessage; private List data = new ArrayList<>(1); private PooledByteBuffer current; private final long maxMessageSize; private long currentSize; private boolean complete; // private int frameCount; // was used only in handleNewFrame() which is marked for removal => commenting out public BufferedBinaryMessage(long maxMessageSize, boolean bufferFullMessage) { this.bufferFullMessage = bufferFullMessage; this.maxMessageSize = maxMessageSize; } public BufferedBinaryMessage(boolean bufferFullMessage) { this(-1, bufferFullMessage); } public void readBlocking(StreamSourceFrameChannel channel) throws IOException { if (current == null) { current = channel.getWebSocketChannel().getBufferPool().allocate(); } for (; ; ) { int res = channel.read(current.getBuffer()); if (res == -1) { complete = true; return; } else if (res == 0) { channel.awaitReadable(); } checkMaxSize(channel, res); if (bufferFullMessage) { dealWithFullBuffer(channel); } else if (!current.getBuffer().hasRemaining()) { return; } } } private void dealWithFullBuffer(StreamSourceFrameChannel channel) { if (!current.getBuffer().hasRemaining()) { current.getBuffer().flip(); data.add(current); current = channel.getWebSocketChannel().getBufferPool().allocate(); } } public void read(final StreamSourceFrameChannel channel, final WebSocketCallback callback) { try { for (; ; ) { if (current == null) { current = channel.getWebSocketChannel().getBufferPool().allocate(); } int res = channel.read(current.getBuffer()); if (res == -1) { this.complete = true; callback.complete(channel.getWebSocketChannel(), this); return; } else if (res == 0) { if(!bufferFullMessage) { callback.complete(channel.getWebSocketChannel(), BufferedBinaryMessage.this); } channel.getReadSetter().set(new ChannelListener() { @Override public void handleEvent(StreamSourceFrameChannel channel) { if(complete ) { return; } try { for (; ; ) { if (current == null) { current = channel.getWebSocketChannel().getBufferPool().allocate(); } int res = channel.read(current.getBuffer()); if (res == -1) { complete = true; channel.suspendReads(); callback.complete(channel.getWebSocketChannel(), BufferedBinaryMessage.this); return; } else if (res == 0) { return; } checkMaxSize(channel, res); if (bufferFullMessage) { dealWithFullBuffer(channel); } else if (!current.getBuffer().hasRemaining()) { callback.complete(channel.getWebSocketChannel(), BufferedBinaryMessage.this); } } } catch (IOException e) { channel.suspendReads(); callback.onError(channel.getWebSocketChannel(), BufferedBinaryMessage.this, e); } } }); channel.resumeReads(); return; } checkMaxSize(channel, res); if (bufferFullMessage) { dealWithFullBuffer(channel); } else if (!current.getBuffer().hasRemaining()) { callback.complete(channel.getWebSocketChannel(), BufferedBinaryMessage.this); } } } catch (IOException e) { callback.onError(channel.getWebSocketChannel(), this, e); } } private void checkMaxSize(StreamSourceFrameChannel channel, int res) throws IOException { currentSize += res; if (maxMessageSize > 0 && currentSize > maxMessageSize) { getData().free(); WebSockets.sendClose(new CloseMessage(CloseMessage.MSG_TOO_BIG, WebSocketMessages.MESSAGES.messageToBig(maxMessageSize)), channel.getWebSocketChannel(), null); throw new IOException(WebSocketMessages.MESSAGES.messageToBig(maxMessageSize)); } } public Pooled getData() { if (current == null) { return new ImmediatePooled<>(new ByteBuffer[0]); } if (data.isEmpty()) { final PooledByteBuffer current = this.current; current.getBuffer().flip(); this.current = null; final ByteBuffer[] data = new ByteBuffer[]{current.getBuffer()}; return new PooledByteBufferArray(Collections.singletonList(current), data); } current.getBuffer().flip(); data.add(current); current = null; ByteBuffer[] ret = new ByteBuffer[data.size()]; for (int i = 0; i < data.size(); ++i) { ret[i] = data.get(i).getBuffer(); } List data = this.data; this.data = new ArrayList<>(); return new PooledByteBufferArray(data, ret); } public boolean isComplete() { return complete; } private static final class PooledByteBufferArray implements Pooled { private final List pooled; private final ByteBuffer[] data; private PooledByteBufferArray(List pooled, ByteBuffer[] data) { this.pooled = pooled; this.data = data; } @Override public void discard() { for (PooledByteBuffer item : pooled) { item.close(); } } @Override public void free() { for (PooledByteBuffer item : pooled) { item.close(); } } @Override public ByteBuffer[] getResource() throws IllegalStateException { return data; } @Override public void close() { free(); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/BufferedTextMessage.java000066400000000000000000000201471420065311100322000ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; import org.xnio.ChannelListener; import io.undertow.connector.PooledByteBuffer; import java.io.IOException; import java.nio.ByteBuffer; /** * A buffered text message. * * @author Stuart Douglas */ public class BufferedTextMessage { private final UTF8Output data = new UTF8Output(); private final boolean bufferFullMessage; private final long maxMessageSize; private boolean complete; long currentSize; /** * @param maxMessageSize The maximum message size * @param bufferFullMessage If the complete message should be buffered */ public BufferedTextMessage(long maxMessageSize, boolean bufferFullMessage) { this.maxMessageSize = maxMessageSize; this.bufferFullMessage = bufferFullMessage; } public BufferedTextMessage(boolean bufferFullMessage) { this(-1, bufferFullMessage); } private void checkMaxSize(StreamSourceFrameChannel channel, int res) throws IOException { if(res > 0) { currentSize += res; } if (maxMessageSize > 0 && currentSize > maxMessageSize) { WebSockets.sendClose(new CloseMessage(CloseMessage.MSG_TOO_BIG, WebSocketMessages.MESSAGES.messageToBig(maxMessageSize)), channel.getWebSocketChannel(), null); throw new IOException(WebSocketMessages.MESSAGES.messageToBig(maxMessageSize)); } } public void readBlocking(StreamSourceFrameChannel channel) throws IOException { PooledByteBuffer pooled = channel.getWebSocketChannel().getBufferPool().allocate(); final ByteBuffer buffer = pooled.getBuffer(); try { for (; ; ) { int res = channel.read(buffer); if (res == -1) { buffer.flip(); data.write(buffer); this.complete = true; return; } else if (res == 0) { channel.awaitReadable(); } checkMaxSize(channel, res); if (!buffer.hasRemaining()) { buffer.flip(); data.write(buffer); buffer.compact(); if (!bufferFullMessage) { //if we are not reading the full message we return return; } } } } finally { pooled.close(); } } public void read(final StreamSourceFrameChannel channel, final WebSocketCallback callback) { PooledByteBuffer pooled = channel.getWebSocketChannel().getBufferPool().allocate(); final ByteBuffer buffer = pooled.getBuffer(); try { try { for (; ; ) { int res = channel.read(buffer); if (res == -1) { this.complete = true; buffer.flip(); data.write(buffer); callback.complete(channel.getWebSocketChannel(), this); return; } else if (res == 0) { buffer.flip(); if (buffer.hasRemaining()) { data.write(buffer); if (!bufferFullMessage) { callback.complete(channel.getWebSocketChannel(), this); } } channel.getReadSetter().set(new ChannelListener() { @Override public void handleEvent(StreamSourceFrameChannel channel) { if(complete ) { return; } PooledByteBuffer pooled = channel.getWebSocketChannel().getBufferPool().allocate(); final ByteBuffer buffer = pooled.getBuffer(); try { try { for (; ; ) { int res = channel.read(buffer); if (res == -1) { checkMaxSize(channel, res); buffer.flip(); data.write(buffer); complete = true; callback.complete(channel.getWebSocketChannel(), BufferedTextMessage.this); return; } else if (res == 0) { buffer.flip(); if (buffer.hasRemaining()) { data.write(buffer); if (!bufferFullMessage) { callback.complete(channel.getWebSocketChannel(), BufferedTextMessage.this); } } return; } if (!buffer.hasRemaining()) { buffer.flip(); data.write(buffer); buffer.clear(); if (!bufferFullMessage) { callback.complete(channel.getWebSocketChannel(), BufferedTextMessage.this); } } } } catch (IOException e) { callback.onError(channel.getWebSocketChannel(), BufferedTextMessage.this, e); } } finally { pooled.close(); } } }); channel.resumeReads(); return; } checkMaxSize(channel, res); if (!buffer.hasRemaining()) { buffer.flip(); data.write(buffer); buffer.clear(); if (!bufferFullMessage) { callback.complete(channel.getWebSocketChannel(), this); } } } } catch (IOException e) { callback.onError(channel.getWebSocketChannel(), this, e); } } finally { pooled.close(); } } /** * Gets the buffered data and clears the buffered text message. If this is not called on a UTF8 * character boundary there may be partial code point data that is still buffered. * * @return The data */ public String getData() { return data.extract(); } public boolean isComplete() { return complete; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/CloseMessage.java000066400000000000000000000054661420065311100306650ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; /** * A close message * * @author Stuart Douglas */ public class CloseMessage { private final int code; private final String reason; /* * For the exact meaning of the codes refer to the WebSocket * RFC Section 7.4. */ public static final int NORMAL_CLOSURE = 1000; public static final int GOING_AWAY = 1001; public static final int WRONG_CODE = 1002; public static final int PROTOCOL_ERROR = 1003; public static final int MSG_CONTAINS_INVALID_DATA = 1007; public static final int MSG_VIOLATES_POLICY = 1008; public static final int MSG_TOO_BIG = 1009; public static final int MISSING_EXTENSIONS = 1010; public static final int UNEXPECTED_ERROR = 1011; public CloseMessage(final ByteBuffer buffer) { if(buffer.remaining() >= 2) { code = (buffer.get() & 0XFF) << 8 | (buffer.get() & 0xFF); reason = new UTF8Output(buffer).extract(); } else { code = NORMAL_CLOSURE; reason = ""; } } public CloseMessage(int code, String reason) { this.code = code; this.reason = reason == null ? "" : reason; } public CloseMessage(final ByteBuffer[] buffers) { this(WebSockets.mergeBuffers(buffers)); } public String getReason() { return reason; } public int getCode() { return code; } public ByteBuffer toByteBuffer() { byte[] data = reason.getBytes(StandardCharsets.UTF_8); ByteBuffer buffer = ByteBuffer.allocate(data.length + 2); buffer.putShort((short) code); buffer.put(data); buffer.flip(); return buffer; } /** * Return {@code true} if the provided code is a valid close status code. */ public static boolean isValid(int code) { if (code >= 0 && code <= 999 || code >= 1004 && code <= 1006 || code >= 1012 && code <= 2999) { return false; } return true; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/InvalidOpCodeException.java000066400000000000000000000022011420065311100326320ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; /** * @author Stuart Douglas */ public class InvalidOpCodeException extends WebSocketException { public InvalidOpCodeException() { } public InvalidOpCodeException(String msg, Throwable cause) { super(msg, cause); } public InvalidOpCodeException(String msg) { super(msg); } public InvalidOpCodeException(Throwable cause) { super(cause); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/SendChannel.java000066400000000000000000000016071420065311100304660ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; /** * Marker interface for channels that send frames. * * @author Norman Maurer */ interface SendChannel { } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/StreamSinkFrameChannel.java000066400000000000000000000052301420065311100326240ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; import io.undertow.server.protocol.framed.AbstractFramedStreamSinkChannel; /** * @author Norman Maurer */ public abstract class StreamSinkFrameChannel extends AbstractFramedStreamSinkChannel { private final WebSocketFrameType type; private int rsv; protected StreamSinkFrameChannel(WebSocketChannel channel, WebSocketFrameType type) { super(channel); this.type = type; } /** * Return the RSV for the extension. Default is 0. */ public int getRsv() { return rsv; } /** * Set the RSV which is used for extensions. *

* This can only be set before any write or transfer operations where passed * to the wrapped {@link org.xnio.channels.StreamSinkChannel}, after that an {@link IllegalStateException} will be thrown. * */ public void setRsv(int rsv) { if (!areExtensionsSupported() && rsv != 0) { throw WebSocketMessages.MESSAGES.extensionsNotSupported(); } this.rsv = rsv; } /** * {@code true} if fragmentation is supported for the {@link WebSocketFrameType}. */ public boolean isFragmentationSupported() { return false; } /** * {@code true} if extensions are supported for the {@link WebSocketFrameType}. */ public boolean areExtensionsSupported() { return false; } /** * Return the {@link WebSocketFrameType} for which the {@link StreamSinkFrameChannel} was obtained. */ public WebSocketFrameType getType() { return type; } public WebSocketChannel getWebSocketChannel() { return getChannel(); } @Override protected boolean isLastFrame() { return type == WebSocketFrameType.CLOSE; } public boolean isFinalFragment() { return super.isFinalFrameQueued(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/StreamSourceFrameChannel.java000066400000000000000000000215521420065311100331650ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import io.undertow.connector.PooledByteBuffer; import io.undertow.websockets.core.function.ChannelFunction; import io.undertow.websockets.core.function.ChannelFunctionFileChannel; import io.undertow.websockets.core.protocol.version07.Masker; import io.undertow.websockets.core.protocol.version07.UTF8Checker; import io.undertow.websockets.extensions.ExtensionFunction; import io.undertow.websockets.extensions.NoopExtensionFunction; import org.xnio.channels.StreamSinkChannel; import io.undertow.server.protocol.framed.AbstractFramedStreamSourceChannel; import io.undertow.server.protocol.framed.FrameHeaderData; /** * Base class for processes Frame bases StreamSourceChannels. * * @author Norman Maurer */ public abstract class StreamSourceFrameChannel extends AbstractFramedStreamSourceChannel { protected final WebSocketFrameType type; private boolean finalFragment; private final int rsv; private final ChannelFunction[] functions; private final ExtensionFunction extensionFunction; private Masker masker; private UTF8Checker checker; protected StreamSourceFrameChannel(WebSocketChannel wsChannel, WebSocketFrameType type, PooledByteBuffer pooled, long frameLength) { this(wsChannel, type, 0, true, pooled, frameLength, null); } protected StreamSourceFrameChannel(WebSocketChannel wsChannel, WebSocketFrameType type, int rsv, boolean finalFragment, PooledByteBuffer pooled, long frameLength, Masker masker, ChannelFunction... functions) { super(wsChannel, pooled, frameLength); this.type = type; this.finalFragment = finalFragment; this.rsv = rsv; this.functions = functions; this.masker = masker; checker = null; for (ChannelFunction func : functions) { if (func instanceof UTF8Checker) { checker = (UTF8Checker) func; } } if (rsv > 0) { this.extensionFunction = wsChannel.getExtensionFunction(); } else { this.extensionFunction = NoopExtensionFunction.INSTANCE; } } /** * Return the {@link WebSocketFrameType} or {@code null} if its not known at the calling time. */ public WebSocketFrameType getType() { return type; } /** * Flag to indicate if this frame is the final fragment in a message. The first fragment (frame) may also be the * final fragment. */ public boolean isFinalFragment() { return finalFragment; } /** * Return the rsv which is used for extensions. */ public int getRsv() { return rsv; } int getWebSocketFrameCount() { return getReadFrameCount(); } @Override protected WebSocketChannel getFramedChannel() { return super.getFramedChannel(); } public WebSocketChannel getWebSocketChannel() { return getFramedChannel(); } public void finalFrame() { this.lastFrame(); this.finalFragment = true; } @Override protected void handleHeaderData(FrameHeaderData headerData) { super.handleHeaderData(headerData); if (((WebSocketFrame) headerData).isFinalFragment()) { finalFrame(); } if(masker != null) { masker.newFrame(headerData); } if(functions != null) { for(ChannelFunction func : functions) { func.newFrame(headerData); } } } @Override public final long transferTo(long position, long count, FileChannel target) throws IOException { long r; if (functions != null && functions.length > 0) { r = super.transferTo(position, count, new ChannelFunctionFileChannel(target, functions)); } else { r = super.transferTo(position, count, target); } return r; } @Override public final long transferTo(long count, ByteBuffer throughBuffer, StreamSinkChannel target) throws IOException { // use this because of XNIO bug // See https://issues.jboss.org/browse/XNIO-185 return WebSocketUtils.transfer(this, count, throughBuffer, target); } @Override public int read(ByteBuffer dst) throws IOException { int position = dst.position(); int r = super.read(dst); if (r > 0) { checker(dst, position, dst.position() - position, false); } else if(r == -1) { checkComplete(); } return r; } @Override public final long read(ByteBuffer[] dsts) throws IOException { return read(dsts, 0, dsts.length); } @Override public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { Bounds[] old = new Bounds[length]; for (int i = offset; i < length; i++) { ByteBuffer dst = dsts[i]; old[i - offset] = new Bounds(dst.position(), dst.limit()); } long b = super.read(dsts, offset, length); if (b > 0) { for (int i = offset; i < length; i++) { ByteBuffer dst = dsts[i]; int oldPos = old[i - offset].position; afterRead(dst, oldPos, dst.position() - oldPos); } } else if(b == -1){ checkComplete(); } return b; } private void checkComplete() throws IOException { try { for (ChannelFunction func : functions) { func.complete(); } } catch (UnsupportedEncodingException e) { getFramedChannel().markReadsBroken(e); throw e; } } /** * Called after data was read into the {@link ByteBuffer} * * @param buffer the {@link ByteBuffer} into which the data was read * @param position the position it was written to * @param length the number of bytes there were written * @throws IOException thrown if an error occurs */ protected void afterRead(ByteBuffer buffer, int position, int length) throws IOException { try { for (ChannelFunction func : functions) { func.afterRead(buffer, position, length); } if (isComplete()) { checkComplete(); } } catch (UnsupportedEncodingException e) { getFramedChannel().markReadsBroken(e); throw e; } } protected void checker(ByteBuffer buffer, int position, int length, boolean complete) throws IOException { if (checker == null) { return; } try { checker.afterRead(buffer, position, length); if (complete) { try { checker.complete(); } catch (UnsupportedEncodingException e) { getFramedChannel().markReadsBroken(e); throw e; } } } catch (UnsupportedEncodingException e) { getFramedChannel().markReadsBroken(e); throw e; } } @Override protected PooledByteBuffer processFrameData(PooledByteBuffer frameData, boolean lastFragmentOfFrame) throws IOException { if(masker != null) { masker.afterRead(frameData.getBuffer(), frameData.getBuffer().position(), frameData.getBuffer().remaining()); } try { return extensionFunction.transformForRead(frameData, this, lastFragmentOfFrame && isFinalFragment()); } catch (IOException e) { getWebSocketChannel().markReadsBroken(new WebSocketFrameCorruptedException(e)); throw e; } catch (Exception e) { getWebSocketChannel().markReadsBroken(new WebSocketFrameCorruptedException(e)); throw new IOException(e); } } private static class Bounds { final int position; final int limit; Bounds(int position, int limit) { this.position = position; this.limit = limit; } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/UTF8Output.java000066400000000000000000000074221420065311100302540ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; import org.xnio.Buffers; import java.nio.ByteBuffer; /** * Utility class which allows to extract a UTF8 String from bytes respecting valid code-points */ public final class UTF8Output { private static final int UTF8_ACCEPT = 0; private static final byte HIGH_BIT = (byte) (1 << 7); private static final byte[] TYPES = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}; private static final byte[] STATES = {0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12}; @SuppressWarnings("RedundantFieldInitialization") private byte state = UTF8_ACCEPT; private int codep; private final StringBuilder stringBuilder; public UTF8Output(ByteBuffer... payload) { stringBuilder = new StringBuilder((int) Buffers.remaining(payload)); write(payload); } public UTF8Output() { stringBuilder = new StringBuilder(); } public void write(ByteBuffer... bytes) { for (ByteBuffer buf : bytes) { while (buf.hasRemaining()) { write(buf.get()); } } } private void write(byte b) { if(state == UTF8_ACCEPT && (b & HIGH_BIT) == 0) { stringBuilder.append((char)b); return; } byte type = TYPES[b & 0xFF]; codep = state != UTF8_ACCEPT ? b & 0x3f | codep << 6 : 0xff >> type & b; state = STATES[state + type]; if (state == UTF8_ACCEPT) { for (char c : Character.toChars(codep)) { stringBuilder.append(c); } } } /** * Extract a String holding the utf8 text */ public String extract() { String text = stringBuilder.toString(); stringBuilder.setLength(0); return text; } public boolean hasData() { return stringBuilder.length() != 0; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/WebSocketCallback.java000066400000000000000000000017161420065311100316100ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; /** * @author Stuart Douglas */ public interface WebSocketCallback { void complete(final WebSocketChannel channel, T context); void onError(final WebSocketChannel channel, T context, Throwable throwable); } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/WebSocketChannel.java000066400000000000000000000372251420065311100314700ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; import io.undertow.conduits.IdleTimeoutConduit; import io.undertow.server.protocol.framed.AbstractFramedChannel; import io.undertow.server.protocol.framed.AbstractFramedStreamSourceChannel; import io.undertow.server.protocol.framed.FrameHeaderData; import io.undertow.websockets.extensions.ExtensionFunction; import org.xnio.ChannelExceptionHandler; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import org.xnio.StreamConnection; import org.xnio.channels.StreamSinkChannel; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * A {@link org.xnio.channels.ConnectedChannel} which can be used to send and receive WebSocket Frames. * * @author Norman Maurer * @author Stuart Douglas */ public abstract class WebSocketChannel extends AbstractFramedChannel { private final boolean client; private final WebSocketVersion version; private final String wsUrl; private volatile boolean closeFrameReceived; private volatile boolean closeFrameSent; /** * If this is true then the web socket close was initiated by the remote peer */ private volatile boolean closeInitiatedByRemotePeer; private volatile int closeCode = -1; private volatile String closeReason; private final String subProtocol; protected final boolean extensionsSupported; protected final ExtensionFunction extensionFunction; protected final boolean hasReservedOpCode; /** * an incoming frame that has not been created yet */ private volatile PartialFrame partialFrame; private final Map attributes = Collections.synchronizedMap(new HashMap()); protected StreamSourceFrameChannel fragmentedChannel; /** * Represents all web socket channels that are attached to the same endpoint. */ private final Set peerConnections; /** * Create a new {@link WebSocketChannel} * 8 * * @param connectedStreamChannel The {@link org.xnio.channels.ConnectedStreamChannel} over which the WebSocket Frames should get send and received. * Be aware that it already must be "upgraded". * @param bufferPool The {@link org.xnio.Pool} which will be used to acquire {@link java.nio.ByteBuffer}'s from. * @param version The {@link WebSocketVersion} of the {@link WebSocketChannel} * @param wsUrl The url for which the channel was created. * @param client * @param peerConnections The concurrent set that is used to track open connections associtated with an endpoint */ protected WebSocketChannel(final StreamConnection connectedStreamChannel, ByteBufferPool bufferPool, WebSocketVersion version, String wsUrl, String subProtocol, final boolean client, boolean extensionsSupported, final ExtensionFunction extensionFunction, Set peerConnections, OptionMap options) { super(connectedStreamChannel, bufferPool, new WebSocketFramePriority(), null, options); this.client = client; this.version = version; this.wsUrl = wsUrl; this.extensionsSupported = extensionsSupported; this.extensionFunction = extensionFunction; this.hasReservedOpCode = extensionFunction.hasExtensionOpCode(); this.subProtocol = subProtocol; this.peerConnections = peerConnections; addCloseTask(new ChannelListener() { @Override public void handleEvent(WebSocketChannel channel) { extensionFunction.dispose(); WebSocketChannel.this.peerConnections.remove(WebSocketChannel.this); } }); } @Override protected Collection> getReceivers() { if(fragmentedChannel == null) { return Collections.emptyList(); } return Collections.>singleton(fragmentedChannel); } @Override protected IdleTimeoutConduit createIdleTimeoutChannel(final StreamConnection connectedStreamChannel) { return new IdleTimeoutConduit(connectedStreamChannel) { @Override protected void doClose() { WebSockets.sendClose(CloseMessage.GOING_AWAY, null, WebSocketChannel.this, null); } }; } @Override protected boolean isLastFrameSent() { return closeFrameSent; } @Override protected boolean isLastFrameReceived() { return closeFrameReceived; } @Override protected void markReadsBroken(Throwable cause) { super.markReadsBroken(cause); } @Override protected void lastDataRead() { if(!closeFrameReceived && !closeFrameSent) { //the peer has likely already gone away, but try and send a close frame anyway //this will likely just result in the write() failing an immediate connection termination //which is what we want closeFrameReceived = true; //not strictly true, but the read side is gone try { sendClose(); } catch (IOException e) { IoUtils.safeClose(this); } } } protected boolean isReadsBroken() { return super.isReadsBroken(); } @Override protected FrameHeaderData parseFrame(ByteBuffer data) throws IOException { if (partialFrame == null) { partialFrame = receiveFrame(); } try { partialFrame.handle(data); } catch (WebSocketException e) { //the data was corrupt //send a close message WebSockets.sendClose(new CloseMessage(CloseMessage.WRONG_CODE, e.getMessage()).toByteBuffer(), this, null); markReadsBroken(e); if (WebSocketLogger.REQUEST_LOGGER.isDebugEnabled()) { WebSocketLogger.REQUEST_LOGGER.debugf(e, "receive failed due to Exception"); } throw new IOException(e); } if (partialFrame.isDone()) { PartialFrame p = this.partialFrame; this.partialFrame = null; return p; } return null; } /** * Create a new {@link io.undertow.websockets.core.StreamSourceFrameChannel} which can be used to read the data of the received Frame * * @return channel A {@link io.undertow.websockets.core.StreamSourceFrameChannel} will be used to read a Frame from. * This will return {@code null} if the right {@link io.undertow.websockets.core.StreamSourceFrameChannel} could not be detected with the given * buffer and so more data is needed. */ protected abstract PartialFrame receiveFrame(); @Override protected StreamSourceFrameChannel createChannel(FrameHeaderData frameHeaderData, PooledByteBuffer frameData) { PartialFrame partialFrame = (PartialFrame) frameHeaderData; StreamSourceFrameChannel channel = partialFrame.getChannel(frameData); if (channel.getType() == WebSocketFrameType.CLOSE) { if(!closeFrameSent) { closeInitiatedByRemotePeer = true; } closeFrameReceived = true; } return channel; } public final boolean setAttribute(String key, Object value) { if (value == null) { return attributes.remove(key) != null; } else { return attributes.put(key, value) == null; } } public final Object getAttribute(String key) { return attributes.get(key); } /** * Returns {@code true} if extensions are supported by this WebSocket Channel. */ public boolean areExtensionsSupported() { return extensionsSupported; } @Override protected void handleBrokenSourceChannel(Throwable e) { if (e instanceof UnsupportedEncodingException) { getFramePriority().immediateCloseFrame(); WebSockets.sendClose(new CloseMessage(CloseMessage.MSG_CONTAINS_INVALID_DATA, e.getMessage()).toByteBuffer(), this, null); } else if (e instanceof WebSocketInvalidCloseCodeException) { WebSockets.sendClose(new CloseMessage(CloseMessage.WRONG_CODE, e.getMessage()).toByteBuffer(), this, null); } else if (e instanceof WebSocketFrameCorruptedException) { getFramePriority().immediateCloseFrame(); WebSockets.sendClose(new CloseMessage(CloseMessage.WRONG_CODE, e.getMessage()).toByteBuffer(), this, null); } } @Override protected void handleBrokenSinkChannel(Throwable e) { } /** * Returns an unmodifiable {@link Set} of the selected subprotocols if any. */ @Deprecated public Set getSubProtocols() { return Collections.singleton(subProtocol); } public String getSubProtocol() { return subProtocol; } public boolean isCloseFrameReceived() { return closeFrameReceived; } public boolean isCloseFrameSent() { return closeFrameSent; } /** * Get the request URI scheme. Normally this is one of {@code ws} or {@code wss}. * * @return the request URI scheme */ public String getRequestScheme() { if (getUrl().startsWith("wss:")) { return "wss"; } else { return "ws"; } } /** * Return {@code true} if this is handled via WebSocket Secure. */ public boolean isSecure() { return "wss".equals(getRequestScheme()); } /** * Return the URL of the WebSocket endpoint. * * @return url The URL of the endpoint */ public String getUrl() { return wsUrl; } /** * Return the {@link WebSocketVersion} which is used * * @return version The {@link WebSocketVersion} which is in use */ public WebSocketVersion getVersion() { return version; } /** * Get the source address of the WebSocket Channel. * * @return the source address of the WebSocket Channel */ public InetSocketAddress getSourceAddress() { return getPeerAddress(InetSocketAddress.class); } /** * Get the destination address of the WebSocket Channel. * * @return the destination address of the WebSocket Channel */ public InetSocketAddress getDestinationAddress() { return getLocalAddress(InetSocketAddress.class); } public boolean isClient() { return client; } /** * Returns a new {@link StreamSinkFrameChannel} for sending the given {@link WebSocketFrameType} with the given payload. * If this method is called multiple times, subsequent {@link StreamSinkFrameChannel}'s will not be writable until all previous frames * were completely written. * * @param type The {@link WebSocketFrameType} for which a {@link StreamSinkChannel} should be created */ public final StreamSinkFrameChannel send(WebSocketFrameType type) throws IOException { if(closeFrameSent || (closeFrameReceived && type != WebSocketFrameType.CLOSE)) { throw WebSocketMessages.MESSAGES.channelClosed(); } if (isWritesBroken()) { throw WebSocketMessages.MESSAGES.streamIsBroken(); } StreamSinkFrameChannel ch = createStreamSinkChannel(type); getFramePriority().addToOrderQueue(ch); if (type == WebSocketFrameType.CLOSE) { closeFrameSent = true; } return ch; } /** * Send a Close frame without a payload */ public void sendClose() throws IOException { closeReason = ""; closeCode = CloseMessage.NORMAL_CLOSURE; StreamSinkFrameChannel closeChannel = send(WebSocketFrameType.CLOSE); closeChannel.shutdownWrites(); if (!closeChannel.flush()) { closeChannel.getWriteSetter().set(ChannelListeners.flushingChannelListener( null, new ChannelExceptionHandler() { @Override public void handleException(final StreamSinkChannel channel, final IOException exception) { IoUtils.safeClose(WebSocketChannel.this); } } )); closeChannel.resumeWrites(); } } /** * Create a new StreamSinkFrameChannel which can be used to send a WebSocket Frame of the type {@link WebSocketFrameType}. * * @param type The {@link WebSocketFrameType} of the WebSocketFrame which will be send over this {@link StreamSinkFrameChannel} */ protected abstract StreamSinkFrameChannel createStreamSinkChannel(WebSocketFrameType type); protected WebSocketFramePriority getFramePriority() { return (WebSocketFramePriority) super.getFramePriority(); } /** * Returns all 'peer' web socket connections that were created from the same endpoint. * * * @return all 'peer' web socket connections */ public Set getPeerConnections() { return Collections.unmodifiableSet(peerConnections); } /** * If this is true the session is being closed because the remote peer sent a close frame * @return true if the remote peer closed the connection */ public boolean isCloseInitiatedByRemotePeer() { return closeInitiatedByRemotePeer; } /** * Interface that represents a frame channel that is in the process of being created */ public interface PartialFrame extends FrameHeaderData { /** * @return The channel, or null if the channel is not available yet */ StreamSourceFrameChannel getChannel(final PooledByteBuffer data); /** * Handles the data, any remaining data will be pushed back */ void handle(ByteBuffer data) throws WebSocketException; /** * @return true if the channel is available */ boolean isDone(); } /** * * @return The close reason */ public String getCloseReason() { return closeReason; } public void setCloseReason(String closeReason) { this.closeReason = closeReason; } /** * * @return The close code */ public int getCloseCode() { return closeCode; } public void setCloseCode(int closeCode) { this.closeCode = closeCode; } public ExtensionFunction getExtensionFunction() { return extensionFunction; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/WebSocketException.java000066400000000000000000000024411420065311100320460ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; import java.io.IOException; /** * Base class for all WebSocket Exceptions * * @author Norman Maurer */ public class WebSocketException extends IOException { private static final long serialVersionUID = -6784834646314672530L; public WebSocketException() { } public WebSocketException(String msg, Throwable cause) { super(msg, cause); } public WebSocketException(String msg) { super(msg); } public WebSocketException(Throwable cause) { super(cause); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/WebSocketFrame.java000066400000000000000000000015741420065311100311500ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; /** * @author Stuart Douglas */ public interface WebSocketFrame extends WebSocketChannel.PartialFrame { boolean isFinalFragment(); } WebSocketFrameCorruptedException.java000066400000000000000000000025621420065311100346360ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; /** * WebSocketException which will be thrown if a corrupted frame was detected * * @author Norman Maurer */ public class WebSocketFrameCorruptedException extends WebSocketException { private static final long serialVersionUID = -6784834646314476130L; public WebSocketFrameCorruptedException() { } public WebSocketFrameCorruptedException(String msg, Throwable cause) { super(msg, cause); } public WebSocketFrameCorruptedException(String msg) { super(msg); } public WebSocketFrameCorruptedException(Throwable cause) { super(cause); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/WebSocketFramePriority.java000066400000000000000000000145401420065311100327070ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; import io.undertow.server.protocol.framed.FramePriority; import java.util.Deque; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedDeque; /** * Web socket frame priority * * @author Stuart Douglas */ public class WebSocketFramePriority implements FramePriority { /** * Strict ordering queue. Makes sure that the initial frame for a stream is sent in the order that send() is called. *

* Required to pass the autobahn test suite with no non-strict performance. *

* TODO: provide a way to disable this. */ private final Queue strictOrderQueue = new ConcurrentLinkedDeque<>(); private StreamSinkFrameChannel currentFragmentedSender; boolean closed = false; boolean immediateCloseFrame = false; @Override public boolean insertFrame(StreamSinkFrameChannel newFrame, List pendingFrames) { if (newFrame.getType() != WebSocketFrameType.PONG && newFrame.getType() != WebSocketFrameType.PING) { StreamSinkFrameChannel order = strictOrderQueue.peek(); if (order != null) { if (order != newFrame && order.isOpen()) { //generally we want to queue close frames immediately //however if the close frame is initiated from this side we respect the ordering //if the close frame is from the other side we have to echo it back immediately if (newFrame.getType() != WebSocketFrameType.CLOSE) { return false; } else if (!newFrame.getWebSocketChannel().isCloseFrameReceived() && !immediateCloseFrame) { return false; } } if(order == newFrame && newFrame.isFinalFragment()) { strictOrderQueue.poll(); } } } if (closed) { //drop the frame newFrame.markBroken(); return true; } if (currentFragmentedSender == null) { //we are not sending fragmented if (!newFrame.isWritesShutdown()) { //start of a fragmented message currentFragmentedSender = newFrame; } if (pendingFrames.isEmpty()) { pendingFrames.add(newFrame); } else if (newFrame.getType() == WebSocketFrameType.PING || newFrame.getType() == WebSocketFrameType.PONG) { //add at the start of the queue int index = 1; //index = 1 because the very first frame may be half written out already boolean done = false; //insert before the first frame that is not a ping or pong while (index < pendingFrames.size()) { WebSocketFrameType type = pendingFrames.get(index).getType(); if(type != WebSocketFrameType.PING && type != WebSocketFrameType.PONG) { pendingFrames.add(index, newFrame); done = true; break; } index++; } if(!done) { pendingFrames.add(newFrame); } } else { pendingFrames.add(newFrame); } } else if (newFrame.getType() == WebSocketFrameType.PING || newFrame.getType() == WebSocketFrameType.PONG) { //we stick ping and pong in the middle of fragmentation if (pendingFrames.isEmpty()) { pendingFrames.add(newFrame); } else { pendingFrames.add(1, newFrame); } } else { //we are currently sending fragmented, we can't queue and non control messages if (currentFragmentedSender != newFrame) { return false; } else { if (newFrame.isFinalFragment()) { currentFragmentedSender = null; } pendingFrames.add(newFrame); } } if (newFrame.getType() == WebSocketFrameType.CLOSE) { closed = true; } return true; } @Override public void frameAdded(StreamSinkFrameChannel addedFrame, List pendingFrames, Deque holdFrames) { if (addedFrame.isFinalFragment()) { while (true) { StreamSinkFrameChannel frame = strictOrderQueue.peek(); if(frame == null) { break; } if(holdFrames.contains(frame)) { if(insertFrame(frame, pendingFrames)) { holdFrames.remove(frame); } else { break; } } else { break; } } while (!holdFrames.isEmpty()) { StreamSinkFrameChannel frame = holdFrames.peek(); if (insertFrame(frame, pendingFrames)) { holdFrames.poll(); } else { return; } } } } void addToOrderQueue(final StreamSinkFrameChannel channel) { if (channel.getType() != WebSocketFrameType.PING && channel.getType() != WebSocketFrameType.PONG) { strictOrderQueue.add(channel); } } void immediateCloseFrame() { this.immediateCloseFrame = true; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/WebSocketFrameType.java000066400000000000000000000027601420065311100320100ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; /** * The different WebSocketFrame types which are out there. * * @author Norman Maurer */ public enum WebSocketFrameType { /** * WebSocketFrame contains binary data */ BINARY, /** * WebSocketFrame contains UTF-8 encoded {@link String} */ TEXT, /** * WebSocketFrame which represent a ping request */ PING, /** * WebSocketFrame which should be issued after a {@link #PING} was received */ PONG, /** * WebSocketFrame which requests the close of the WebSockets connection */ CLOSE, /** * WebSocketFrame which notify about more data to come */ CONTINUATION, /** * Unknown frame-type */ UNKOWN, } WebSocketHandshakeException.java000066400000000000000000000025351420065311100336020ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; /** * {@link WebSocketException} which should be used during the WebSocket-Handshake processing. * * @author Norman Maurer */ public class WebSocketHandshakeException extends WebSocketException { private static final long serialVersionUID = 1L; public WebSocketHandshakeException() { } public WebSocketHandshakeException(String s) { super(s); } public WebSocketHandshakeException(String s, Throwable throwable) { super(s, throwable); } public WebSocketHandshakeException(final Throwable cause) { super(cause); } } WebSocketInvalidCloseCodeException.java000066400000000000000000000025741420065311100350660ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; /** * WebSocketException which will be thrown if a corrupted frame was detected * * @author Norman Maurer */ public class WebSocketInvalidCloseCodeException extends WebSocketException { private static final long serialVersionUID = -6784834646314476130L; public WebSocketInvalidCloseCodeException() { } public WebSocketInvalidCloseCodeException(String msg, Throwable cause) { super(msg, cause); } public WebSocketInvalidCloseCodeException(String msg) { super(msg); } public WebSocketInvalidCloseCodeException(Throwable cause) { super(cause); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/WebSocketLogger.java000066400000000000000000000061761420065311100313400ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; import io.undertow.websockets.WebSocketExtension; import org.jboss.logging.BasicLogger; import org.jboss.logging.Logger; import org.jboss.logging.annotations.Cause; import org.jboss.logging.annotations.LogMessage; import org.jboss.logging.annotations.Message; import org.jboss.logging.annotations.MessageLogger; /** * log messages start at 25000 * * @author Stuart Douglas */ @MessageLogger(projectCode = "UT") public interface WebSocketLogger extends BasicLogger { WebSocketLogger ROOT_LOGGER = Logger.getMessageLogger(WebSocketLogger.class, WebSocketLogger.class.getPackage().getName()); WebSocketLogger REQUEST_LOGGER = Logger.getMessageLogger(WebSocketLogger.class, WebSocketLogger.class.getPackage().getName() + ".request"); WebSocketLogger EXTENSION_LOGGER = Logger.getMessageLogger(WebSocketLogger.class, WebSocketLogger.class.getPackage().getName() + ".extension"); // // @LogMessage(level = Logger.Level.ERROR) // @Message(id = 25001, value = "WebSocket handshake failed") // void webSocketHandshakeFailed(@Cause Throwable cause); // // @LogMessage(level = Logger.Level.ERROR) // @Message(id = 25002, value = "StreamSinkFrameChannel %s was closed before writing was finished, web socket connection is now unusable") // void closedBeforeFinishedWriting(StreamSinkFrameChannel streamSinkFrameChannel); @LogMessage(level = Logger.Level.DEBUG) @Message(id = 25003, value = "Decoding WebSocket Frame with opCode %s") void decodingFrameWithOpCode(int opCode); // // @LogMessage(level = Logger.Level.ERROR) // @Message(id = 25004, value = "Failure during execution of SendCallback") // void sendCallbackExecutionError(@Cause Throwable cause); // // @LogMessage(level = Logger.Level.ERROR) // @Message(id = 25005, value = "Failed to set idle timeout") // void setIdleTimeFailed(@Cause Throwable cause); // // // @LogMessage(level = Logger.Level.ERROR) // @Message(id = 25006, value = "Failed to get idle timeout") // void getIdleTimeFailed(@Cause Throwable cause); @LogMessage(level = Logger.Level.ERROR) @Message(id = 25007, value = "Unhandled exception for annotated endpoint %s") void unhandledErrorInAnnotatedEndpoint(Object instance, @Cause Throwable thr); @LogMessage(level = Logger.Level.WARN) @Message(id = 25008, value = "Incorrect parameter %s for extension") void incorrectExtensionParameter(WebSocketExtension.Parameter param); } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/WebSocketMessages.java000066400000000000000000000175671420065311100316760ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; import org.jboss.logging.Messages; import org.jboss.logging.annotations.Cause; import org.jboss.logging.annotations.Message; import org.jboss.logging.annotations.MessageBundle; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Collection; import java.util.zip.DataFormatException; /** * start at 20000 * @author Stuart Douglas */ @MessageBundle(projectCode = "UT") public interface WebSocketMessages { WebSocketMessages MESSAGES = Messages.getBundle(WebSocketMessages.class); // // @Message(id = 2001, value = "Not a WebSocket handshake request: missing %s in the headers") // WebSocketHandshakeException missingHeader(String header); @Message(id = 2002, value = "Channel is closed") IOException channelClosed(); @Message(id = 2003, value = "Text frame contains non UTF-8 data") UnsupportedEncodingException invalidTextFrameEncoding(); // // @Message(id = 2004, value = "Cannot call shutdownWrites, only %s of %s bytes written") // IOException notAllPayloadDataWritten(long written, long payloadSize); @Message(id = 2005, value = "Fragmented control frame") WebSocketFrameCorruptedException fragmentedControlFrame(); @Message(id = 2006, value = "Control frame with payload length > 125 octets") WebSocketFrameCorruptedException toBigControlFrame(); @Message(id = 2007, value = "Control frame using reserved opcode = %s") WebSocketFrameCorruptedException reservedOpCodeInControlFrame(int opCode); @Message(id = 2008, value = "Received close control frame with payload len 1") WebSocketFrameCorruptedException controlFrameWithPayloadLen1(); @Message(id = 2009, value = "Data frame using reserved opcode = %s") WebSocketFrameCorruptedException reservedOpCodeInDataFrame(int opCode); @Message(id = 2010, value = "Received continuation data frame outside fragmented message") WebSocketFrameCorruptedException continuationFrameOutsideFragmented(); @Message(id = 2011, value = "Received non-continuation data frame while inside fragmented message") WebSocketFrameCorruptedException nonContinuationFrameInsideFragmented(); // // @Message(id = 2012, value = "Invalid data frame length (not using minimal length encoding)") // WebSocketFrameCorruptedException invalidDataFrameLength(); @Message(id = 2013, value = "Cannot decode web socket frame with opcode: %s") IllegalStateException unsupportedOpCode(int opCode); @Message(id = 2014, value = "WebSocketFrameType %s is not supported by this WebSocketChannel\"") IllegalArgumentException unsupportedFrameType(WebSocketFrameType type); @Message(id = 2015, value = "Extensions not allowed but received rsv of %s") WebSocketFrameCorruptedException extensionsNotAllowed(int rsv); @Message(id = 2016, value = "Could not find supported protocol in request list %s. Supported protocols are %s") WebSocketHandshakeException unsupportedProtocol(String requestedSubprotocols, Collection subprotocols); // // @Message(id = 2017, value = "No Length encoded in the frame") // WebSocketFrameCorruptedException noLengthEncodedInFrame(); // // @Message(id = 2018, value = "Payload is not support in CloseFrames when using WebSocket Version 00") // IllegalArgumentException payloadNotSupportedInCloseFrames(); @Message(id = 2019, value = "Invalid payload for PING (payload length must be <= 125, was %s)") IllegalArgumentException invalidPayloadLengthForPing(long payloadLength); // // @Message(id = 2020, value = "Payload is not supported for Close Frames when using WebSocket 00") // IOException noPayloadAllowedForCloseFrames(); // // @Message(id = 2021, value = "Fragmentation not supported") // UnsupportedOperationException fragmentationNotSupported(); // // @Message(id = 2022, value = "Can only be changed before the write is in progress") // IllegalStateException writeInProgress(); @Message(id = 2023, value = "Extensions not supported") UnsupportedOperationException extensionsNotSupported(); // // @Message(id = 2024, value = "The payload length must be >= 0") // IllegalArgumentException negativePayloadLength(); // // @Message(id = 2025, value = "Closed before all bytes where read") // IOException closedBeforeAllBytesWereRead(); @Message(id = 2026, value = "Invalid close frame status code: %s") WebSocketInvalidCloseCodeException invalidCloseFrameStatusCode(int statusCode); @Message(id = 2027, value = "Could not send data, as the underlying web socket connection has been broken") IOException streamIsBroken(); // // @Message(id = 2028, value = "Specified length is bigger the available size of the FileChannel") // IllegalArgumentException lengthBiggerThenFileChannel(); // // @Message(id = 2029, value = "FragmentedSender was complete already") // IllegalArgumentException fragmentedSenderCompleteAlready(); // // @Message(id = 2030, value = "Array of SenderCallbacks must be non empty") // IllegalArgumentException senderCallbacksEmpty(); // // @Message(id = 2031, value = "Only one FragmentedSender can be used at the same time") // IllegalStateException fragmentedSenderInUse(); // // @Message(id = 2032, value = "Close frame was send before") // IOException closeFrameSentBefore(); // // @Message(id = 2033, value = "Blocking operation was called in IO thread") // IllegalStateException blockingOperationInIoThread(); @Message(id = 2034, value = "Web socket frame was not masked") WebSocketFrameCorruptedException frameNotMasked(); @Message(id = 2035, value = "The response did not contain an 'Upgrade: websocket' header") IOException noWebSocketUpgradeHeader(); @Message(id = 2036, value = "The response did not contain a 'Connection: upgrade' header") IOException noWebSocketConnectionHeader(); @Message(id = 2037, value = "Sec-WebSocket-Accept mismatch, expecting %s, received %s") IOException webSocketAcceptKeyMismatch(String dKey, String acceptKey); // // @Message(id = 2038, value = "Cannot call method with frame type %s, only text or binary is allowed") // IllegalArgumentException incorrectFrameType(WebSocketFrameType type); // // @Message(id = 2039, value = "Data has already been released") // IllegalStateException dataHasBeenReleased(); @Message(id = 2040, value = "Message exceeded max message size of %s") String messageToBig(long maxMessageSize); // // @Message(id = 2041, value = "Attempted to write more data than the specified payload length") // IOException messageOverflow(); // // @Message(id = 2042, value = "Server responded with unsupported extension %s. Supported extensions: %s") // IOException unsupportedExtension(String part, List supportedExtensions); // // @Message(id = 2043, value = "WebSocket client is trying to use extensions but there is not extensions configured") // IllegalStateException badExtensionsConfiguredInClient(); @Message(id = 2044, value = "Compressed message payload is corrupted") IOException badCompressedPayload(@Cause final DataFormatException cause); @Message(id = 2045, value = "Unable to send on newly created channel!") IllegalStateException unableToSendOnNewChannel(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/WebSocketUtils.java000066400000000000000000000246251420065311100312200ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; import io.undertow.UndertowLogger; import org.xnio.Buffers; import org.xnio.ChannelExceptionHandler; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import io.undertow.connector.ByteBufferPool; import io.undertow.util.Transfer; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.nio.charset.StandardCharsets; /** * Utility class which holds general useful utility methods which * can be used within WebSocket implementations. * * @author Norman Maurer */ public final class WebSocketUtils { private static final String EMPTY = ""; /** * Create a {@link ByteBuffer} which holds the UTF8 encoded bytes for the * given {@link String}. * * @param utfString The {@link String} to convert * @return buffer The {@link ByteBuffer} which was created */ public static ByteBuffer fromUtf8String(CharSequence utfString) { if (utfString == null || utfString.length() == 0) { return Buffers.EMPTY_BYTE_BUFFER; } else { return ByteBuffer.wrap(utfString.toString().getBytes(StandardCharsets.UTF_8)); } } public static String toUtf8String(ByteBuffer buffer) { if (!buffer.hasRemaining()) { return EMPTY; } if (buffer.hasArray()) { return new String(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining(), StandardCharsets.UTF_8); } else { byte[] content = new byte[buffer.remaining()]; buffer.get(content); return new String(content, StandardCharsets.UTF_8); } } public static String toUtf8String(ByteBuffer... buffers) { int size = 0; for (ByteBuffer buf: buffers) { size += buf.remaining(); } if (size == 0) { return EMPTY; } int index = 0; byte[] bytes = new byte[size]; for (ByteBuffer buf: buffers) { if (buf.hasArray()) { int len = buf.remaining(); System.arraycopy(buf.array(), buf.arrayOffset() + buf.position(), bytes, index, len); index += len; } else { int len = buf.remaining(); buf.get(bytes, index, len); index += len; } } return new String(bytes, StandardCharsets.UTF_8); } /** * Transfer the data from the source to the sink using the given through buffer to pass data through. */ public static long transfer(final ReadableByteChannel source, final long count, final ByteBuffer throughBuffer, final WritableByteChannel sink) throws IOException { long total = 0L; while (total < count) { throughBuffer.clear(); if (count - total < throughBuffer.remaining()) { throughBuffer.limit((int) (count - total)); } try { long res = source.read(throughBuffer); if (res <= 0) { return total == 0L ? res : total; } } finally { throughBuffer.flip(); } while (throughBuffer.hasRemaining()) { long res = sink.write(throughBuffer); if (res <= 0) { return total; } total += res; } } return total; } /** * Echo back the frame to the sender */ public static void echoFrame(final WebSocketChannel channel, final StreamSourceFrameChannel ws) throws IOException { final WebSocketFrameType type; switch (ws.getType()) { case PONG: // pong frames must be discarded ws.close(); return; case PING: // if a ping is send the autobahn testsuite expects a PONG when echo back type = WebSocketFrameType.PONG; break; default: type = ws.getType(); break; } final StreamSinkFrameChannel sink = channel.send(type); sink.setRsv(ws.getRsv()); initiateTransfer(ws, sink, new ChannelListener() { @Override public void handleEvent(StreamSourceFrameChannel streamSourceFrameChannel) { IoUtils.safeClose(streamSourceFrameChannel); } }, new ChannelListener() { @Override public void handleEvent(StreamSinkFrameChannel streamSinkFrameChannel) { try { streamSinkFrameChannel.shutdownWrites(); } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); IoUtils.safeClose(streamSinkFrameChannel, channel); return; } try { if (!streamSinkFrameChannel.flush()) { streamSinkFrameChannel.getWriteSetter().set(ChannelListeners.flushingChannelListener( new ChannelListener() { @Override public void handleEvent(StreamSinkFrameChannel streamSinkFrameChannel) { streamSinkFrameChannel.getWriteSetter().set(null); IoUtils.safeClose(streamSinkFrameChannel); if (type == WebSocketFrameType.CLOSE) { IoUtils.safeClose(channel); } } }, new ChannelExceptionHandler() { @Override public void handleException(StreamSinkFrameChannel streamSinkFrameChannel, IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); IoUtils.safeClose(streamSinkFrameChannel, channel); } } )); streamSinkFrameChannel.resumeWrites(); } else { if (type == WebSocketFrameType.CLOSE) { IoUtils.safeClose(channel); } streamSinkFrameChannel.getWriteSetter().set(null); IoUtils.safeClose(streamSinkFrameChannel); } } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); IoUtils.safeClose(streamSinkFrameChannel, channel); } } }, new ChannelExceptionHandler() { @Override public void handleException(StreamSourceFrameChannel streamSourceFrameChannel, IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); IoUtils.safeClose(streamSourceFrameChannel, channel); } }, new ChannelExceptionHandler() { @Override public void handleException(StreamSinkFrameChannel streamSinkFrameChannel, IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); IoUtils.safeClose(streamSinkFrameChannel, channel); } }, channel.getBufferPool() ); } /** * Initiate a low-copy transfer between two stream channels. The pool should be a direct buffer pool for best * performance. * * @param source the source channel * @param sink the target channel * @param sourceListener the source listener to set and call when the transfer is complete, or {@code null} to clear the listener at that time * @param sinkListener the target listener to set and call when the transfer is complete, or {@code null} to clear the listener at that time * @param readExceptionHandler the read exception handler to call if an error occurs during a read operation * @param writeExceptionHandler the write exception handler to call if an error occurs during a write operation * @param pool the pool from which the transfer buffer should be allocated */ @Deprecated public static void initiateTransfer(final I source, final O sink, final ChannelListener sourceListener, final ChannelListener sinkListener, final ChannelExceptionHandler readExceptionHandler, final ChannelExceptionHandler writeExceptionHandler, ByteBufferPool pool) { Transfer.initiateTransfer(source, sink, sourceListener, sinkListener, readExceptionHandler, writeExceptionHandler, pool); } private WebSocketUtils() { // utility class } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/WebSocketVersion.java000066400000000000000000000051231420065311100315350ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; import io.undertow.util.AttachmentKey; /** *

* Enum which list all the different versions of the WebSocket specification (to the current date). *

*

* A specification is tied to one wire protocol version but a protocol version may have use by more than 1 version of * the specification. *

* * @author Norman Maurer */ public enum WebSocketVersion { /** * Unknown version of the protocol */ UNKNOWN, /** * draft-ietf-hybi-thewebsocketprotocol- 00. */ V00, /** * draft-ietf-hybi-thewebsocketprotocol- 07 */ V07, /** * draft-ietf-hybi-thewebsocketprotocol- 10 */ V08, /** * RFC 6455. This was originally draft-ietf-hybi-thewebsocketprotocol- * 17 */ V13; /** * Returns a {@link String} representation of the {@link WebSocketVersion} that can be used in the HTTP Headers. */ public String toHttpHeaderValue() { if (this == V00) { return "0"; } if (this == V07) { return "7"; } if (this == V08) { return "8"; } if (this == V13) { return "13"; } // Should never hit here. throw new IllegalStateException("Unknown WebSocket version: " + this); } public static final AttachmentKey ATTACHMENT_KEY = AttachmentKey.create(WebSocketVersion.class); } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/WebSockets.java000066400000000000000000001341701420065311100303570ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core; import io.undertow.connector.PooledByteBuffer; import io.undertow.util.ImmediatePooledByteBuffer; import io.undertow.util.WorkerUtils; import org.xnio.Buffers; import org.xnio.ChannelExceptionHandler; import org.xnio.ChannelListener; import org.xnio.IoUtils; import org.xnio.XnioExecutor; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; import static org.xnio.ChannelListeners.flushingChannelListener; /** * @author Stuart Douglas */ public class WebSockets { /** * Sends a complete text message, invoking the callback when complete * * @param message The text to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion */ public static void sendText(final String message, final WebSocketChannel wsChannel, final WebSocketCallback callback) { sendText(message, wsChannel, callback, null); } /** * Sends a complete text message, invoking the callback when complete * * @param message The text to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion */ public static void sendText(final String message, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context) { final ByteBuffer data = ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8)); sendInternal(data, WebSocketFrameType.TEXT, wsChannel, callback, context, -1); } /** * Sends a complete text message, invoking the callback when complete * * @param message The text to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendText(final String message, final WebSocketChannel wsChannel, final WebSocketCallback callback, long timeoutmillis) { sendText(message, wsChannel, callback, null, timeoutmillis); } /** * Sends a complete text message, invoking the callback when complete * * @param message The text to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendText(final String message, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context, long timeoutmillis) { final ByteBuffer data = ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8)); sendInternal(data, WebSocketFrameType.TEXT, wsChannel, callback, context, timeoutmillis); } /** * Sends a complete text message, invoking the callback when complete * * @param message The text to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion */ public static void sendText(final ByteBuffer message, final WebSocketChannel wsChannel, final WebSocketCallback callback) { sendInternal(message, WebSocketFrameType.TEXT, wsChannel, callback, null, -1); } /** * Sends a complete text message, invoking the callback when complete * * @param message The text to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion */ public static void sendText(final ByteBuffer message, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context) { sendInternal(message, WebSocketFrameType.TEXT, wsChannel, callback, context, -1); } /** * Sends a complete text message, invoking the callback when complete * * @param message The text to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendText(final ByteBuffer message, final WebSocketChannel wsChannel, final WebSocketCallback callback, long timeoutmillis) { sendInternal(message, WebSocketFrameType.TEXT, wsChannel, callback, null, timeoutmillis); } /** * Sends a complete text message, invoking the callback when complete * * @param message The text to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendText(final ByteBuffer message, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context, long timeoutmillis) { sendInternal(message, WebSocketFrameType.TEXT, wsChannel, callback, context, timeoutmillis); } /** * Sends a complete text message, invoking the callback when complete * Automatically frees the pooled byte buffer when done. * * @param pooledData The data to send, it will be freed when done * @param wsChannel The web socket channel * @param callback The callback to invoke on completion */ public static void sendText(final PooledByteBuffer pooledData, final WebSocketChannel wsChannel, final WebSocketCallback callback) { sendInternal(pooledData, WebSocketFrameType.TEXT, wsChannel, callback, null, -1); } /** * Sends a complete text message, invoking the callback when complete * Automatically frees the pooled byte buffer when done. * * @param pooledData The data to send, it will be freed when done * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion */ public static void sendText(final PooledByteBuffer pooledData, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context) { sendInternal(pooledData, WebSocketFrameType.TEXT, wsChannel, callback, context, -1); } /** * Sends a complete text message, invoking the callback when complete * Automatically frees the pooled byte buffer when done. * * @param pooledData The data to send, it will be freed when done * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendText(final PooledByteBuffer pooledData, final WebSocketChannel wsChannel, final WebSocketCallback callback, long timeoutmillis) { sendInternal(pooledData, WebSocketFrameType.TEXT, wsChannel, callback, null, timeoutmillis); } /** * Sends a complete text message, invoking the callback when complete * Automatically frees the pooled byte buffer when done. * * @param pooledData The data to send, it will be freed when done * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendText(final PooledByteBuffer pooledData, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context, long timeoutmillis) { sendInternal(pooledData, WebSocketFrameType.TEXT, wsChannel, callback, context, timeoutmillis); } /** * Sends a complete text message, invoking the callback when complete * * @param message The text to send * @param wsChannel The web socket channel */ public static void sendTextBlocking(final String message, final WebSocketChannel wsChannel) throws IOException { final ByteBuffer data = ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8)); sendBlockingInternal(data, WebSocketFrameType.TEXT, wsChannel); } /** * Sends a complete text message, invoking the callback when complete * * @param message The text to send * @param wsChannel The web socket channel */ public static void sendTextBlocking(final ByteBuffer message, final WebSocketChannel wsChannel) throws IOException { sendBlockingInternal(message, WebSocketFrameType.TEXT, wsChannel); } /** * Sends a complete text message, invoking the callback when complete * Automatically frees the pooled byte buffer when done. * * @param pooledData The data to send, it will be freed when done * @param wsChannel The web socket channel */ public static void sendTextBlocking(final PooledByteBuffer pooledData, final WebSocketChannel wsChannel) throws IOException { sendBlockingInternal(pooledData, WebSocketFrameType.TEXT, wsChannel); } /** * Sends a complete ping message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion */ public static void sendPing(final ByteBuffer data, final WebSocketChannel wsChannel, final WebSocketCallback callback) { sendInternal(data, WebSocketFrameType.PING, wsChannel, callback, null, -1); } /** * Sends a complete ping message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion */ public static void sendPing(final ByteBuffer data, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context) { sendInternal(data, WebSocketFrameType.PING, wsChannel, callback, context, -1); } /** * Sends a complete ping message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendPing(final ByteBuffer data, final WebSocketChannel wsChannel, final WebSocketCallback callback, long timeoutmillis) { sendInternal(data, WebSocketFrameType.PING, wsChannel, callback, null, timeoutmillis); } /** * Sends a complete ping message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendPing(final ByteBuffer data, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context, long timeoutmillis) { sendInternal(data, WebSocketFrameType.PING, wsChannel, callback, context, timeoutmillis); } /** * Sends a complete ping message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion */ public static void sendPing(final ByteBuffer[] data, final WebSocketChannel wsChannel, final WebSocketCallback callback) { sendInternal(mergeBuffers(data), WebSocketFrameType.PING, wsChannel, callback, null, -1); } /** * Sends a complete ping message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion */ public static void sendPing(final ByteBuffer[] data, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context) { sendInternal(mergeBuffers(data), WebSocketFrameType.PING, wsChannel, callback, context, -1); } /** * Sends a complete ping message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendPing(final ByteBuffer[] data, final WebSocketChannel wsChannel, final WebSocketCallback callback, long timeoutmillis) { sendInternal(mergeBuffers(data), WebSocketFrameType.PING, wsChannel, callback, null, timeoutmillis); } /** * Sends a complete ping message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendPing(final ByteBuffer[] data, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context, long timeoutmillis) { sendInternal(mergeBuffers(data), WebSocketFrameType.PING, wsChannel, callback, context, timeoutmillis); } /** * Sends a complete ping message, invoking the callback when complete * Automatically frees the pooled byte buffer when done. * * @param pooledData The data to send, it will be freed when done * @param wsChannel The web socket channel * @param callback The callback to invoke on completion */ public static void sendPing(final PooledByteBuffer pooledData, final WebSocketChannel wsChannel, final WebSocketCallback callback) { sendInternal(pooledData, WebSocketFrameType.PING, wsChannel, callback, null, -1); } /** * Sends a complete ping message, invoking the callback when complete * Automatically frees the pooled byte buffer when done. * * @param pooledData The data to send, it will be freed when done * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion */ public static void sendPing(final PooledByteBuffer pooledData, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context) { sendInternal(pooledData, WebSocketFrameType.PING, wsChannel, callback, context, -1); } /** * Sends a complete ping message, invoking the callback when complete * Automatically frees the pooled byte buffer when done. * * @param pooledData The data to send, it will be freed when done * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendPing(final PooledByteBuffer pooledData, final WebSocketChannel wsChannel, final WebSocketCallback callback, long timeoutmillis) { sendInternal(pooledData, WebSocketFrameType.PING, wsChannel, callback, null, timeoutmillis); } /** * Sends a complete ping message, invoking the callback when complete * Automatically frees the pooled byte buffer when done. * * @param pooledData The data to send, it will be freed when done * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendPing(final PooledByteBuffer pooledData, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context, long timeoutmillis) { sendInternal(pooledData, WebSocketFrameType.PING, wsChannel, callback, context, timeoutmillis); } /** * Sends a complete ping message using blocking IO * * @param data The data to send * @param wsChannel The web socket channel */ public static void sendPingBlocking(final ByteBuffer data, final WebSocketChannel wsChannel) throws IOException { sendBlockingInternal(data, WebSocketFrameType.PING, wsChannel); } /** * Sends a complete ping message using blocking IO * * @param data The data to send * @param wsChannel The web socket channel */ public static void sendPingBlocking(final ByteBuffer[] data, final WebSocketChannel wsChannel) throws IOException { sendBlockingInternal(mergeBuffers(data), WebSocketFrameType.PING, wsChannel); } /** * Sends a complete ping message using blocking IO * Automatically frees the pooled byte buffer when done. * * @param pooledData The data to send, it will be freed when done * @param wsChannel The web socket channel */ public static void sendPingBlocking(final PooledByteBuffer pooledData, final WebSocketChannel wsChannel) throws IOException { sendBlockingInternal(pooledData, WebSocketFrameType.PING, wsChannel); } /** * Sends a complete pong message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion */ public static void sendPong(final ByteBuffer data, final WebSocketChannel wsChannel, final WebSocketCallback callback) { sendInternal(data, WebSocketFrameType.PONG, wsChannel, callback, null, -1); } /** * Sends a complete pong message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion */ public static void sendPong(final ByteBuffer data, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context) { sendInternal(data, WebSocketFrameType.PONG, wsChannel, callback, context, -1); } /** * Sends a complete pong message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendPong(final ByteBuffer data, final WebSocketChannel wsChannel, final WebSocketCallback callback, long timeoutmillis) { sendInternal(data, WebSocketFrameType.PONG, wsChannel, callback, null, timeoutmillis); } /** * Sends a complete pong message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendPong(final ByteBuffer data, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context, long timeoutmillis) { sendInternal(data, WebSocketFrameType.PONG, wsChannel, callback, context, timeoutmillis); } /** * Sends a complete pong message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion */ public static void sendPong(final ByteBuffer[] data, final WebSocketChannel wsChannel, final WebSocketCallback callback) { sendInternal(mergeBuffers(data), WebSocketFrameType.PONG, wsChannel, callback, null, -1); } /** * Sends a complete pong message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion */ public static void sendPong(final ByteBuffer[] data, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context) { sendInternal(mergeBuffers(data), WebSocketFrameType.PONG, wsChannel, callback, context, -1); } /** * Sends a complete pong message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendPong(final ByteBuffer[] data, final WebSocketChannel wsChannel, final WebSocketCallback callback, long timeoutmillis) { sendInternal(mergeBuffers(data), WebSocketFrameType.PONG, wsChannel, callback, null, timeoutmillis); } /** * Sends a complete pong message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendPong(final ByteBuffer[] data, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context, long timeoutmillis) { sendInternal(mergeBuffers(data), WebSocketFrameType.PONG, wsChannel, callback, context, timeoutmillis); } /** * Sends a complete pong message, invoking the callback when complete * Automatically frees the pooled byte buffer when done. * * @param pooledData The data to send, it will be freed when done * @param wsChannel The web socket channel * @param callback The callback to invoke on completion */ public static void sendPong(final PooledByteBuffer pooledData, final WebSocketChannel wsChannel, final WebSocketCallback callback) { sendInternal(pooledData, WebSocketFrameType.PONG, wsChannel, callback, null, -1); } /** * Sends a complete pong message, invoking the callback when complete * Automatically frees the pooled byte buffer when done. * * @param pooledData The data to send, it will be freed when done * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion */ public static void sendPong(final PooledByteBuffer pooledData, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context) { sendInternal(pooledData, WebSocketFrameType.PONG, wsChannel, callback, context, -1); } /** * Sends a complete pong message, invoking the callback when complete * Automatically frees the pooled byte buffer when done. * * @param pooledData The data to send, it will be freed when done * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendPong(final PooledByteBuffer pooledData, final WebSocketChannel wsChannel, final WebSocketCallback callback, long timeoutmillis) { sendInternal(pooledData, WebSocketFrameType.PONG, wsChannel, callback, null, timeoutmillis); } /** * Sends a complete pong message, invoking the callback when complete * Automatically frees the pooled byte buffer when done. * * @param pooledData The data to send, it will be freed when done * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendPong(final PooledByteBuffer pooledData, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context, long timeoutmillis) { sendInternal(pooledData, WebSocketFrameType.PONG, wsChannel, callback, context, timeoutmillis); } /** * Sends a complete pong message using blocking IO * * @param data The data to send * @param wsChannel The web socket channel */ public static void sendPongBlocking(final ByteBuffer data, final WebSocketChannel wsChannel) throws IOException { sendBlockingInternal(data, WebSocketFrameType.PONG, wsChannel); } /** * Sends a complete pong message using blocking IO * * @param data The data to send * @param wsChannel The web socket channel */ public static void sendPongBlocking(final ByteBuffer[] data, final WebSocketChannel wsChannel) throws IOException { sendBlockingInternal(mergeBuffers(data), WebSocketFrameType.PONG, wsChannel); } /** * Sends a complete pong message using blocking IO * Automatically frees the pooled byte buffer when done. * * @param pooledData The data to send, it will be freed when done * @param wsChannel The web socket channel */ public static void sendPongBlocking(final PooledByteBuffer pooledData, final WebSocketChannel wsChannel) throws IOException { sendBlockingInternal(pooledData, WebSocketFrameType.PONG, wsChannel); } /** * Sends a complete binary message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion */ public static void sendBinary(final ByteBuffer data, final WebSocketChannel wsChannel, final WebSocketCallback callback) { sendInternal(data, WebSocketFrameType.BINARY, wsChannel, callback, null, -1); } /** * Sends a complete binary message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion */ public static void sendBinary(final ByteBuffer data, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context) { sendInternal(data, WebSocketFrameType.BINARY, wsChannel, callback, context, -1); } /** * Sends a complete binary message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendBinary(final ByteBuffer data, final WebSocketChannel wsChannel, final WebSocketCallback callback, long timeoutmillis) { sendInternal(data, WebSocketFrameType.BINARY, wsChannel, callback, null, timeoutmillis); } /** * Sends a complete binary message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendBinary(final ByteBuffer data, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context, long timeoutmillis) { sendInternal(data, WebSocketFrameType.BINARY, wsChannel, callback, context, timeoutmillis); } /** * Sends a complete binary message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion */ public static void sendBinary(final ByteBuffer[] data, final WebSocketChannel wsChannel, final WebSocketCallback callback) { sendInternal(mergeBuffers(data), WebSocketFrameType.BINARY, wsChannel, callback, null, -1); } /** * Sends a complete binary message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion */ public static void sendBinary(final ByteBuffer[] data, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context) { sendInternal(mergeBuffers(data), WebSocketFrameType.BINARY, wsChannel, callback, context, -1); } /** * Sends a complete binary message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendBinary(final ByteBuffer[] data, final WebSocketChannel wsChannel, final WebSocketCallback callback, long timeoutmillis) { sendInternal(mergeBuffers(data), WebSocketFrameType.BINARY, wsChannel, callback, null, timeoutmillis); } /** * Sends a complete binary message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendBinary(final ByteBuffer[] data, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context, long timeoutmillis) { sendInternal(mergeBuffers(data), WebSocketFrameType.BINARY, wsChannel, callback, context, timeoutmillis); } /** * Sends a complete binary message, invoking the callback when complete * Automatically frees the pooled byte buffer when done. * * @param pooledData The data to send, it will be freed when done * @param wsChannel The web socket channel * @param callback The callback to invoke on completion */ public static void sendBinary(final PooledByteBuffer pooledData, final WebSocketChannel wsChannel, final WebSocketCallback callback) { sendInternal(pooledData, WebSocketFrameType.BINARY, wsChannel, callback, null, -1); } /** * Sends a complete binary message, invoking the callback when complete * Automatically frees the pooled byte buffer when done. * * @param pooledData The data to send, it will be freed when done * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion */ public static void sendBinary(final PooledByteBuffer pooledData, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context) { sendInternal(pooledData, WebSocketFrameType.BINARY, wsChannel, callback, context, -1); } /** * Sends a complete binary message, invoking the callback when complete * Automatically frees the pooled byte buffer when done. * * @param pooledData The data to send, it will be freed when done * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendBinary(final PooledByteBuffer pooledData, final WebSocketChannel wsChannel, final WebSocketCallback callback, long timeoutmillis) { sendInternal(pooledData, WebSocketFrameType.BINARY, wsChannel, callback, null, timeoutmillis); } /** * Sends a complete binary message, invoking the callback when complete * Automatically frees the pooled byte buffer when done. * * @param pooledData The data to send, it will be freed when done * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion * @param timeoutmillis the timeout in milliseconds */ public static void sendBinary(final PooledByteBuffer pooledData, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context, long timeoutmillis) { sendInternal(pooledData, WebSocketFrameType.BINARY, wsChannel, callback, context, timeoutmillis); } /** * Sends a complete binary message using blocking IO * * @param data The data to send * @param wsChannel The web socket channel */ public static void sendBinaryBlocking(final ByteBuffer data, final WebSocketChannel wsChannel) throws IOException { sendBlockingInternal(data, WebSocketFrameType.BINARY, wsChannel); } /** * Sends a complete binary message using blocking IO * * @param data The data to send * @param wsChannel The web socket channel */ public static void sendBinaryBlocking(final ByteBuffer[] data, final WebSocketChannel wsChannel) throws IOException { sendBlockingInternal(mergeBuffers(data), WebSocketFrameType.BINARY, wsChannel); } /** * Sends a complete binary message using blocking IO * Automatically frees the pooled byte buffer when done. * * @param pooledData The data to send, it will be freed when done * @param wsChannel The web socket channel */ public static void sendBinaryBlocking(final PooledByteBuffer pooledData, final WebSocketChannel wsChannel) throws IOException { sendBlockingInternal(pooledData, WebSocketFrameType.BINARY, wsChannel); } /** * Sends a complete close message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion */ public static void sendClose(final ByteBuffer data, final WebSocketChannel wsChannel, final WebSocketCallback callback) { CloseMessage sm = new CloseMessage(data); sendClose(sm, wsChannel, callback); } /** * Sends a complete close message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion */ public static void sendClose(final ByteBuffer data, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context) { CloseMessage sm = new CloseMessage(data); sendClose(sm, wsChannel, callback, context); } /** * Sends a complete close message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion */ public static void sendClose(final ByteBuffer[] data, final WebSocketChannel wsChannel, final WebSocketCallback callback) { CloseMessage sm = new CloseMessage(data); sendClose(sm, wsChannel, callback); } /** * Sends a complete close message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion */ public static void sendClose(final ByteBuffer[] data, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context) { CloseMessage sm = new CloseMessage(data); sendClose(sm, wsChannel, callback, context); } /** * Sends a complete close message, invoking the callback when complete * * @param code The close code * @param wsChannel The web socket channel * @param callback The callback to invoke on completion */ public static void sendClose(final int code, String reason, final WebSocketChannel wsChannel, final WebSocketCallback callback) { sendClose(new CloseMessage(code, reason), wsChannel, callback); } /** * Sends a complete close message, invoking the callback when complete * * @param code The close code * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion */ public static void sendClose(final int code, String reason, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context) { sendClose(new CloseMessage(code, reason), wsChannel, callback, context); } /** * Sends a complete close message, invoking the callback when complete * * @param closeMessage The close message * @param wsChannel The web socket channel * @param callback The callback to invoke on completion */ public static void sendClose(final CloseMessage closeMessage, final WebSocketChannel wsChannel, final WebSocketCallback callback) { sendClose(closeMessage, wsChannel, callback, null); } /** * Sends a complete close message, invoking the callback when complete * * @param closeMessage The close message * @param wsChannel The web socket channel * @param callback The callback to invoke on completion * @param context The context object that will be passed to the callback on completion */ public static void sendClose(final CloseMessage closeMessage, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context) { wsChannel.setCloseCode(closeMessage.getCode()); wsChannel.setCloseReason(closeMessage.getReason()); sendInternal(closeMessage.toByteBuffer(), WebSocketFrameType.CLOSE, wsChannel, callback, context, -1); } /** * Sends a complete close message, invoking the callback when complete * * @param closeMessage the close message * @param wsChannel The web socket channel */ public static void sendCloseBlocking(final CloseMessage closeMessage, final WebSocketChannel wsChannel) throws IOException { wsChannel.setCloseReason(closeMessage.getReason()); wsChannel.setCloseCode(closeMessage.getCode()); sendBlockingInternal(closeMessage.toByteBuffer(), WebSocketFrameType.CLOSE, wsChannel); } /** * Sends a complete close message, invoking the callback when complete * * @param code * @param wsChannel The web socket channel */ public static void sendCloseBlocking(final int code, String reason, final WebSocketChannel wsChannel) throws IOException { sendCloseBlocking(new CloseMessage(code, reason), wsChannel); } /** * Sends a complete close message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel */ public static void sendCloseBlocking(final ByteBuffer data, final WebSocketChannel wsChannel) throws IOException { sendCloseBlocking(new CloseMessage(data), wsChannel); } /** * Sends a complete close message, invoking the callback when complete * * @param data The data to send * @param wsChannel The web socket channel */ public static void sendCloseBlocking(final ByteBuffer[] data, final WebSocketChannel wsChannel) throws IOException { sendCloseBlocking(new CloseMessage(data), wsChannel); } private static void sendInternal(final ByteBuffer data, WebSocketFrameType type, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context, long timeoutmillis) { sendInternal(new ImmediatePooledByteBuffer(data), type, wsChannel, callback, context, timeoutmillis); } private static void sendInternal(final PooledByteBuffer pooledData, WebSocketFrameType type, final WebSocketChannel wsChannel, final WebSocketCallback callback, T context, long timeoutmillis) { boolean closePooledData = true; try { StreamSinkFrameChannel channel = wsChannel.send(type); // TODO chunk data into some MTU-like thing to control packet size closePooledData = false; // channel.send takes ownership of pooledData so it no longer needs to be closed if(!channel.send(pooledData)) { throw WebSocketMessages.MESSAGES.unableToSendOnNewChannel(); } flushChannelAsync(wsChannel, callback, channel, context, timeoutmillis); } catch (IOException e) { if (callback != null) { callback.onError(wsChannel, context, e); } else { IoUtils.safeClose(wsChannel); } } finally { if ( closePooledData ) { pooledData.close(); } } } private static void flushChannelAsync(final WebSocketChannel wsChannel, final WebSocketCallback callback, StreamSinkFrameChannel channel, final T context, long timeoutmillis) throws IOException { final WebSocketFrameType type = channel.getType(); channel.shutdownWrites(); if (!channel.flush()) { channel.getWriteSetter().set(flushingChannelListener( new ChannelListener() { @Override public void handleEvent(StreamSinkFrameChannel channel) { if (callback != null) { callback.complete(wsChannel, context); } if (type == WebSocketFrameType.CLOSE && wsChannel.isCloseFrameReceived()) { IoUtils.safeClose(wsChannel); } //we explicitly set the channel to null, as in some situations this //listener may get invoked twice channel.getWriteSetter().set(null); } }, new ChannelExceptionHandler() { @Override public void handleException(StreamSinkFrameChannel channel, IOException exception) { if (callback != null) { callback.onError(wsChannel, context, exception); } IoUtils.safeClose(channel, wsChannel); //we explicitly set the channel to null, as in some situations this //listener may get invoked twice channel.getWriteSetter().set(null); } } )); if(timeoutmillis > 0) { setupTimeout(channel, timeoutmillis); } channel.resumeWrites(); return; } if (callback != null) { callback.complete(wsChannel, context); } } private static void setupTimeout(final StreamSinkFrameChannel channel, long timeoutmillis) { final XnioExecutor.Key key = WorkerUtils.executeAfter(channel.getIoThread(), new Runnable() { @Override public void run() { if (channel.isOpen()) { IoUtils.safeClose(channel); } } }, timeoutmillis, TimeUnit.MILLISECONDS); channel.getCloseSetter().set(new ChannelListener() { @Override public void handleEvent(StreamSinkFrameChannel channel) { key.remove(); } }); } private static void sendBlockingInternal(final ByteBuffer data, WebSocketFrameType type, final WebSocketChannel wsChannel) throws IOException { sendBlockingInternal(new ImmediatePooledByteBuffer(data), type, wsChannel); } private static void sendBlockingInternal(final PooledByteBuffer pooledData, WebSocketFrameType type, final WebSocketChannel wsChannel) throws IOException { boolean closePooledData = true; try { StreamSinkFrameChannel channel = wsChannel.send(type); // TODO chunk data into some MTU-like thing to control packet size closePooledData = false; // channel.send takes ownership of pooledData so it no longer needs to be closed if(!channel.send(pooledData)) { throw WebSocketMessages.MESSAGES.unableToSendOnNewChannel(); } channel.shutdownWrites(); while (!channel.flush()) { channel.awaitWritable(); } if (type == WebSocketFrameType.CLOSE && wsChannel.isCloseFrameReceived()) { IoUtils.safeClose(wsChannel); } } finally { if (closePooledData) { pooledData.close(); } } } private WebSockets() { } public static ByteBuffer mergeBuffers(ByteBuffer... payload) { int size = (int) Buffers.remaining(payload); if (size == 0) { return Buffers.EMPTY_BYTE_BUFFER; } ByteBuffer buffer = ByteBuffer.allocate(size); for (ByteBuffer buf : payload) { buffer.put(buf); } buffer.flip(); return buffer; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/function/000077500000000000000000000000001420065311100272625ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/function/ChannelFunction.java000066400000000000000000000041741420065311100332110ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.function; import io.undertow.server.protocol.framed.FrameHeaderData; import java.io.IOException; import java.nio.ByteBuffer; /** * @author Norman Maurer */ public interface ChannelFunction { void newFrame(FrameHeaderData headerData); /** * Is called on the {@link ByteBuffer} after a read operation completes * * @param buf the {@link ByteBuffer} to operate on * @param position the index in the {@link ByteBuffer} to start from * @param length the number of bytes to operate on * @throws IOException thrown if an error occurs */ void afterRead(ByteBuffer buf, int position, int length) throws IOException; /** * Is called on the {@link ByteBuffer} before a write operation completes * * @param buf the {@link ByteBuffer} to operate on * @param position the index in the {@link ByteBuffer} to start from * @param length the number of bytes to operate on * @throws IOException thrown if an error occurs */ void beforeWrite(ByteBuffer buf, int position, int length) throws IOException; /** * Is called to complete the {@link ChannelFunction}. Access it after complete * is called may result in unexpected behavior. * * @throws IOException thrown if an error occurs */ void complete() throws IOException; } ChannelFunctionFileChannel.java000066400000000000000000000120141420065311100352130ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/function/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.function; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; /** * @author Norman Maurer */ public class ChannelFunctionFileChannel extends FileChannel { private final ChannelFunction[] functions; private final FileChannel channel; public ChannelFunctionFileChannel(FileChannel channel, ChannelFunction... functions) { this.channel = channel; this.functions = functions; } @Override public long position() throws IOException { return channel.position(); } @Override public FileChannel position(long newPosition) throws IOException { channel.position(newPosition); return this; } @Override public long size() throws IOException { return channel.size(); } @Override public FileChannel truncate(long size) throws IOException { channel.truncate(size); return this; } @Override public void force(boolean metaData) throws IOException { channel.force(metaData); } @Override public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException { return channel.map(mode, position, size); } @Override public FileLock lock(long position, long size, boolean shared) throws IOException { return channel.lock(position, size, shared); } @Override public FileLock tryLock(long position, long size, boolean shared) throws IOException { return channel.tryLock(position, size, shared); } @Override protected void implCloseChannel() throws IOException { channel.close(); } @Override public int write(ByteBuffer src, long position) throws IOException { beforeWriting(src); return channel.write(src, position); } @Override public int read(ByteBuffer dst) throws IOException { int pos = dst.position(); int r = channel.read(dst); if (r > 0) { afterReading(dst, pos, r); } return r; } @Override public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { int[] positions = new int[length]; for (int i = 0; i < positions.length; i++) { positions[i] = dsts[i].position(); } long r = channel.read(dsts, offset, length); if (r > 0) { for (int i = offset; i < length; i++) { ByteBuffer dst = dsts[i]; afterReading(dst, positions[i], dst.position()); } } return r; } @Override public int write(ByteBuffer src) throws IOException { beforeWriting(src); return channel.write(src); } @Override public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { for (int i = offset; i < length; i++) { beforeWriting(srcs[i]); } return channel.write(srcs, offset, length); } @Override public int read(ByteBuffer dst, long position) throws IOException { int pos = dst.position(); int r = channel.read(dst, position); if (r > 0) { afterReading(dst, pos, r); } return r; } @Override public long transferTo(long position, long count, WritableByteChannel target) throws IOException { return channel.transferTo(position, count, new ChannelFunctionWritableByteChannel(target, functions)); } @Override public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException { return channel.transferFrom(new ChannelFunctionReadableByteChannel(channel, functions) ,position, count); } private void beforeWriting(ByteBuffer buffer) throws IOException { for (ChannelFunction func: functions) { int pos = buffer.position(); func.beforeWrite(buffer, pos, buffer.limit() - pos); } } private void afterReading(ByteBuffer buffer, int position, int length) throws IOException { for (ChannelFunction func: functions) { func.afterRead(buffer, position, length); } } } ChannelFunctionReadableByteChannel.java000066400000000000000000000035101420065311100366600ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/function/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.function; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; /** * @author Norman Maurer */ public class ChannelFunctionReadableByteChannel implements ReadableByteChannel { private final ChannelFunction[] functions; private final ReadableByteChannel channel; public ChannelFunctionReadableByteChannel(ReadableByteChannel channel, ChannelFunction... functions) { this.channel = channel; this.functions = functions; } @Override public int read(ByteBuffer dst) throws IOException { int pos = dst.position(); int r = 0; try { r = channel.read(dst); return r; } finally { if (r > 0) { for (ChannelFunction func: functions) { func.afterRead(dst, pos, r); } } } } @Override public boolean isOpen() { return channel.isOpen(); } @Override public void close() throws IOException { channel.close(); } } ChannelFunctionStreamSourceChannel.java000066400000000000000000000114541420065311100367570ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/function/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.function; import org.xnio.ChannelListener; import org.xnio.Option; import org.xnio.XnioExecutor; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.concurrent.TimeUnit; /** * @author Norman Maurer */ public class ChannelFunctionStreamSourceChannel implements StreamSourceChannel { private final StreamSourceChannel channel; private final ChannelFunction[] functions; public ChannelFunctionStreamSourceChannel(StreamSourceChannel channel, ChannelFunction... functions) { this.channel = channel; this.functions = functions; } @Override public long transferTo(long position, long count, FileChannel target) throws IOException { return channel.transferTo(position, count, new ChannelFunctionFileChannel(target, functions)); } @Override public long transferTo(long count, ByteBuffer throughBuffer, StreamSinkChannel target) throws IOException { return target.transferFrom(this, count, throughBuffer); } @Override public ChannelListener.Setter getReadSetter() { return channel.getReadSetter(); } @Override public ChannelListener.Setter getCloseSetter() { return channel.getCloseSetter(); } @Override public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { long r = 0; for (int a = offset; a < length; a++) { int i = read(dsts[a]); if (i < 1) { break; } r += i; } return r; } @Override public long read(ByteBuffer[] dsts) throws IOException { long r = 0; for (ByteBuffer buf: dsts) { int i = read(buf); if (i < 1) { break; } r += i; } return r; } @Override public void suspendReads() { channel.suspendReads(); } @Override public void resumeReads() { channel.resumeReads(); } @Override public boolean isReadResumed() { return channel.isReadResumed(); } @Override public void wakeupReads() { channel.wakeupReads(); } @Override public void shutdownReads() throws IOException { channel.shutdownReads(); } @Override public void awaitReadable() throws IOException { channel.awaitReadable(); } @Override public void awaitReadable(long time, TimeUnit timeUnit) throws IOException { channel.awaitReadable(time, timeUnit); } @Override public XnioExecutor getReadThread() { return channel.getReadThread(); } @Override public int read(ByteBuffer dst) throws IOException { int position = dst.position(); int r = channel.read(dst); if (r > 0) { afterReading(dst, position, r); } return r; } @Override public XnioWorker getWorker() { return channel.getWorker(); } @Override public XnioIoThread getIoThread() { return channel.getIoThread(); } @Override public boolean supportsOption(Option option) { return channel.supportsOption(option); } @Override public T getOption(Option option) throws IOException { return channel.getOption(option); } @Override public T setOption(Option option, T value) throws IOException { return channel.setOption(option, value); } private void afterReading(ByteBuffer buffer, int position, int length) throws IOException { for (ChannelFunction func: functions) { func.afterRead(buffer, position, length); } } @Override public boolean isOpen() { return channel.isOpen(); } @Override public void close() throws IOException { channel.close(); } } ChannelFunctionWritableByteChannel.java000066400000000000000000000033121420065311100367320ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/function/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.function; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; /** * @author Norman Maurer */ public class ChannelFunctionWritableByteChannel implements WritableByteChannel { private final ChannelFunction[] functions; private final WritableByteChannel channel; public ChannelFunctionWritableByteChannel(WritableByteChannel channel, ChannelFunction... functions) { this.channel = channel; this.functions = functions; } @Override public int write(ByteBuffer src) throws IOException { for(ChannelFunction func : functions) { int pos = src.position(); func.beforeWrite(src, pos, src.limit() - pos); } return channel.write(src); } @Override public boolean isOpen() { return channel.isOpen(); } @Override public void close() throws IOException { channel.close(); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/000077500000000000000000000000001420065311100272765ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/Handshake.java000066400000000000000000000223371420065311100320360ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol; import io.undertow.util.Headers; import io.undertow.websockets.WebSocketExtension; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketVersion; import io.undertow.websockets.extensions.ExtensionFunction; import io.undertow.websockets.extensions.ExtensionHandshake; import io.undertow.websockets.spi.WebSocketHttpExchange; import org.xnio.IoFuture; import io.undertow.connector.ByteBufferPool; import org.xnio.StreamConnection; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; /** * Abstract base class for doing a WebSocket Handshake. * * @author Mike Brock */ public abstract class Handshake { private final WebSocketVersion version; private final String hashAlgorithm; private final String magicNumber; protected final Set subprotocols; private static final byte[] EMPTY = new byte[0]; private static final Pattern PATTERN = Pattern.compile("\\s*,\\s*"); protected Set availableExtensions = new HashSet<>(); protected boolean allowExtensions; protected Handshake(WebSocketVersion version, String hashAlgorithm, String magicNumber, final Set subprotocols) { this.version = version; this.hashAlgorithm = hashAlgorithm; this.magicNumber = magicNumber; this.subprotocols = subprotocols; } /** * Return the version for which the {@link Handshake} can be used. */ public WebSocketVersion getVersion() { return version; } /** * Return the algorithm that is used to hash during the handshake */ public String getHashAlgorithm() { return hashAlgorithm; } /** * Return the magic number which will be mixed in */ public String getMagicNumber() { return magicNumber; } /** * Return the full url of the websocket location of the given {@link WebSocketHttpExchange} */ protected static String getWebSocketLocation(WebSocketHttpExchange exchange) { String scheme; if ("https".equals(exchange.getRequestScheme())) { scheme = "wss"; } else { scheme = "ws"; } return scheme + "://" + exchange.getRequestHeader(Headers.HOST_STRING) + exchange.getRequestURI(); } /** * Issue the WebSocket upgrade * * @param exchange The {@link WebSocketHttpExchange} for which the handshake and upgrade should occur. */ public final void handshake(final WebSocketHttpExchange exchange) { exchange.putAttachment(WebSocketVersion.ATTACHMENT_KEY, version); handshakeInternal(exchange); } protected abstract void handshakeInternal(final WebSocketHttpExchange exchange); /** * Return {@code true} if this implementation can be used to issue a handshake. */ public abstract boolean matches(WebSocketHttpExchange exchange); /** * Create the {@link WebSocketChannel} from the {@link WebSocketHttpExchange} */ public abstract WebSocketChannel createChannel(WebSocketHttpExchange exchange, final StreamConnection channel, final ByteBufferPool pool); /** * convenience method to perform the upgrade */ protected final void performUpgrade(final WebSocketHttpExchange exchange, final byte[] data) { exchange.setResponseHeader(Headers.CONTENT_LENGTH_STRING, String.valueOf(data.length)); exchange.setResponseHeader(Headers.UPGRADE_STRING, "WebSocket"); exchange.setResponseHeader(Headers.CONNECTION_STRING, "Upgrade"); upgradeChannel(exchange, data); } protected void upgradeChannel(final WebSocketHttpExchange exchange, final byte[] data) { if (data.length > 0) { writePayload(exchange, ByteBuffer.wrap(data)); } else { exchange.endExchange(); } } private static void writePayload(final WebSocketHttpExchange exchange, final ByteBuffer payload) { exchange.sendData(payload).addNotifier(new IoFuture.Notifier() { @Override public void notify(final IoFuture ioFuture, final Object attachment) { if (ioFuture.getStatus() == IoFuture.Status.DONE) { exchange.endExchange(); } else { exchange.close(); } } }, null); } /** * Perform the upgrade using no payload */ protected final void performUpgrade(final WebSocketHttpExchange exchange) { performUpgrade(exchange, EMPTY); } /** * Selects the first matching supported sub protocol and add it the the headers of the exchange. * */ protected final void selectSubprotocol(final WebSocketHttpExchange exchange) { String requestedSubprotocols = exchange.getRequestHeader(Headers.SEC_WEB_SOCKET_PROTOCOL_STRING); if (requestedSubprotocols == null) { return; } String[] requestedSubprotocolArray = PATTERN.split(requestedSubprotocols); String subProtocol = supportedSubprotols(requestedSubprotocolArray); if (subProtocol != null && !subProtocol.isEmpty()) { exchange.setResponseHeader(Headers.SEC_WEB_SOCKET_PROTOCOL_STRING, subProtocol); } } protected final void selectExtensions(final WebSocketHttpExchange exchange) { List requestedExtensions = WebSocketExtension.parse(exchange.getRequestHeader(Headers.SEC_WEB_SOCKET_EXTENSIONS_STRING)); List extensions = selectedExtension(requestedExtensions); if (extensions != null && !extensions.isEmpty()) { exchange.setResponseHeader(Headers.SEC_WEB_SOCKET_EXTENSIONS_STRING, WebSocketExtension.toExtensionHeader(extensions)); } } protected String supportedSubprotols(String[] requestedSubprotocolArray) { for (String p : requestedSubprotocolArray) { String requestedSubprotocol = p.trim(); for (String supportedSubprotocol : subprotocols) { if (requestedSubprotocol.equals(supportedSubprotocol)) { return supportedSubprotocol; } } } return null; } protected List selectedExtension(List extensionList) { List selected = new ArrayList<>(); List configured = new ArrayList<>(); for (WebSocketExtension ext : extensionList) { for (ExtensionHandshake extHandshake : availableExtensions) { WebSocketExtension negotiated = extHandshake.accept(ext); if (negotiated != null && !extHandshake.isIncompatible(configured)) { selected.add(negotiated); configured.add(extHandshake); } } } return selected; } /** * Add a new WebSocket Extension handshake to the list of available extensions. * * @param extension a new {@code ExtensionHandshake} */ public final void addExtension(ExtensionHandshake extension) { availableExtensions.add(extension); allowExtensions = true; } /** * Create the {@code ExtensionFunction} list associated with the negotiated extensions defined in the exchange's response. * * @param exchange the exchange used to retrieve negotiated extensions * @return a list of {@code ExtensionFunction} with the implementation of the extensions */ protected final List initExtensions(final WebSocketHttpExchange exchange) { String extHeader = exchange.getResponseHeaders().get(Headers.SEC_WEB_SOCKET_EXTENSIONS_STRING) != null ? exchange.getResponseHeaders().get(Headers.SEC_WEB_SOCKET_EXTENSIONS_STRING).get(0) : null; List negotiated = new ArrayList<>(); if (extHeader != null) { List extensions = WebSocketExtension.parse(extHeader); if (extensions != null && !extensions.isEmpty()) { for (WebSocketExtension ext : extensions) { for (ExtensionHandshake extHandshake : availableExtensions) { if (extHandshake.getName().equals(ext.getName())) { negotiated.add(extHandshake.create()); } } } } } return negotiated; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version07/000077500000000000000000000000001420065311100311325ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version07/Base64.java000066400000000000000000002376761420065311100330460ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol.version07; import static org.xnio.IoUtils.safeClose; import io.undertow.UndertowLogger; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; /** *

* Encodes and decodes to and from Base64 notation. *

*

* Homepage: http://iharder.net/base64. *

* *

* Example: *

* * String encoded = Base64.encode( myByteArray );
* byte[] myByteArray = Base64.decode( encoded ); * *

* The options parameter, which appears in a few places, is used to pass several pieces of information to the encoder. * In the "higher level" methods such as encodeBytes( bytes, options ) the options parameter can be used to indicate such things * as first gzipping the bytes before encoding them, not inserting linefeeds, and encoding using the URL-safe and Ordered * dialects. *

* *

* Note, according to RFC3548, Section 2.1, implementations should not add * line feeds unless explicitly told to do so. I've got Base64 set to this behavior now, although earlier versions broke lines * by default. *

* *

* The constants defined in Base64 can be OR-ed together to combine options, so you might make a call like this: *

* * String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES ); *

* to compress the data before encoding it and then making the output have newline characters. *

*

* Also... *

* String encoded = Base64.encodeBytes( crazyString.getBytes() ); * * * *

* Change Log: *

*
    *
  • v2.3.7 - Fixed subtle bug when base 64 input stream contained the value 01111111, which is an invalid base 64 character * but should not throw an ArrayIndexOutOfBoundsException either. Led to discovery of mishandling (or potential for better * handling) of other bad input characters. You should now get an IOException if you try decoding something that has bad * characters in it.
  • *
  • v2.3.6 - Fixed bug when breaking lines and the final byte of the encoded string ended in the last column; the buffer was * not properly shrunk and contained an extra (null) byte that made it into the string.
  • *
  • v2.3.5 - Fixed bug in {@link #encodeFromFile} where estimated buffer size was wrong for files of size 31, 34, and 37 * bytes.
  • *
  • v2.3.4 - Fixed bug when working with gzipped streams whereby flushing the Base64.OutputStream closed the Base64 encoding * (by padding with equals signs) too soon. Also added an option to suppress the automatic decoding of gzipped streams. Also * added experimental support for specifying a class loader when using the * {@link #decodeToObject(String, int, ClassLoader)} method.
  • *
  • v2.3.3 - Changed default char encoding to US-ASCII which reduces the internal Java footprint with its CharEncoders and so * forth. Fixed some javadocs that were inconsistent. Removed imports and specified things like java.io.IOException explicitly * inline.
  • *
  • v2.3.2 - Reduced memory footprint! Finally refined the "guessing" of how big the final encoded data will be so that the * code doesn't have to create two output arrays: an oversized initial one and then a final, exact-sized one. Big win when using * the {@link #encodeBytesToBytes(byte[])} family of methods (and not using the gzip options which uses a different mechanism * with streams and stuff).
  • *
  • v2.3.1 - Added {@link #encodeBytesToBytes(byte[], int, int, int)} and some similar helper methods to be more efficient * with memory by not returning a String but just a byte array.
  • *
  • v2.3 - This is not a drop-in replacement! This is two years of comments and bug fixes queued up and * finally executed. Thanks to everyone who sent me stuff, and I'm sorry I wasn't able to distribute your fixes to everyone * else. Much bad coding was cleaned up including throwing exceptions where necessary instead of returning null values or * something similar. Here are some changes that may affect you: *
      *
    • Does not break lines, by default. This is to keep in compliance with RFC3548.
    • *
    • Throws exceptions instead of returning null values. Because some operations (especially those that may permit * the GZIP option) use IO streams, there is a possibility of an java.io.IOException being thrown. After some discussion and * thought, I've changed the behavior of the methods to throw java.io.IOExceptions rather than return null if ever there's an * error. I think this is more appropriate, though it will require some changes to your code. Sorry, it should have been done * this way to begin with.
    • *
    • Removed all references to System.out, System.err, and the like. Shame on me. All I can say is sorry they were * ever there.
    • *
    • Throws NullPointerExceptions and IllegalArgumentExceptions as needed such as when passed arrays are null or * offsets are invalid.
    • *
    • Cleaned up as much javadoc as I could to avoid any javadoc warnings. This was especially annoying before for people who * were thorough in their own projects and then had gobs of javadoc warnings on this file.
    • *
    *
  • v2.2.1 - Fixed bug using URL_SAFE and ORDERED encodings. Fixed bug when using very small files (~< 40 bytes).
  • *
  • v2.2 - Added some helper methods for encoding/decoding directly from one file to the next. Also added a main() method to * support command line encoding/decoding from one file to the next. Also added these Base64 dialects: *
      *
    1. The default is RFC3548 format.
    2. *
    3. Calling Base64.setFormat(Base64.BASE64_FORMAT.URLSAFE_FORMAT) generates URL and file name friendly format as described in * Section 4 of RFC3548. http://www.faqs.org/rfcs/rfc3548.html
    4. *
    5. Calling Base64.setFormat(Base64.BASE64_FORMAT.ORDERED_FORMAT) generates URL and file name friendly format that preserves * lexical ordering as described in http://www.faqs.org/qa/rfcc-1940.html
    6. *
    * Special thanks to Jim Kellerman at http://www.powerset.com/ for contributing the new * Base64 dialects.
  • * *
  • v2.1 - Cleaned up javadoc comments and unused variables and methods. Added some convenience methods for reading and * writing to and from files.
  • *
  • v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems with other encodings (like EBCDIC).
  • *
  • v2.0.1 - Fixed an error when decoding a single byte, that is, when the encoded data was a single byte.
  • *
  • v2.0 - I got rid of methods that used booleans to set options. Now everything is more consolidated and cleaner. The code * now detects when data that's being decoded is gzip-compressed and will decompress it automatically. Generally things are * cleaner. You'll probably have to change some method calls that you were making to support the new options format ( * ints that you "OR" together).
  • *
  • v1.5.1 - Fixed bug when decompressing and decoding to a byte[] using decode( String s, boolean gzipCompressed ). * Added the ability to "suspend" encoding in the Output Stream so you can turn on and off the encoding if you need to embed * base64 data in an otherwise "normal" stream (like an XML file).
  • *
  • v1.5 - Output stream passes on flush() command but doesn't do anything itself. This helps when using GZIP streams. Added * the ability to GZip-compress objects before encoding them.
  • *
  • v1.4 - Added helper methods to read/write files.
  • *
  • v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.
  • *
  • v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream where last buffer being read, if not * completely full, was not returned.
  • *
  • v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.
  • *
  • v1.3.3 - Fixed I/O streams which were totally messed up.
  • *
* *

* I am placing this code in the Public Domain. Do with it as you will. This software comes with no guarantees or warranties but * with plenty of well-wishing instead! Please visit http://iharder.net/base64 * periodically to check for updates or to contribute improvements. *

* * @author Robert Harder * @author rob@iharder.net * @version 2.3.7 */ class Base64 { /* ******** P U B L I C F I E L D S ******** */ /** No options specified. Value is zero. */ public static final int NO_OPTIONS = 0; /** Specify encoding in first bit. Value is one. */ public static final int ENCODE = 1; /** Specify decoding in first bit. Value is zero. */ public static final int DECODE = 0; /** Specify that data should be gzip-compressed in second bit. Value is two. */ public static final int GZIP = 2; /** Specify that gzipped data should not be automatically gunzipped. */ public static final int DONT_GUNZIP = 4; /** Do break lines when encoding. Value is 8. */ public static final int DO_BREAK_LINES = 8; /** * Encode using Base64-like encoding that is URL- and Filename-safe as described in Section 4 of RFC3548: http://www.faqs.org/rfcs/rfc3548.html. It is important to note that data * encoded this way is not officially valid Base64, or at the very least should not be called Base64 without also * specifying that is was encoded using the URL- and Filename-safe dialect. */ public static final int URL_SAFE = 16; /** * Encode using the special "ordered" dialect of Base64 described here: http://www.faqs.org/qa/rfcc-1940.html. */ public static final int ORDERED = 32; /* ******** P R I V A T E F I E L D S ******** */ /** Maximum line length (76) of Base64 output. */ private static final int MAX_LINE_LENGTH = 76; /** The equals sign (=) as a byte. */ private static final byte EQUALS_SIGN = (byte) '='; /** The new line character (\n) as a byte. */ private static final byte NEW_LINE = (byte) '\n'; private static final byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding private static final byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding /* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ /** The 64 valid Base64 values. */ /* Host platform me be something funny like EBCDIC, so we hardcode these values. */ private static final byte[] _STANDARD_ALPHABET = { (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '+', (byte) '/' }; /** * Translates a Base64 value to either its 6-bit reconstruction value or a negative number indicating some other meaning. **/ private static final byte[] _STANDARD_DECODABET = { -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 -5, -5, // Whitespace: Tab and Linefeed -9, -9, // Decimal 11 - 12 -5, // Whitespace: Carriage Return -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 -9, -9, -9, -9, -9, // Decimal 27 - 31 -5, // Whitespace: Space -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 62, // Plus sign at decimal 43 -9, -9, -9, // Decimal 44 - 46 63, // Slash at decimal 47 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine -9, -9, -9, // Decimal 58 - 60 -1, // Equals sign at decimal 61 -9, -9, -9, // Decimal 62 - 64 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' -9, -9, -9, -9, -9 // Decimal 123 - 127 , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 }; /* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ /** * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: http://www.faqs.org/rfcs/rfc3548.html. Notice that the last two bytes * become "hyphen" and "underscore" instead of "plus" and "slash." */ private static final byte[] _URL_SAFE_ALPHABET = { (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '-', (byte) '_' }; /** * Used in decoding URL- and Filename-safe dialects of Base64. */ private static final byte[] _URL_SAFE_DECODABET = { -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 -5, -5, // Whitespace: Tab and Linefeed -9, -9, // Decimal 11 - 12 -5, // Whitespace: Carriage Return -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 -9, -9, -9, -9, -9, // Decimal 27 - 31 -5, // Whitespace: Space -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 -9, // Plus sign at decimal 43 -9, // Decimal 44 62, // Minus sign at decimal 45 -9, // Decimal 46 -9, // Slash at decimal 47 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine -9, -9, -9, // Decimal 58 - 60 -1, // Equals sign at decimal 61 -9, -9, -9, // Decimal 62 - 64 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' -9, -9, -9, -9, // Decimal 91 - 94 63, // Underscore at decimal 95 -9, // Decimal 96 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' -9, -9, -9, -9, -9 // Decimal 123 - 127 , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 }; /* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ /** * I don't get the point of this technique, but someone requested it, and it is described here: http://www.faqs.org/qa/rfcc-1940.html. */ private static final byte[] _ORDERED_ALPHABET = { (byte) '-', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) '_', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z' }; /** * Used in decoding the "ordered" dialect of Base64. */ private static final byte[] _ORDERED_DECODABET = { -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 -5, -5, // Whitespace: Tab and Linefeed -9, -9, // Decimal 11 - 12 -5, // Whitespace: Carriage Return -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 -9, -9, -9, -9, -9, // Decimal 27 - 31 -5, // Whitespace: Space -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 -9, // Plus sign at decimal 43 -9, // Decimal 44 0, // Minus sign at decimal 45 -9, // Decimal 46 -9, // Slash at decimal 47 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // Numbers zero through nine -9, -9, -9, // Decimal 58 - 60 -1, // Equals sign at decimal 61 -9, -9, -9, // Decimal 62 - 64 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, // Letters 'A' through 'M' 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, // Letters 'N' through 'Z' -9, -9, -9, -9, // Decimal 91 - 94 37, // Underscore at decimal 95 -9, // Decimal 96 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, // Letters 'a' through 'm' 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // Letters 'n' through 'z' -9, -9, -9, -9, -9 // Decimal 123 - 127 , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 }; /* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ /** * Returns one of the _SOMETHING_ALPHABET byte arrays depending on the options specified. It's possible, though silly, to * specify ORDERED and URLSAFE in which case one of them will be picked, though there is no guarantee as to which one * will be picked. */ private static byte[] getAlphabet(int options) { if ((options & URL_SAFE) == URL_SAFE) { return _URL_SAFE_ALPHABET; } else if ((options & ORDERED) == ORDERED) { return _ORDERED_ALPHABET; } else { return _STANDARD_ALPHABET; } } // end getAlphabet /** * Returns one of the _SOMETHING_DECODABET byte arrays depending on the options specified. It's possible, though silly, to * specify ORDERED and URL_SAFE in which case one of them will be picked, though there is no guarantee as to which one will * be picked. */ private static byte[] getDecodabet(int options) { if ((options & URL_SAFE) == URL_SAFE) { return _URL_SAFE_DECODABET; } else if ((options & ORDERED) == ORDERED) { return _ORDERED_DECODABET; } else { return _STANDARD_DECODABET; } } // end getAlphabet /** Defeats instantiation. */ private Base64() { } /* ******** E N C O D I N G M E T H O D S ******** */ /** * Encodes up to the first three bytes of array threeBytes and returns a four-byte array in Base64 notation. The * actual number of significant bytes in your array is given by numSigBytes. The array threeBytes * needs only be as big as numSigBytes. Code can reuse a byte array by passing a four-byte array as * b4. * * @param b4 A reusable byte array to reduce array instantiation * @param threeBytes the array to convert * @param numSigBytes the number of significant bytes in your array * @return four byte array in Base64 notation. * @since 1.5.1 */ private static byte[] encode3to4(byte[] b4, byte[] threeBytes, int numSigBytes, int options) { encode3to4(threeBytes, 0, numSigBytes, b4, 0, options); return b4; } // end encode3to4 /** *

* Encodes up to three bytes of the array source and writes the resulting four Base64 bytes to * destination. The source and destination arrays can be manipulated anywhere along their length by specifying * srcOffset and destOffset. This method does not check to make sure your arrays are large enough to * accomodate srcOffset + 3 for the source array or destOffset + 4 for the * destination array. The actual number of significant bytes in your array is given by numSigBytes. *

*

* This is the lowest level of the encoding methods with all possible parameters. *

* * @param source the array to convert * @param srcOffset the index where conversion begins * @param numSigBytes the number of significant bytes in your array * @param destination the array to hold the conversion * @param destOffset the index where output will be put * @return the destination array * @since 1.3 */ private static byte[] encode3to4(byte[] source, int srcOffset, int numSigBytes, byte[] destination, int destOffset, int options) { byte[] ALPHABET = getAlphabet(options); // 1 2 3 // 01234567890123456789012345678901 Bit position // --------000000001111111122222222 Array position from threeBytes // --------| || || || | Six bit groups to index ALPHABET // >>18 >>12 >> 6 >> 0 Right shift necessary // 0x3f 0x3f 0x3f Additional AND // Create buffer with zero-padding if there are only one or two // significant bytes passed in the array. // We have to shift left 24 in order to flush out the 1's that appear // when Java treats a value as negative that is cast from a byte to an int. int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); switch (numSigBytes) { case 3: destination[destOffset] = ALPHABET[(inBuff >>> 18)]; destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f]; return destination; case 2: destination[destOffset] = ALPHABET[(inBuff >>> 18)]; destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; destination[destOffset + 3] = EQUALS_SIGN; return destination; case 1: destination[destOffset] = ALPHABET[(inBuff >>> 18)]; destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; destination[destOffset + 2] = EQUALS_SIGN; destination[destOffset + 3] = EQUALS_SIGN; return destination; default: return destination; } // end switch } // end encode3to4 /** * Performs Base64 encoding on the raw ByteBuffer, writing it to the encoded ByteBuffer. This is * an experimental feature. Currently it does not pass along any options (such as {@link #DO_BREAK_LINES} or {@link #GZIP}. * * @param raw input buffer * @param encoded output buffer * @since 2.3 */ public static void encode(java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded) { byte[] raw3 = new byte[3]; byte[] enc4 = new byte[4]; while (raw.hasRemaining()) { int rem = Math.min(3, raw.remaining()); raw.get(raw3, 0, rem); Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS); encoded.put(enc4); } // end input remaining } /** * Performs Base64 encoding on the raw ByteBuffer, writing it to the encoded CharBuffer. This is * an experimental feature. Currently it does not pass along any options (such as {@link #DO_BREAK_LINES} or {@link #GZIP}. * * @param raw input buffer * @param encoded output buffer * @since 2.3 */ public static void encode(java.nio.ByteBuffer raw, java.nio.CharBuffer encoded) { byte[] raw3 = new byte[3]; byte[] enc4 = new byte[4]; while (raw.hasRemaining()) { int rem = Math.min(3, raw.remaining()); raw.get(raw3, 0, rem); Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS); for (int i = 0; i < 4; i++) { encoded.put((char) (enc4[i] & 0xFF)); } } // end input remaining } /** * Serializes an object and returns the Base64-encoded version of that serialized object. * *

* As of v 2.3, if the object cannot be serialized or there is another error, the method will throw an java.io.IOException. * This is new to v2.3! In earlier versions, it just returned a null value, but in retrospect that's a pretty poor * way to handle it. *

* * The object is not GZip-compressed before being encoded. * * @param serializableObject The object to encode * @return The Base64-encoded object * @throws java.io.IOException if there is an error * @throws NullPointerException if serializedObject is null * @since 1.4 */ public static String encodeObject(java.io.Serializable serializableObject) throws java.io.IOException { return encodeObject(serializableObject, NO_OPTIONS); } // end encodeObject /** * Serializes an object and returns the Base64-encoded version of that serialized object. * *

* As of v 2.3, if the object cannot be serialized or there is another error, the method will throw an java.io.IOException. * This is new to v2.3! In earlier versions, it just returned a null value, but in retrospect that's a pretty poor * way to handle it. *

* * The object is not GZip-compressed before being encoded. *

* Example options: * *

     *   GZIP: gzip-compresses object before encoding it.
     *   DO_BREAK_LINES: break lines at 76 characters
     * 
*

* Example: encodeObject( myObj, Base64.GZIP ) or *

* Example: encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES ) * * @param serializableObject The object to encode * @param options Specified options * @return The Base64-encoded object * @see Base64#GZIP * @see Base64#DO_BREAK_LINES * @throws java.io.IOException if there is an error * @since 2.0 */ public static String encodeObject(java.io.Serializable serializableObject, int options) throws java.io.IOException { if (serializableObject == null) { throw new NullPointerException("Cannot serialize a null object."); } // end if: null // Streams java.io.ByteArrayOutputStream baos = null; java.io.OutputStream b64os = null; java.util.zip.GZIPOutputStream gzos = null; java.io.ObjectOutputStream oos = null; try { // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); b64os = new Base64.OutputStream(baos, ENCODE | options); if ((options & GZIP) != 0) { // Gzip gzos = new java.util.zip.GZIPOutputStream(b64os); oos = new java.io.ObjectOutputStream(gzos); } else { // Not gzipped oos = new java.io.ObjectOutputStream(b64os); } oos.writeObject(serializableObject); } // end try catch (java.io.IOException e) { // Catch it and then throw it immediately so that // the finally{} block is called for cleanup. throw e; } // end catch finally { safeClose(oos); safeClose(gzos); safeClose(b64os); safeClose(baos); } // end finally // Return value according to relevant encoding. return new String(baos.toByteArray(), StandardCharsets.US_ASCII); } // end encode /** * Encodes a byte array into Base64 notation. Does not GZip-compress data. * * @param source The data to convert * @return The data in Base64-encoded form * @throws NullPointerException if source array is null * @since 1.4 */ public static String encodeBytes(byte[] source) { // Since we're not going to have the GZIP encoding turned on, // we're not going to have an java.io.IOException thrown, so // we should not force the user to have to catch it. String encoded = null; try { encoded = encodeBytes(source, 0, source.length, NO_OPTIONS); } catch (java.io.IOException ex) { assert false : ex.getMessage(); } // end catch assert encoded != null; return encoded; } // end encodeBytes /** * Encodes a byte array into Base64 notation. *

* Example options: * *

     *   GZIP: gzip-compresses object before encoding it.
     *   DO_BREAK_LINES: break lines at 76 characters
     *     Note: Technically, this makes your encoding non-compliant.
     * 
*

* Example: encodeBytes( myData, Base64.GZIP ) or *

* Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) * * *

* As of v 2.3, if there is an error with the GZIP stream, the method will throw an java.io.IOException. This is new to * v2.3! In earlier versions, it just returned a null value, but in retrospect that's a pretty poor way to handle it. *

* * * @param source The data to convert * @param options Specified options * @return The Base64-encoded data as a String * @see Base64#GZIP * @see Base64#DO_BREAK_LINES * @throws java.io.IOException if there is an error * @throws NullPointerException if source array is null * @since 2.0 */ public static String encodeBytes(byte[] source, int options) throws java.io.IOException { return encodeBytes(source, 0, source.length, options); } // end encodeBytes /** * Encodes a byte array into Base64 notation. Does not GZip-compress data. * *

* As of v 2.3, if there is an error, the method will throw an java.io.IOException. This is new to v2.3! In earlier * versions, it just returned a null value, but in retrospect that's a pretty poor way to handle it. *

* * * @param source The data to convert * @param off Offset in array where conversion should begin * @param len Length of data to convert * @return The Base64-encoded data as a String * @throws NullPointerException if source array is null * @throws IllegalArgumentException if source array, offset, or length are invalid * @since 1.4 */ public static String encodeBytes(byte[] source, int off, int len) { // Since we're not going to have the GZIP encoding turned on, // we're not going to have an java.io.IOException thrown, so // we should not force the user to have to catch it. String encoded = null; try { encoded = encodeBytes(source, off, len, NO_OPTIONS); } catch (java.io.IOException ex) { assert false : ex.getMessage(); } // end catch assert encoded != null; return encoded; } // end encodeBytes /** * Encodes a byte array into Base64 notation. *

* Example options: * *

     *   GZIP: gzip-compresses object before encoding it.
     *   DO_BREAK_LINES: break lines at 76 characters
     *     Note: Technically, this makes your encoding non-compliant.
     * 
*

* Example: encodeBytes( myData, Base64.GZIP ) or *

* Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) * * *

* As of v 2.3, if there is an error with the GZIP stream, the method will throw an java.io.IOException. This is new to * v2.3! In earlier versions, it just returned a null value, but in retrospect that's a pretty poor way to handle it. *

* * * @param source The data to convert * @param off Offset in array where conversion should begin * @param len Length of data to convert * @param options Specified options * @return The Base64-encoded data as a String * @see Base64#GZIP * @see Base64#DO_BREAK_LINES * @throws java.io.IOException if there is an error * @throws NullPointerException if source array is null * @throws IllegalArgumentException if source array, offset, or length are invalid * @since 2.0 */ public static String encodeBytes(byte[] source, int off, int len, int options) throws java.io.IOException { byte[] encoded = encodeBytesToBytes(source, off, len, options); // Return value according to relevant encoding. return new String(encoded, StandardCharsets.US_ASCII); } // end encodeBytes /** * Similar to {@link #encodeBytes(byte[])} but returns a byte array instead of instantiating a String. This is more * efficient if you're working with I/O streams and have large data sets to encode. * * * @param source The data to convert * @return The Base64-encoded data as a byte[] (of ASCII characters) * @throws NullPointerException if source array is null * @since 2.3.1 */ public static byte[] encodeBytesToBytes(byte[] source) { byte[] encoded = null; try { encoded = encodeBytesToBytes(source, 0, source.length, Base64.NO_OPTIONS); } catch (java.io.IOException ex) { assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); } return encoded; } /** * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns a byte array instead of instantiating a String. This * is more efficient if you're working with I/O streams and have large data sets to encode. * * * @param source The data to convert * @param off Offset in array where conversion should begin * @param len Length of data to convert * @param options Specified options * @return The Base64-encoded data as a String * @see Base64#GZIP * @see Base64#DO_BREAK_LINES * @throws java.io.IOException if there is an error * @throws NullPointerException if source array is null * @throws IllegalArgumentException if source array, offset, or length are invalid * @since 2.3.1 */ public static byte[] encodeBytesToBytes(byte[] source, int off, int len, int options) throws java.io.IOException { if (source == null) { throw new NullPointerException("Cannot serialize a null array."); } // end if: null if (off < 0) { throw new IllegalArgumentException("Cannot have negative offset: " + off); } // end if: off < 0 if (len < 0) { throw new IllegalArgumentException("Cannot have length offset: " + len); } // end if: len < 0 if (off + len > source.length) { throw new IllegalArgumentException(String.format( "Cannot have offset of %d and length of %d with array of length %d", off, len, source.length)); } // end if: off < 0 // Compress? if ((options & GZIP) != 0) { java.io.ByteArrayOutputStream baos = null; java.util.zip.GZIPOutputStream gzos = null; Base64.OutputStream b64os = null; try { // GZip -> Base64 -> ByteArray baos = new java.io.ByteArrayOutputStream(); b64os = new Base64.OutputStream(baos, ENCODE | options); gzos = new java.util.zip.GZIPOutputStream(b64os); gzos.write(source, off, len); gzos.close(); } // end try catch (java.io.IOException e) { // Catch it and then throw it immediately so that // the finally{} block is called for cleanup. throw e; } // end catch finally { safeClose(gzos); safeClose(b64os); safeClose(baos); } // end finally return baos.toByteArray(); } // end if: compress // Else, don't compress. Better not to use streams at all then. else { boolean breakLines = (options & DO_BREAK_LINES) != 0; // int len43 = len * 4 / 3; // byte[] outBuff = new byte[ ( len43 ) // Main 4:3 // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines // Try to determine more precisely how big the array needs to be. // If we get it right, we don't have to do an array copy, and // we save a bunch of memory. int encLen = (len / 3) * 4 + (len % 3 > 0 ? 4 : 0); // Bytes needed for actual encoding if (breakLines) { encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters } byte[] outBuff = new byte[encLen]; int d = 0; int e = 0; int len2 = len - 2; int lineLength = 0; for (; d < len2; d += 3, e += 4) { encode3to4(source, d + off, 3, outBuff, e, options); lineLength += 4; if (breakLines && lineLength >= MAX_LINE_LENGTH) { outBuff[e + 4] = NEW_LINE; e++; lineLength = 0; } // end if: end of line } // en dfor: each piece of array if (d < len) { encode3to4(source, d + off, len - d, outBuff, e, options); e += 4; } // end if: some padding needed // Only resize array if we didn't guess it right. if (e <= outBuff.length - 1) { // If breaking lines and the last byte falls right at // the line length (76 bytes per line), there will be // one extra byte, and the array will need to be resized. // Not too bad of an estimate on array size, I'd say. byte[] finalOut = new byte[e]; System.arraycopy(outBuff, 0, finalOut, 0, e); // System.err.println("Having to resize array from " + outBuff.length + " to " + e ); return finalOut; } else { // System.err.println("No need to resize array."); return outBuff; } } // end else: don't compress } // end encodeBytesToBytes /* ******** D E C O D I N G M E T H O D S ******** */ /** * Decodes four bytes from array source and writes the resulting bytes (up to three of them) to * destination. The source and destination arrays can be manipulated anywhere along their length by specifying * srcOffset and destOffset. This method does not check to make sure your arrays are large enough to * accommodate srcOffset + 4 for the source array or destOffset + 3 for the * destination array. This method returns the actual number of bytes that were converted from the Base64 * encoding. *

* This is the lowest level of the decoding methods with all possible parameters. *

* * * @param source the array to convert * @param srcOffset the index where conversion begins * @param destination the array to hold the conversion * @param destOffset the index where output will be put * @param options alphabet type is pulled from this (standard, url-safe, ordered) * @return the number of decoded bytes converted * @throws NullPointerException if source or destination arrays are null * @throws IllegalArgumentException if srcOffset or destOffset are invalid or there is not enough room in the array. * @since 1.3 */ private static int decode4to3(byte[] source, int srcOffset, byte[] destination, int destOffset, int options) { // Lots of error checking and exception throwing if (source == null) { throw new NullPointerException("Source array was null."); } // end if if (destination == null) { throw new NullPointerException("Destination array was null."); } // end if if (srcOffset < 0 || srcOffset + 3 >= source.length) { throw new IllegalArgumentException(String.format( "Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset)); } // end if if (destOffset < 0 || destOffset + 2 >= destination.length) { throw new IllegalArgumentException(String.format( "Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset)); } // end if byte[] DECODABET = getDecodabet(options); // Example: Dk== if (source[srcOffset + 2] == EQUALS_SIGN) { // Two ways to do the same thing. Don't know which way I like best. // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12); destination[destOffset] = (byte) (outBuff >>> 16); return 1; } // Example: DkL= else if (source[srcOffset + 3] == EQUALS_SIGN) { // Two ways to do the same thing. Don't know which way I like best. // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6); destination[destOffset] = (byte) (outBuff >>> 16); destination[destOffset + 1] = (byte) (outBuff >>> 8); return 2; } // Example: DkLE else { // Two ways to do the same thing. Don't know which way I like best. // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6) | ((DECODABET[source[srcOffset + 3]] & 0xFF)); destination[destOffset] = (byte) (outBuff >> 16); destination[destOffset + 1] = (byte) (outBuff >> 8); destination[destOffset + 2] = (byte) (outBuff); return 3; } } // end decodeToBytes /** * Low-level access to decoding ASCII characters in the form of a byte array. Ignores GUNZIP option, if it's * set. This is not generally a recommended method, although it is used internally as part of the decoding process. * Special case: if len = 0, an empty array is returned. Still, if you need more speed and reduced memory footprint (and * aren't gzipping), consider this method. * * @param source The Base64 encoded data * @return decoded data * @since 2.3.1 */ public static byte[] decode(byte[] source) throws java.io.IOException { byte[] decoded = null; // try { decoded = decode(source, 0, source.length, Base64.NO_OPTIONS); // } catch( java.io.IOException ex ) { // assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); // } return decoded; } /** * Low-level access to decoding ASCII characters in the form of a byte array. Ignores GUNZIP option, if it's * set. This is not generally a recommended method, although it is used internally as part of the decoding process. * Special case: if len = 0, an empty array is returned. Still, if you need more speed and reduced memory footprint (and * aren't gzipping), consider this method. * * @param source The Base64 encoded data * @param off The offset of where to begin decoding * @param len The length of characters to decode * @param options Can specify options such as alphabet type to use * @return decoded data * @throws java.io.IOException If bogus characters exist in source data * @since 1.3 */ public static byte[] decode(byte[] source, int off, int len, int options) throws java.io.IOException { // Lots of error checking and exception throwing if (source == null) { throw new NullPointerException("Cannot decode null source array."); } // end if if (off < 0 || off + len > source.length) { throw new IllegalArgumentException(String.format( "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len)); } // end if if (len == 0) { return new byte[0]; } else if (len < 4) { throw new IllegalArgumentException( "Base64-encoded string must have at least four characters, but length specified was " + len); } // end if byte[] DECODABET = getDecodabet(options); int len34 = len * 3 / 4; // Estimate on array size byte[] outBuff = new byte[len34]; // Upper limit on size of output int outBuffPosn = 0; // Keep track of where we're writing byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space int b4Posn = 0; // Keep track of four byte input buffer int i = 0; // Source array counter byte sbiDecode = 0; // Special value from DECODABET for (i = off; i < off + len; i++) { // Loop through source sbiDecode = DECODABET[source[i] & 0xFF]; // White space, Equals sign, or legit Base64 character // Note the values such as -5 and -9 in the // DECODABETs at the top of the file. if (sbiDecode >= WHITE_SPACE_ENC) { if (sbiDecode >= EQUALS_SIGN_ENC) { b4[b4Posn++] = source[i]; // Save non-whitespace if (b4Posn > 3) { // Time to decode? outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, options); b4Posn = 0; // If that was the equals sign, break out of 'for' loop if (source[i] == EQUALS_SIGN) { break; } // end if: equals sign } // end if: quartet built } // end if: equals sign or better } // end if: white space, equals sign or better else { // There's a bad input character in the Base64 stream. throw new java.io.IOException(String.format("Bad Base64 input character decimal %d in array position %d", ((int) source[i]) & 0xFF, i)); } // end else: } // each input character byte[] out = new byte[outBuffPosn]; System.arraycopy(outBuff, 0, out, 0, outBuffPosn); return out; } // end decode /** * Decodes data from Base64 notation, automatically detecting gzip-compressed data and decompressing it. * * @param s the string to decode * @return the decoded data * @throws java.io.IOException If there is a problem * @since 1.4 */ public static byte[] decode(String s) throws java.io.IOException { return decode(s, NO_OPTIONS); } /** * Decodes data from Base64 notation, automatically detecting gzip-compressed data and decompressing it. * * @param s the string to decode * @param options encode options such as URL_SAFE * @return the decoded data * @throws java.io.IOException if there is an error * @throws NullPointerException if s is null * @since 1.4 */ public static byte[] decode(String s, int options) throws java.io.IOException { if (s == null) { throw new NullPointerException("Input string was null."); } // end if byte[] bytes = s.getBytes(StandardCharsets.US_ASCII); // // Decode bytes = decode(bytes, 0, bytes.length, options); // Check to see if it's gzip-compressed // GZIP Magic Two-Byte Number: 0x8b1f (35615) boolean dontGunzip = (options & DONT_GUNZIP) != 0; if ((bytes != null) && (bytes.length >= 4) && (!dontGunzip)) { int head = ((int) bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); if (java.util.zip.GZIPInputStream.GZIP_MAGIC == head) { java.io.ByteArrayInputStream bais = null; java.util.zip.GZIPInputStream gzis = null; java.io.ByteArrayOutputStream baos = null; byte[] buffer = new byte[2048]; int length = 0; try { baos = new java.io.ByteArrayOutputStream(); bais = new java.io.ByteArrayInputStream(bytes); gzis = new java.util.zip.GZIPInputStream(bais); while ((length = gzis.read(buffer)) >= 0) { baos.write(buffer, 0, length); } // end while: reading input // No error? Get new bytes. bytes = baos.toByteArray(); } // end try catch (java.io.IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); // Just return originally-decoded bytes } // end catch finally { safeClose(baos); safeClose(gzis); safeClose(bais); } // end finally } // end if: gzipped } // end if: bytes.length >= 2 return bytes; } // end decode /** * Attempts to decode Base64 data and deserialize a Java Object within. Returns null if there was an error. * * @param encodedObject The Base64 data to decode * @return The decoded and deserialized object * @throws NullPointerException if encodedObject is null * @throws java.io.IOException if there is a general error * @throws ClassNotFoundException if the decoded object is of a class that cannot be found by the JVM * @since 1.5 */ public static Object decodeToObject(String encodedObject) throws java.io.IOException, ClassNotFoundException { return decodeToObject(encodedObject, NO_OPTIONS, null); } /** * Attempts to decode Base64 data and deserialize a Java Object within. Returns null if there was an error. If * loader is not null, it will be the class loader used when deserializing. * * @param encodedObject The Base64 data to decode * @param options Various parameters related to decoding * @param loader Optional class loader to use in deserializing classes. * @return The decoded and deserialized object * @throws NullPointerException if encodedObject is null * @throws java.io.IOException if there is a general error * @throws ClassNotFoundException if the decoded object is of a class that cannot be found by the JVM * @since 2.3.4 */ public static Object decodeToObject(String encodedObject, int options, final ClassLoader loader) throws java.io.IOException, ClassNotFoundException { // Decode and gunzip if necessary byte[] objBytes = decode(encodedObject, options); java.io.ByteArrayInputStream bais = null; java.io.ObjectInputStream ois = null; Object obj = null; try { bais = new java.io.ByteArrayInputStream(objBytes); // If no custom class loader is provided, use Java's builtin OIS. if (loader == null) { ois = new java.io.ObjectInputStream(bais); } // end if: no loader provided // Else make a customized object input stream that uses // the provided class loader. else { ois = new java.io.ObjectInputStream(bais) { @Override public Class resolveClass(java.io.ObjectStreamClass streamClass) throws java.io.IOException, ClassNotFoundException { Class c = Class.forName(streamClass.getName(), false, loader); if (c == null) { return super.resolveClass(streamClass); } else { return c; // Class loader knows of this class. } // end else: not null } // end resolveClass }; // end ois } // end else: no custom class loader obj = ois.readObject(); } // end try catch (java.io.IOException e) { throw e; // Catch and throw in order to execute finally{} } // end catch catch (ClassNotFoundException e) { throw e; // Catch and throw in order to execute finally{} } // end catch finally { safeClose(bais); safeClose(ois); } // end finally return obj; } // end decodeObject /** * Convenience method for encoding data to a file. * *

* As of v 2.3, if there is a error, the method will throw an java.io.IOException. This is new to v2.3! In earlier * versions, it just returned false, but in retrospect that's a pretty poor way to handle it. *

* * @param dataToEncode byte array of data to encode in base64 form * @param filename Filename for saving encoded data * @throws java.io.IOException if there is an error * @throws NullPointerException if dataToEncode is null * @since 2.1 */ public static void encodeToFile(byte[] dataToEncode, String filename) throws java.io.IOException { if (dataToEncode == null) { throw new NullPointerException("Data to encode was null."); } // end iff Base64.OutputStream bos = null; try { bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.ENCODE); bos.write(dataToEncode); } // end try catch (java.io.IOException e) { throw e; // Catch and throw to execute finally{} block } // end catch: java.io.IOException finally { safeClose(bos); } // end finally } // end encodeToFile /** * Convenience method for decoding data to a file. * *

* As of v 2.3, if there is a error, the method will throw an java.io.IOException. This is new to v2.3! In earlier * versions, it just returned false, but in retrospect that's a pretty poor way to handle it. *

* * @param dataToDecode Base64-encoded data as a string * @param filename Filename for saving decoded data * @throws java.io.IOException if there is an error * @since 2.1 */ public static void decodeToFile(String dataToDecode, String filename) throws java.io.IOException { Base64.OutputStream bos = null; try { bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.DECODE); bos.write(dataToDecode.getBytes(StandardCharsets.US_ASCII)); } // end try catch (java.io.IOException e) { throw e; // Catch and throw to execute finally{} block } // end catch: java.io.IOException finally { safeClose(bos); } // end finally } // end decodeToFile /** * Convenience method for reading a base64-encoded file and decoding it. * *

* As of v 2.3, if there is a error, the method will throw an java.io.IOException. This is new to v2.3! In earlier * versions, it just returned false, but in retrospect that's a pretty poor way to handle it. *

* * @param filename Filename for reading encoded data * @return decoded byte array * @throws java.io.IOException if there is an error * @since 2.1 */ public static byte[] decodeFromFile(String filename) throws java.io.IOException { byte[] decodedData = null; Base64.InputStream bis = null; try { // Set up some useful variables Path file = Paths.get(filename); byte[] buffer; int length = 0; int numBytes; // Check for size of file if (Files.size(file) > Integer.MAX_VALUE) { throw new java.io.IOException("File is too big for this convenience method (" + Files.size(file) + " bytes)."); } // end if: file too big for int index buffer = new byte[(int) Files.size(file)]; // Open a stream bis = new Base64.InputStream(new java.io.BufferedInputStream(Files.newInputStream(file)), Base64.DECODE); // Read until done while ((numBytes = bis.read(buffer, length, 4096)) >= 0) { length += numBytes; } // end while // Save in a variable to return decodedData = new byte[length]; System.arraycopy(buffer, 0, decodedData, 0, length); } // end try catch (java.io.IOException e) { throw e; // Catch and release to execute finally{} } // end catch: java.io.IOException finally { safeClose(bis); } // end finally return decodedData; } // end decodeFromFile /** * Convenience method for reading a binary file and base64-encoding it. * *

* As of v 2.3, if there is a error, the method will throw an java.io.IOException. This is new to v2.3! In earlier * versions, it just returned false, but in retrospect that's a pretty poor way to handle it. *

* * @param filename Filename for reading binary data * @return base64-encoded string * @throws java.io.IOException if there is an error * @since 2.1 */ public static String encodeFromFile(String filename) throws java.io.IOException { String encodedData = null; Base64.InputStream bis = null; try { // Set up some useful variables Path file = Paths.get(filename); byte[] buffer = new byte[Math.max((int) (Files.size(file) * 1.4 + 1), 40)]; // Need max() for math on small files // (v2.2.1); Need +1 for a few corner cases // (v2.3.5) int length = 0; int numBytes; // Open a stream bis = new Base64.InputStream(new java.io.BufferedInputStream(Files.newInputStream(file)), Base64.ENCODE); // Read until done while ((numBytes = bis.read(buffer, length, 4096)) >= 0) { length += numBytes; } // end while // Save in a variable to return encodedData = new String(buffer, 0, length, StandardCharsets.US_ASCII); } // end try catch (java.io.IOException e) { throw e; // Catch and release to execute finally{} } // end catch: java.io.IOException finally { safeClose(bis); } // end finally return encodedData; } // end encodeFromFile /** * Reads infile and encodes it to outfile. * * @param infile Input file * @param outfile Output file * @throws java.io.IOException if there is an error * @since 2.2 */ public static void encodeFileToFile(String infile, String outfile) throws java.io.IOException { String encoded = Base64.encodeFromFile(infile); java.io.OutputStream out = null; try { out = new java.io.BufferedOutputStream(new java.io.FileOutputStream(outfile)); out.write(encoded.getBytes(StandardCharsets.US_ASCII)); // Strict, 7-bit output. } // end try catch (java.io.IOException e) { throw e; // Catch and release to execute finally{} } // end catch finally { safeClose(out); } // end finally } // end encodeFileToFile /** * Reads infile and decodes it to outfile. * * @param infile Input file * @param outfile Output file * @throws java.io.IOException if there is an error * @since 2.2 */ public static void decodeFileToFile(String infile, String outfile) throws java.io.IOException { byte[] decoded = Base64.decodeFromFile(infile); java.io.OutputStream out = null; try { out = new java.io.BufferedOutputStream(new java.io.FileOutputStream(outfile)); out.write(decoded); } // end try catch (java.io.IOException e) { throw e; // Catch and release to execute finally{} } // end catch finally { safeClose(out); } // end finally } // end decodeFileToFile /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ /** * A {@link Base64.InputStream} will read data from another java.io.InputStream, given in the constructor, and * encode/decode to/from Base64 notation on the fly. * * @see Base64 * @since 1.3 */ public static class InputStream extends java.io.FilterInputStream { private boolean encode; // Encoding or decoding private int position; // Current position in the buffer private byte[] buffer; // Small buffer holding converted data private int bufferLength; // Length of buffer (3 or 4) private int numSigBytes; // Number of meaningful bytes in the buffer private int lineLength; private boolean breakLines; // Break lines at less than 80 characters private int options; // Record options used to create the stream. private byte[] decodabet; // Local copies to avoid extra method calls /** * Constructs a {@link Base64.InputStream} in DECODE mode. * * @param in the java.io.InputStream from which to read data. * @since 1.3 */ InputStream(java.io.InputStream in) { this(in, DECODE); } // end constructor /** * Constructs a {@link Base64.InputStream} in either ENCODE or DECODE mode. *

* Valid options: * *

         *   ENCODE or DECODE: Encode or Decode as data is read.
         *   DO_BREAK_LINES: break lines at 76 characters
         *     (only meaningful when encoding)
         * 
*

* Example: new Base64.InputStream( in, Base64.DECODE ) * * * @param in the java.io.InputStream from which to read data. * @param options Specified options * @see Base64#ENCODE * @see Base64#DECODE * @see Base64#DO_BREAK_LINES * @since 2.0 */ InputStream(java.io.InputStream in, int options) { super(in); this.options = options; // Record for later this.breakLines = (options & DO_BREAK_LINES) > 0; this.encode = (options & ENCODE) > 0; this.bufferLength = encode ? 4 : 3; this.buffer = new byte[bufferLength]; this.position = -1; this.lineLength = 0; this.decodabet = getDecodabet(options); } // end constructor /** * Reads enough of the input stream to convert to/from Base64 and returns the next byte. * * @return next byte * @since 1.3 */ @Override public int read() throws java.io.IOException { // Do we need to get data? if (position < 0) { if (encode) { byte[] b3 = new byte[3]; int numBinaryBytes = 0; for (int i = 0; i < 3; i++) { int b = in.read(); // If end of stream, b is -1. if (b >= 0) { b3[i] = (byte) b; numBinaryBytes++; } else { break; // out of for loop } // end else: end of stream } // end for: each needed input byte if (numBinaryBytes > 0) { encode3to4(b3, 0, numBinaryBytes, buffer, 0, options); position = 0; numSigBytes = 4; } // end if: got data else { return -1; // Must be end of stream } // end else } // end if: encoding // Else decoding else { byte[] b4 = new byte[4]; int i = 0; for (i = 0; i < 4; i++) { // Read four "meaningful" bytes: int b = 0; do { b = in.read(); } while (b >= 0 && decodabet[b & 0x7f] <= WHITE_SPACE_ENC); if (b < 0) { break; // Reads a -1 if end of stream } // end if: end of stream b4[i] = (byte) b; } // end for: each needed input byte if (i == 4) { numSigBytes = decode4to3(b4, 0, buffer, 0, options); position = 0; } // end if: got four characters else if (i == 0) { return -1; } // end else if: also padded correctly else { // Must have broken out from above. throw new java.io.IOException("Improperly padded Base64 input."); } // end } // end else: decode } // end else: get data // Got data? if (position >= 0) { // End of relevant data? if ( /* !encode && */position >= numSigBytes) { return -1; } // end if: got data if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) { lineLength = 0; return '\n'; } // end if else { lineLength++; // This isn't important when decoding // but throwing an extra "if" seems // just as wasteful. int b = buffer[position++]; if (position >= bufferLength) { position = -1; } // end if: end return b & 0xFF; // This is how you "cast" a byte that's // intended to be unsigned. } // end else } // end if: position >= 0 // Else error else { throw new java.io.IOException("Error in Base64 code reading stream."); } // end else } // end read /** * Calls {@link #read()} repeatedly until the end of stream is reached or len bytes are read. Returns number * of bytes read into array or -1 if end of stream is encountered. * * @param dest array to hold values * @param off offset for array * @param len max number of bytes to read into array * @return bytes read into array or -1 if end of stream is encountered. * @since 1.3 */ @Override public int read(byte[] dest, int off, int len) throws java.io.IOException { int i; int b; for (i = 0; i < len; i++) { b = read(); if (b >= 0) { dest[off + i] = (byte) b; } else if (i == 0) { return -1; } else { break; // Out of 'for' loop } // Out of 'for' loop } // end for: each byte read return i; } // end read } // end inner class InputStream /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ /** * A {@link Base64.OutputStream} will write data to another java.io.OutputStream, given in the constructor, and * encode/decode to/from Base64 notation on the fly. * * @see Base64 * @since 1.3 */ public static class OutputStream extends java.io.FilterOutputStream { private boolean encode; private int position; private byte[] buffer; private int bufferLength; private int lineLength; private boolean breakLines; private byte[] b4; // Scratch used in a few places private boolean suspendEncoding; private int options; // Record for later private byte[] decodabet; // Local copies to avoid extra method calls /** * Constructs a {@link Base64.OutputStream} in ENCODE mode. * * @param out the java.io.OutputStream to which data will be written. * @since 1.3 */ OutputStream(java.io.OutputStream out) { this(out, ENCODE); } // end constructor /** * Constructs a {@link Base64.OutputStream} in either ENCODE or DECODE mode. *

* Valid options: * *

         *   ENCODE or DECODE: Encode or Decode as data is read.
         *   DO_BREAK_LINES: don't break lines at 76 characters
         *     (only meaningful when encoding)
         * 
*

* Example: new Base64.OutputStream( out, Base64.ENCODE ) * * @param out the java.io.OutputStream to which data will be written. * @param options Specified options. * @see Base64#ENCODE * @see Base64#DECODE * @see Base64#DO_BREAK_LINES * @since 1.3 */ OutputStream(java.io.OutputStream out, int options) { super(out); this.breakLines = (options & DO_BREAK_LINES) != 0; this.encode = (options & ENCODE) != 0; this.bufferLength = encode ? 3 : 4; this.buffer = new byte[bufferLength]; this.position = 0; this.lineLength = 0; this.suspendEncoding = false; this.b4 = new byte[4]; this.options = options; this.decodabet = getDecodabet(options); } // end constructor /** * Writes the byte to the output stream after converting to/from Base64 notation. When encoding, bytes are buffered * three at a time before the output stream actually gets a write() call. When decoding, bytes are buffered four at a * time. * * @param theByte the byte to write * @since 1.3 */ @Override public void write(int theByte) throws java.io.IOException { // Encoding suspended? if (suspendEncoding) { this.out.write(theByte); return; } // end if: suspended // Encode? if (encode) { buffer[position++] = (byte) theByte; if (position >= bufferLength) { // Enough to encode. this.out.write(encode3to4(b4, buffer, bufferLength, options)); lineLength += 4; if (breakLines && lineLength >= MAX_LINE_LENGTH) { this.out.write(NEW_LINE); lineLength = 0; } // end if: end of line position = 0; } // end if: enough to output } // end if: encoding // Else, Decoding else { // Meaningful Base64 character? if (decodabet[theByte & 0x7f] > WHITE_SPACE_ENC) { buffer[position++] = (byte) theByte; if (position >= bufferLength) { // Enough to output. int len = Base64.decode4to3(buffer, 0, b4, 0, options); out.write(b4, 0, len); position = 0; } // end if: enough to output } // end if: meaningful base64 character else if (decodabet[theByte & 0x7f] != WHITE_SPACE_ENC) { throw new java.io.IOException("Invalid character in Base64 data."); } // end else: not white space either } // end else: decoding } // end write /** * Calls {@link #write(int)} repeatedly until len bytes are written. * * @param theBytes array from which to read bytes * @param off offset for array * @param len max number of bytes to read into array * @since 1.3 */ @Override public void write(byte[] theBytes, int off, int len) throws java.io.IOException { // Encoding suspended? if (suspendEncoding) { this.out.write(theBytes, off, len); return; } // end if: suspended for (int i = 0; i < len; i++) { write(theBytes[off + i]); } // end for: each byte written } // end write /** * Method added by PHIL. [Thanks, PHIL. -Rob] This pads the buffer without closing the stream. * * @throws java.io.IOException if there's an error. */ public void flushBase64() throws java.io.IOException { if (position > 0) { if (encode) { out.write(encode3to4(b4, buffer, position, options)); position = 0; } // end if: encoding else { throw new java.io.IOException("Base64 input not properly padded."); } // end else: decoding } // end if: buffer partially full } // end flush /** * Flushes and closes (I think, in the superclass) the stream. * * @since 1.3 */ @Override public void close() throws java.io.IOException { // 1. Ensure that pending characters are written flushBase64(); // 2. Actually close the stream // Base class both flushes and closes. super.close(); buffer = null; out = null; } // end close /** * Suspends encoding of the stream. May be helpful if you need to embed a piece of base64-encoded data in a stream. * * @throws java.io.IOException if there's an error flushing * @since 1.5.1 */ public void suspendEncoding() throws java.io.IOException { flushBase64(); this.suspendEncoding = true; } // end suspendEncoding /** * Resumes encoding of the stream. May be helpful if you need to embed a piece of base64-encoded data in a stream. * * @since 1.5.1 */ public void resumeEncoding() { this.suspendEncoding = false; } // end resumeEncoding } // end inner class OutputStream } // end class Base64 Hybi07Handshake.java000066400000000000000000000105601420065311100345710ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version07/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol.version07; import io.undertow.util.Headers; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketVersion; import io.undertow.websockets.core.protocol.Handshake; import io.undertow.websockets.extensions.CompositeExtensionFunction; import io.undertow.websockets.extensions.ExtensionFunction; import io.undertow.websockets.spi.WebSocketHttpExchange; import org.xnio.IoUtils; import io.undertow.connector.ByteBufferPool; import org.xnio.StreamConnection; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Collections; import java.util.List; import java.util.Set; /** * The handshaking protocol implementation for Hybi-07. * * @author Mike Brock */ public class Hybi07Handshake extends Handshake { public static final String MAGIC_NUMBER = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; protected Hybi07Handshake(final WebSocketVersion version, final Set subprotocols, boolean allowExtensions) { super(version, "SHA1", MAGIC_NUMBER, subprotocols); this.allowExtensions = allowExtensions; } public Hybi07Handshake(final Set subprotocols, boolean allowExtensions) { this(WebSocketVersion.V07, subprotocols, allowExtensions); } public Hybi07Handshake() { this(WebSocketVersion.V07, Collections.emptySet(), false); } @Override public boolean matches(final WebSocketHttpExchange exchange) { if (exchange.getRequestHeader(Headers.SEC_WEB_SOCKET_KEY_STRING) != null && exchange.getRequestHeader(Headers.SEC_WEB_SOCKET_VERSION_STRING) != null) { return exchange.getRequestHeader(Headers.SEC_WEB_SOCKET_VERSION_STRING) .equals(getVersion().toHttpHeaderValue()); } return false; } protected void handshakeInternal(final WebSocketHttpExchange exchange) { String origin = exchange.getRequestHeader(Headers.SEC_WEB_SOCKET_ORIGIN_STRING); if (origin != null) { exchange.setResponseHeader(Headers.SEC_WEB_SOCKET_ORIGIN_STRING, origin); } selectSubprotocol(exchange); selectExtensions(exchange); exchange.setResponseHeader(Headers.SEC_WEB_SOCKET_LOCATION_STRING, getWebSocketLocation(exchange)); final String key = exchange.getRequestHeader(Headers.SEC_WEB_SOCKET_KEY_STRING); try { final String solution = solve(key); exchange.setResponseHeader(Headers.SEC_WEB_SOCKET_ACCEPT_STRING, solution); performUpgrade(exchange); } catch (NoSuchAlgorithmException e) { IoUtils.safeClose(exchange); exchange.endExchange(); return; } } protected final String solve(final String nonceBase64) throws NoSuchAlgorithmException { final String concat = nonceBase64.trim() + getMagicNumber(); final MessageDigest digest = MessageDigest.getInstance(getHashAlgorithm()); digest.update(concat.getBytes(StandardCharsets.UTF_8)); return Base64.encodeBytes(digest.digest()).trim(); } @Override public WebSocketChannel createChannel(WebSocketHttpExchange exchange, final StreamConnection channel, final ByteBufferPool pool) { List extensionFunctions = initExtensions(exchange); return new WebSocket07Channel(channel, pool, getWebSocketLocation(exchange), exchange.getResponseHeader(Headers.SEC_WEB_SOCKET_PROTOCOL_STRING), false, !extensionFunctions.isEmpty(), CompositeExtensionFunction.compose(extensionFunctions), exchange.getPeerConnections(), exchange.getOptions()); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version07/Masker.java000066400000000000000000000046151420065311100332250ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol.version07; import io.undertow.server.protocol.framed.FrameHeaderData; import io.undertow.websockets.core.function.ChannelFunction; import java.nio.ByteBuffer; /** * @author Norman Maurer */ public final class Masker implements ChannelFunction { private byte[] maskingKey; int m; Masker(int maskingKey) { this.maskingKey = createsMaskingKey(maskingKey); } public void setMaskingKey(int maskingKey) { this.maskingKey = createsMaskingKey(maskingKey); m = 0; } private static byte[] createsMaskingKey(int maskingKey) { byte[] key = new byte[4]; key[0] = (byte) (maskingKey >> 24 & 0xFF); key[1] = (byte) (maskingKey >> 16 & 0xFF); key[2] = (byte) (maskingKey >> 8 & 0xFF); key[3] = (byte) (maskingKey & 0xFF); return key; } private void mask(ByteBuffer buf, int position, int length) { int limit = position + length; for (int i = position ; i < limit; ++i) { buf.put(i, (byte) (buf.get(i) ^ maskingKey[m++])); m %= 4; } } @Override public void newFrame(FrameHeaderData headerData) { WebSocket07Channel.WebSocketFrameHeader header = (WebSocket07Channel.WebSocketFrameHeader) headerData; setMaskingKey(header.getMaskingKey()); } @Override public void afterRead(ByteBuffer buf, int position, int length) { mask(buf, position, length); } @Override public void beforeWrite(ByteBuffer buf, int position, int length) { mask(buf, position, length); } @Override public void complete() { // noop } } UTF8Checker.java000066400000000000000000000104661420065311100337400ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version07/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol.version07; import io.undertow.server.protocol.framed.FrameHeaderData; import io.undertow.websockets.core.WebSocketMessages; import io.undertow.websockets.core.function.ChannelFunction; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; /** * An utility class which can be used to check if a sequence of bytes or ByteBuffers contain non UTF-8 data. *

* Please use a new instance per stream. * * @author Norman Maurer */ public class UTF8Checker implements ChannelFunction { private static final int UTF8_ACCEPT = 0; private static final int UTF8_REJECT = 12; private static final byte[] TYPES = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}; private static final byte[] STATES = {0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12}; private int state = UTF8_ACCEPT; private void checkUTF8(int b) throws UnsupportedEncodingException { byte type = TYPES[b & 0xFF]; state = STATES[state + type]; if (state == UTF8_REJECT) { throw WebSocketMessages.MESSAGES.invalidTextFrameEncoding(); } } /** * Check if the given ByteBuffer contains non UTF-8 data. * * @param buf the ByteBuffer to check * @param position the index in the {@link ByteBuffer} to start from * @param length the number of bytes to operate on * @throws UnsupportedEncodingException is thrown if non UTF-8 data is found */ private void checkUTF8(ByteBuffer buf, int position, int length) throws UnsupportedEncodingException { int limit = position + length; for (int i = position; i < limit; i++) { checkUTF8(buf.get(i)); } } @Override public void newFrame(FrameHeaderData headerData) { } @Override public void afterRead(ByteBuffer buf, int position, int length) throws IOException{ checkUTF8(buf, position, length); } @Override public void beforeWrite(ByteBuffer buf, int position, int length) throws UnsupportedEncodingException{ checkUTF8(buf, position, length); } @Override public void complete() throws UnsupportedEncodingException { if (state != UTF8_ACCEPT) { throw WebSocketMessages.MESSAGES.invalidTextFrameEncoding(); } } } WebSocket07BinaryFrameSinkChannel.java000066400000000000000000000024061420065311100402130ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version07/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol.version07; import io.undertow.websockets.core.WebSocketFrameType; /** * @author Norman Maurer */ class WebSocket07BinaryFrameSinkChannel extends WebSocket07FrameSinkChannel { WebSocket07BinaryFrameSinkChannel(WebSocket07Channel wsChannel) { super(wsChannel, WebSocketFrameType.BINARY); } @Override public boolean isFragmentationSupported() { return true; } @Override public boolean areExtensionsSupported() { return true; } } WebSocket07BinaryFrameSourceChannel.java000066400000000000000000000031731420065311100405510ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version07/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol.version07; import io.undertow.websockets.core.StreamSourceFrameChannel; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketFrameType; import io.undertow.connector.PooledByteBuffer; /** * @author Norman Maurer */ class WebSocket07BinaryFrameSourceChannel extends StreamSourceFrameChannel { WebSocket07BinaryFrameSourceChannel(WebSocketChannel wsChannel, int rsv, boolean finalFragment, Masker masker, PooledByteBuffer pooled, long frameLength) { super(wsChannel, WebSocketFrameType.BINARY, rsv, finalFragment, pooled, frameLength, masker); } WebSocket07BinaryFrameSourceChannel(WebSocketChannel wsChannel, int rsv, boolean finalFragment, PooledByteBuffer pooled, long frameLength) { super(wsChannel, WebSocketFrameType.BINARY, rsv, finalFragment, pooled, frameLength, null); } } WebSocket07Channel.java000066400000000000000000000471361420065311100352570ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version07/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol.version07; import io.undertow.server.protocol.framed.AbstractFramedStreamSourceChannel; import io.undertow.websockets.core.StreamSinkFrameChannel; import io.undertow.websockets.core.StreamSourceFrameChannel; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketException; import io.undertow.websockets.core.WebSocketFrame; import io.undertow.websockets.core.WebSocketFrameCorruptedException; import io.undertow.websockets.core.WebSocketFrameType; import io.undertow.websockets.core.WebSocketLogger; import io.undertow.websockets.core.WebSocketMessages; import io.undertow.websockets.core.WebSocketVersion; import io.undertow.websockets.extensions.ExtensionFunction; import org.xnio.IoUtils; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import org.xnio.StreamConnection; import java.nio.ByteBuffer; import java.util.Set; /** * {@link WebSocketChannel} which is used for {@link WebSocketVersion#V08} * * @author Norman Maurer */ public class WebSocket07Channel extends WebSocketChannel { private enum State { READING_FIRST, READING_SECOND, READING_EXTENDED_SIZE1, READING_EXTENDED_SIZE2, READING_EXTENDED_SIZE3, READING_EXTENDED_SIZE4, READING_EXTENDED_SIZE5, READING_EXTENDED_SIZE6, READING_EXTENDED_SIZE7, READING_EXTENDED_SIZE8, READING_MASK_1, READING_MASK_2, READING_MASK_3, READING_MASK_4, DONE, } private int fragmentedFramesCount; private final ByteBuffer lengthBuffer = ByteBuffer.allocate(8); private UTF8Checker checker; protected static final byte OPCODE_CONT = 0x0; protected static final byte OPCODE_TEXT = 0x1; protected static final byte OPCODE_BINARY = 0x2; protected static final byte OPCODE_CLOSE = 0x8; protected static final byte OPCODE_PING = 0x9; protected static final byte OPCODE_PONG = 0xA; /** * Create a new {@link WebSocket07Channel} * * @param channel The {@link StreamConnection} over which the WebSocket Frames should get send and received. * Be aware that it already must be "upgraded". * @param bufferPool The {@link ByteBufferPool} which will be used to acquire {@link ByteBuffer}'s from. * @param wsUrl The url for which the {@link WebSocket07Channel} was created. */ public WebSocket07Channel(StreamConnection channel, ByteBufferPool bufferPool, String wsUrl, String subProtocol, final boolean client, boolean allowExtensions, final ExtensionFunction extensionFunction, Set openConnections, OptionMap options) { super(channel, bufferPool, WebSocketVersion.V08, wsUrl, subProtocol, client, allowExtensions, extensionFunction, openConnections, options); } @Override protected PartialFrame receiveFrame() { return new WebSocketFrameHeader(); } @Override protected void markReadsBroken(Throwable cause) { super.markReadsBroken(cause); } @Override protected void closeSubChannels() { IoUtils.safeClose(fragmentedChannel); } @Override protected StreamSinkFrameChannel createStreamSinkChannel(WebSocketFrameType type) { switch (type) { case TEXT: return new WebSocket07TextFrameSinkChannel(this); case BINARY: return new WebSocket07BinaryFrameSinkChannel(this); case CLOSE: return new WebSocket07CloseFrameSinkChannel(this); case PONG: return new WebSocket07PongFrameSinkChannel(this); case PING: return new WebSocket07PingFrameSinkChannel(this); default: throw WebSocketMessages.MESSAGES.unsupportedFrameType(type); } } class WebSocketFrameHeader implements WebSocketFrame { private boolean frameFinalFlag; private int frameRsv; private int frameOpcode; private int maskingKey; private boolean frameMasked; private long framePayloadLength; private State state = State.READING_FIRST; private int framePayloadLen1; private boolean done = false; @Override public StreamSourceFrameChannel getChannel(PooledByteBuffer pooled) { StreamSourceFrameChannel channel = createChannel(pooled); if (frameFinalFlag) { channel.finalFrame(); } else { fragmentedChannel = channel; } return channel; } public StreamSourceFrameChannel createChannel(PooledByteBuffer pooled) { // Processing ping/pong/close frames because they cannot be // fragmented as per spec if (frameOpcode == OPCODE_PING) { if (frameMasked) { return new WebSocket07PingFrameSourceChannel(WebSocket07Channel.this, frameRsv, new Masker(maskingKey), pooled, framePayloadLength); } else { return new WebSocket07PingFrameSourceChannel(WebSocket07Channel.this, frameRsv, pooled, framePayloadLength); } } if (frameOpcode == OPCODE_PONG) { if (frameMasked) { return new WebSocket07PongFrameSourceChannel(WebSocket07Channel.this, frameRsv, new Masker(maskingKey), pooled, framePayloadLength); } else { return new WebSocket07PongFrameSourceChannel(WebSocket07Channel.this, frameRsv, pooled, framePayloadLength); } } if (frameOpcode == OPCODE_CLOSE) { if (frameMasked) { return new WebSocket07CloseFrameSourceChannel(WebSocket07Channel.this, frameRsv, new Masker(maskingKey), pooled, framePayloadLength); } else { return new WebSocket07CloseFrameSourceChannel(WebSocket07Channel.this, frameRsv, pooled, framePayloadLength); } } if (frameOpcode == OPCODE_TEXT) { // try to grab the checker which was used before UTF8Checker checker = WebSocket07Channel.this.checker; if (checker == null) { checker = new UTF8Checker(); } if (!frameFinalFlag) { // if this is not the final fragment store the used checker to use it in later fragments also WebSocket07Channel.this.checker = checker; } else { // was the final fragment reset the checker to null WebSocket07Channel.this.checker = null; } if (frameMasked) { return new WebSocket07TextFrameSourceChannel(WebSocket07Channel.this, frameRsv, frameFinalFlag, new Masker(maskingKey), checker, pooled, framePayloadLength); } else { return new WebSocket07TextFrameSourceChannel(WebSocket07Channel.this, frameRsv, frameFinalFlag, checker, pooled, framePayloadLength); } } else if (frameOpcode == OPCODE_BINARY) { if (frameMasked) { return new WebSocket07BinaryFrameSourceChannel(WebSocket07Channel.this, frameRsv, frameFinalFlag, new Masker(maskingKey), pooled, framePayloadLength); } else { return new WebSocket07BinaryFrameSourceChannel(WebSocket07Channel.this, frameRsv, frameFinalFlag, pooled, framePayloadLength); } } else if (frameOpcode == OPCODE_CONT) { throw new RuntimeException(); //should never happen } else { /* Spec does not define how specific OpCodes should be treated. We are going to return a Binary if an extension code is present. Extensions implementation should be responsible of specific logic. */ if (hasReservedOpCode) { if (frameMasked) { return new WebSocket07BinaryFrameSourceChannel(WebSocket07Channel.this, frameRsv, frameFinalFlag, new Masker(maskingKey), pooled, framePayloadLength); } else { return new WebSocket07BinaryFrameSourceChannel(WebSocket07Channel.this, frameRsv, frameFinalFlag, pooled, framePayloadLength); } } else { throw WebSocketMessages.MESSAGES.unsupportedOpCode(frameOpcode); } } } @Override public void handle(final ByteBuffer buffer) throws WebSocketException { if (!buffer.hasRemaining()) { return; } while (state != State.DONE) { byte b; switch (state) { case READING_FIRST: // Read FIN, RSV, OPCODE b = buffer.get(); frameFinalFlag = (b & 0x80) != 0; frameRsv = (b & 0x70) >> 4; frameOpcode = b & 0x0F; if (WebSocketLogger.REQUEST_LOGGER.isDebugEnabled()) { WebSocketLogger.REQUEST_LOGGER.decodingFrameWithOpCode(frameOpcode); } state = State.READING_SECOND; // clear the lengthBuffer to reuse it later lengthBuffer.clear(); case READING_SECOND: if (!buffer.hasRemaining()) { return; } b = buffer.get(); // Read MASK, PAYLOAD LEN 1 // frameMasked = (b & 0x80) != 0; framePayloadLen1 = b & 0x7F; if (frameRsv != 0) { if (!areExtensionsSupported()) { throw WebSocketMessages.MESSAGES.extensionsNotAllowed(frameRsv); } } if (frameOpcode > 7) { // control frame (have MSB in opcode set) validateControlFrame(); } else { // data frame validateDataFrame(); } if (framePayloadLen1 == 126 || framePayloadLen1 == 127) { state = State.READING_EXTENDED_SIZE1; } else { framePayloadLength = framePayloadLen1; if (frameMasked) { state = State.READING_MASK_1; } else { state = State.DONE; } continue; } case READING_EXTENDED_SIZE1: // Read frame payload length if (!buffer.hasRemaining()) { return; } b = buffer.get(); lengthBuffer.put(b); state = State.READING_EXTENDED_SIZE2; case READING_EXTENDED_SIZE2: if (!buffer.hasRemaining()) { return; } b = buffer.get(); lengthBuffer.put(b); if (framePayloadLen1 == 126) { lengthBuffer.flip(); // must be unsigned short framePayloadLength = lengthBuffer.getShort() & 0xFFFF; if (frameMasked) { state = State.READING_MASK_1; } else { state = State.DONE; } continue; } state = State.READING_EXTENDED_SIZE3; case READING_EXTENDED_SIZE3: if (!buffer.hasRemaining()) { return; } b = buffer.get(); lengthBuffer.put(b); state = State.READING_EXTENDED_SIZE4; case READING_EXTENDED_SIZE4: if (!buffer.hasRemaining()) { return; } b = buffer.get(); lengthBuffer.put(b); state = State.READING_EXTENDED_SIZE5; case READING_EXTENDED_SIZE5: if (!buffer.hasRemaining()) { return; } b = buffer.get(); lengthBuffer.put(b); state = State.READING_EXTENDED_SIZE6; case READING_EXTENDED_SIZE6: if (!buffer.hasRemaining()) { return; } b = buffer.get(); lengthBuffer.put(b); state = State.READING_EXTENDED_SIZE7; case READING_EXTENDED_SIZE7: if (!buffer.hasRemaining()) { return; } b = buffer.get(); lengthBuffer.put(b); state = State.READING_EXTENDED_SIZE8; case READING_EXTENDED_SIZE8: if (!buffer.hasRemaining()) { return; } b = buffer.get(); lengthBuffer.put(b); lengthBuffer.flip(); framePayloadLength = lengthBuffer.getLong(); if (frameMasked) { state = State.READING_MASK_1; } else { state = State.DONE; break; } state = State.READING_MASK_1; case READING_MASK_1: if (!buffer.hasRemaining()) { return; } b = buffer.get(); maskingKey = b & 0xFF; state = State.READING_MASK_2; case READING_MASK_2: if (!buffer.hasRemaining()) { return; } b = buffer.get(); maskingKey = maskingKey << 8 | b & 0xFF; state = State.READING_MASK_3; case READING_MASK_3: if (!buffer.hasRemaining()) { return; } b = buffer.get(); maskingKey = maskingKey << 8 | b & 0xFF; state = State.READING_MASK_4; case READING_MASK_4: if (!buffer.hasRemaining()) { return; } b = buffer.get(); maskingKey = maskingKey << 8 | b & 0xFF; state = State.DONE; break; default: throw new IllegalStateException(state.toString()); } } if (frameFinalFlag) { // check if the frame is a ping frame as these are allowed in the middle if (frameOpcode != OPCODE_PING && frameOpcode != OPCODE_PONG) { fragmentedFramesCount = 0; } } else { // Increment counter fragmentedFramesCount++; } done = true; } private void validateDataFrame() throws WebSocketFrameCorruptedException { if (!isClient() && !frameMasked) { throw WebSocketMessages.MESSAGES.frameNotMasked(); } // check for reserved data frame opcodes if (!(frameOpcode == OPCODE_CONT || frameOpcode == OPCODE_TEXT || frameOpcode == OPCODE_BINARY)) { throw WebSocketMessages.MESSAGES.reservedOpCodeInDataFrame(frameOpcode); } // check opcode vs message fragmentation state 1/2 if (fragmentedFramesCount == 0 && frameOpcode == OPCODE_CONT) { throw WebSocketMessages.MESSAGES.continuationFrameOutsideFragmented(); } // check opcode vs message fragmentation state 2/2 if (fragmentedFramesCount != 0 && frameOpcode != OPCODE_CONT) { throw WebSocketMessages.MESSAGES.nonContinuationFrameInsideFragmented(); } } private void validateControlFrame() throws WebSocketFrameCorruptedException { // control frames MUST NOT be fragmented if (!frameFinalFlag) { throw WebSocketMessages.MESSAGES.fragmentedControlFrame(); } // control frames MUST have payload 125 octets or less as stated in the spec if (framePayloadLen1 > 125) { throw WebSocketMessages.MESSAGES.toBigControlFrame(); } // check for reserved control frame opcodes if (!(frameOpcode == OPCODE_CLOSE || frameOpcode == OPCODE_PING || frameOpcode == OPCODE_PONG)) { throw WebSocketMessages.MESSAGES.reservedOpCodeInControlFrame(frameOpcode); } // close frame : if there is a body, the first two bytes of the // body MUST be a 2-byte unsigned integer (in network byte // order) representing a status code if (frameOpcode == 8 && framePayloadLen1 == 1) { throw WebSocketMessages.MESSAGES.controlFrameWithPayloadLen1(); } } @Override public boolean isDone() { return done; } @Override public long getFrameLength() { return framePayloadLength; } int getMaskingKey() { return maskingKey; } @Override public AbstractFramedStreamSourceChannel getExistingChannel() { if (frameOpcode == OPCODE_CONT) { StreamSourceFrameChannel ret = fragmentedChannel; if(frameFinalFlag) { fragmentedChannel = null; } return ret; } return null; } @Override public boolean isFinalFragment() { return frameFinalFlag; } } } WebSocket07CloseFrameSinkChannel.java000066400000000000000000000021201420065311100400250ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version07/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol.version07; import io.undertow.websockets.core.WebSocketFrameType; /** * @author Norman Maurer */ class WebSocket07CloseFrameSinkChannel extends WebSocket07FrameSinkChannel { WebSocket07CloseFrameSinkChannel(WebSocket07Channel wsChannel) { super(wsChannel, WebSocketFrameType.CLOSE); } } WebSocket07CloseFrameSourceChannel.java000066400000000000000000000062201420065311100403660ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version07/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol.version07; import io.undertow.websockets.core.StreamSourceFrameChannel; import io.undertow.websockets.core.WebSocketFrameType; import io.undertow.websockets.core.WebSocketMessages; import io.undertow.connector.PooledByteBuffer; import java.io.IOException; import java.nio.ByteBuffer; /** * @author Norman Maurer */ class WebSocket07CloseFrameSourceChannel extends StreamSourceFrameChannel { WebSocket07CloseFrameSourceChannel(WebSocket07Channel wsChannel, int rsv, Masker masker, PooledByteBuffer pooled, long frameLength) { // no fragmentation allowed per spec super(wsChannel, WebSocketFrameType.CLOSE, rsv, true, pooled, frameLength, masker, new CloseFrameValidatorChannelFunction(wsChannel)); } WebSocket07CloseFrameSourceChannel(WebSocket07Channel wsChannel, int rsv, PooledByteBuffer pooled, long frameLength) { // no fragmentation allowed per spec super(wsChannel, WebSocketFrameType.CLOSE, rsv, true, pooled, frameLength, null, new CloseFrameValidatorChannelFunction(wsChannel)); } public static class CloseFrameValidatorChannelFunction extends UTF8Checker { private final WebSocket07Channel wsChannel; private int statusBytesRead; private int status; CloseFrameValidatorChannelFunction(WebSocket07Channel wsChannel) { this.wsChannel = wsChannel; } @Override public void afterRead(ByteBuffer buf, int position, int length) throws IOException { int i = 0; if(statusBytesRead < 2) { while (statusBytesRead < 2 && i < length) { status <<= 8; status += buf.get(position + i) & 0xFF; statusBytesRead ++; ++i; } if(statusBytesRead == 2) { // Must have 2 byte integer within the valid range if (status >= 0 && status <= 999 || status >= 1004 && status <= 1006 || status >= 1012 && status <= 2999 || status >= 5000) { IOException exception = WebSocketMessages.MESSAGES.invalidCloseFrameStatusCode(status); wsChannel.markReadsBroken(exception); throw exception; } } } super.afterRead(buf, position + i, length - i); } } } WebSocket07FrameSinkChannel.java000066400000000000000000000135701420065311100370520ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version07/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol.version07; import io.undertow.UndertowLogger; import io.undertow.server.protocol.framed.SendFrameHeader; import io.undertow.util.ImmediatePooledByteBuffer; import io.undertow.websockets.core.StreamSinkFrameChannel; import io.undertow.websockets.core.WebSocketFrameType; import io.undertow.websockets.core.WebSocketMessages; import io.undertow.websockets.extensions.ExtensionFunction; import io.undertow.websockets.extensions.NoopExtensionFunction; import io.undertow.connector.PooledByteBuffer; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.ThreadLocalRandom; /** * {@link StreamSinkFrameChannel} implementation for writing WebSocket Frames on {@link io.undertow.websockets.core.WebSocketVersion#V08} connections * * @author Norman Maurer */ public abstract class WebSocket07FrameSinkChannel extends StreamSinkFrameChannel { private final Masker masker; private volatile boolean dataWritten = false; protected final ExtensionFunction extensionFunction; protected WebSocket07FrameSinkChannel(WebSocket07Channel wsChannel, WebSocketFrameType type) { super(wsChannel, type); if(wsChannel.isClient()) { masker = new Masker(0); } else { masker = null; } /* Checks if there are negotiated extensions that need to modify RSV bits */ if (wsChannel.areExtensionsSupported() && (type == WebSocketFrameType.TEXT || type == WebSocketFrameType.BINARY)) { extensionFunction = wsChannel.getExtensionFunction(); setRsv(extensionFunction.writeRsv(0)); } else { extensionFunction = NoopExtensionFunction.INSTANCE; setRsv(0); } } @Override protected void handleFlushComplete(boolean finalFrame) { dataWritten = true; // TODO not sure we need to do this as the key was set when it was last used // if(masker != null) { // masker.setMaskingKey(maskingKey); // } } private byte opCode() { if(dataWritten) { return WebSocket07Channel.OPCODE_CONT; } switch (getType()) { case CONTINUATION: return WebSocket07Channel.OPCODE_CONT; case TEXT: return WebSocket07Channel.OPCODE_TEXT; case BINARY: return WebSocket07Channel.OPCODE_BINARY; case CLOSE: return WebSocket07Channel.OPCODE_CLOSE; case PING: return WebSocket07Channel.OPCODE_PING; case PONG: return WebSocket07Channel.OPCODE_PONG; default: throw WebSocketMessages.MESSAGES.unsupportedFrameType(getType()); } } @Override protected SendFrameHeader createFrameHeader() { byte b0 = 0; //if writes are shutdown this is the final fragment if (isFinalFrameQueued()) { b0 |= 1 << 7; // set FIN } /* Known extensions (i.e. compression) should not modify RSV bit on continuation bit. */ byte opCode = opCode(); int rsv = opCode == WebSocket07Channel.OPCODE_CONT ? 0 : getRsv(); b0 |= (rsv & 7) << 4; b0 |= opCode & 0xf; final ByteBuffer header = ByteBuffer.allocate(14); byte maskKey = 0; if(masker != null) { maskKey |= 1 << 7; } long payloadSize = getBuffer().remaining(); if (payloadSize > 125 && opCode == WebSocket07Channel.OPCODE_PING) { throw WebSocketMessages.MESSAGES.invalidPayloadLengthForPing(payloadSize); } if (payloadSize <= 125) { header.put(b0); header.put((byte)((payloadSize | maskKey) & 0xFF)); } else if (payloadSize <= 0xFFFF) { header.put(b0); header.put((byte) ((126 | maskKey) & 0xFF)); header.put((byte) (payloadSize >>> 8 & 0xFF)); header.put((byte) (payloadSize & 0xFF)); } else { header.put(b0); header.put((byte) ((127 | maskKey) & 0xFF)); header.putLong(payloadSize); } if(masker != null) { int maskingKey = ThreadLocalRandom.current().nextInt(); //generate a new key for this frame header.put((byte)((maskingKey >> 24) & 0xFF)); header.put((byte)((maskingKey >> 16) & 0xFF)); header.put((byte)((maskingKey >> 8) & 0xFF)); header.put((byte)((maskingKey & 0xFF))); masker.setMaskingKey(maskingKey); //do any required masking ByteBuffer buf = getBuffer(); masker.beforeWrite(buf, buf.position(), buf.remaining()); } header.flip(); return new SendFrameHeader(0, new ImmediatePooledByteBuffer(header)); } @Override protected PooledByteBuffer preWriteTransform(PooledByteBuffer body) { try { return super.preWriteTransform(extensionFunction.transformForWrite(body, this, this.isFinalFrameQueued())); } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); markBroken(); throw new RuntimeException(e); } } } WebSocket07PingFrameSinkChannel.java000066400000000000000000000021151420065311100376610ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version07/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol.version07; import io.undertow.websockets.core.WebSocketFrameType; /** * @author Norman Maurer */ class WebSocket07PingFrameSinkChannel extends WebSocket07FrameSinkChannel { WebSocket07PingFrameSinkChannel(WebSocket07Channel wsChannel) { super(wsChannel, WebSocketFrameType.PING); } } WebSocket07PingFrameSourceChannel.java000066400000000000000000000031621420065311100402200ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version07/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol.version07; import io.undertow.websockets.core.StreamSourceFrameChannel; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketFrameType; import io.undertow.connector.PooledByteBuffer; /** * @author Norman Maurer */ class WebSocket07PingFrameSourceChannel extends StreamSourceFrameChannel { WebSocket07PingFrameSourceChannel(WebSocketChannel wsChannel, int rsv, Masker masker, PooledByteBuffer pooled, long frameLength) { // can not be fragmented super(wsChannel, WebSocketFrameType.PING, rsv, true, pooled, frameLength, masker); } WebSocket07PingFrameSourceChannel(WebSocketChannel wsChannel, int rsv, PooledByteBuffer pooled, long frameLength) { // can not be fragmented super(wsChannel, WebSocketFrameType.PING, rsv, true, pooled, frameLength, null); } } WebSocket07PongFrameSinkChannel.java000066400000000000000000000021151420065311100376670ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version07/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol.version07; import io.undertow.websockets.core.WebSocketFrameType; /** * @author Norman Maurer */ class WebSocket07PongFrameSinkChannel extends WebSocket07FrameSinkChannel { WebSocket07PongFrameSinkChannel(WebSocket07Channel wsChannel) { super(wsChannel, WebSocketFrameType.PONG); } } WebSocket07PongFrameSourceChannel.java000066400000000000000000000031701420065311100402250ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version07/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol.version07; import io.undertow.websockets.core.StreamSourceFrameChannel; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketFrameType; import io.undertow.connector.PooledByteBuffer; /** * @author Norman Maurer */ class WebSocket07PongFrameSourceChannel extends StreamSourceFrameChannel { WebSocket07PongFrameSourceChannel(WebSocketChannel wsChannel, int rsv, final Masker masker, PooledByteBuffer pooled, long frameLength) { // can not be fragmented super(wsChannel, WebSocketFrameType.PONG, rsv, true, pooled, frameLength, masker); } WebSocket07PongFrameSourceChannel(WebSocketChannel wsChannel, int rsv, PooledByteBuffer pooled, long frameLength) { // can not be fragmented super(wsChannel, WebSocketFrameType.PONG, rsv, true, pooled, frameLength, null); } } WebSocket07TextFrameSinkChannel.java000066400000000000000000000025331420065311100377140ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version07/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol.version07; import io.undertow.websockets.core.WebSocketFrameType; /** * WebSocket08FrameSinkChannel that is used to write WebSocketFrameType#TEXT frames. * * * @author Norman Maurer */ class WebSocket07TextFrameSinkChannel extends WebSocket07FrameSinkChannel { WebSocket07TextFrameSinkChannel(WebSocket07Channel wsChannel) { super(wsChannel, WebSocketFrameType.TEXT); } @Override public boolean isFragmentationSupported() { return true; } @Override public boolean areExtensionsSupported() { return true; } } WebSocket07TextFrameSourceChannel.java000066400000000000000000000031741420065311100402520ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version07/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol.version07; import io.undertow.websockets.core.StreamSourceFrameChannel; import io.undertow.websockets.core.WebSocketFrameType; import io.undertow.connector.PooledByteBuffer; /** * @author Norman Maurer */ class WebSocket07TextFrameSourceChannel extends StreamSourceFrameChannel { WebSocket07TextFrameSourceChannel(WebSocket07Channel wsChannel, int rsv, boolean finalFragment, Masker masker, UTF8Checker checker, PooledByteBuffer pooled, long frameLength) { super(wsChannel, WebSocketFrameType.TEXT, rsv, finalFragment, pooled, frameLength, masker, checker); } WebSocket07TextFrameSourceChannel(WebSocket07Channel wsChannel, int rsv, boolean finalFragment, UTF8Checker checker, PooledByteBuffer pooled, long frameLength) { super(wsChannel, WebSocketFrameType.TEXT, rsv, finalFragment, pooled, frameLength, null, checker); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version08/000077500000000000000000000000001420065311100311335ustar00rootroot00000000000000Hybi08Handshake.java000066400000000000000000000045541420065311100346010ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version08/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol.version08; import io.undertow.util.Headers; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketVersion; import io.undertow.websockets.core.protocol.version07.Hybi07Handshake; import io.undertow.websockets.extensions.CompositeExtensionFunction; import io.undertow.websockets.extensions.ExtensionFunction; import io.undertow.websockets.spi.WebSocketHttpExchange; import io.undertow.connector.ByteBufferPool; import org.xnio.StreamConnection; import java.util.Collections; import java.util.List; import java.util.Set; /** * The handshaking protocol implementation for Hybi-07, which is identical to Hybi-08, and thus is just a thin * subclass of {@link Hybi07Handshake} that sets a different version number. * * @author Mike Brock */ public class Hybi08Handshake extends Hybi07Handshake { public Hybi08Handshake() { super(WebSocketVersion.V08, Collections.emptySet(), false); } public Hybi08Handshake(Set subprotocols, boolean allowExtensions) { super(WebSocketVersion.V08, subprotocols, allowExtensions); } @Override public WebSocketChannel createChannel(final WebSocketHttpExchange exchange, final StreamConnection channel, final ByteBufferPool pool) { List extensionFunctions = initExtensions(exchange); return new WebSocket08Channel(channel, pool, getWebSocketLocation(exchange), exchange.getResponseHeader(Headers.SEC_WEB_SOCKET_PROTOCOL_STRING), false, !extensionFunctions.isEmpty(), CompositeExtensionFunction.compose(extensionFunctions), exchange.getPeerConnections(), exchange.getOptions()); } } WebSocket08Channel.java000066400000000000000000000035351420065311100352540ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version08/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol.version08; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketVersion; import io.undertow.websockets.core.protocol.version07.WebSocket07Channel; import io.undertow.websockets.extensions.ExtensionFunction; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import org.xnio.StreamConnection; import java.util.Set; /** * {@link io.undertow.websockets.core.WebSocketChannel} which is used for {@link WebSocketVersion#V08} * * @author Norman Maurer */ public class WebSocket08Channel extends WebSocket07Channel { public WebSocket08Channel(StreamConnection channel, ByteBufferPool bufferPool, String wsUrl, String subProtocols, final boolean client, boolean allowExtensions, final ExtensionFunction extensionFunction, Set openConnections, OptionMap options) { super(channel, bufferPool, wsUrl, subProtocols, client, allowExtensions, extensionFunction, openConnections, options); } @Override public WebSocketVersion getVersion() { return WebSocketVersion.V08; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version13/000077500000000000000000000000001420065311100311275ustar00rootroot00000000000000Hybi13Handshake.java000066400000000000000000000063031420065311100345630ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version13/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol.version13; import io.undertow.util.Headers; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketVersion; import io.undertow.websockets.core.protocol.version07.Hybi07Handshake; import io.undertow.websockets.extensions.CompositeExtensionFunction; import io.undertow.websockets.extensions.ExtensionFunction; import io.undertow.websockets.spi.WebSocketHttpExchange; import org.xnio.IoUtils; import io.undertow.connector.ByteBufferPool; import org.xnio.StreamConnection; import java.security.NoSuchAlgorithmException; import java.util.Collections; import java.util.List; import java.util.Set; /** * The handshaking protocol implementation for Hybi-13. * * @author Mike Brock * @author Stuart Douglas */ public class Hybi13Handshake extends Hybi07Handshake { public Hybi13Handshake() { super(WebSocketVersion.V13, Collections.emptySet(), false); } public Hybi13Handshake(Set subprotocols, boolean allowExtensions) { super(WebSocketVersion.V13, subprotocols, allowExtensions); } @Override protected void handshakeInternal(final WebSocketHttpExchange exchange) { String origin = exchange.getRequestHeader(Headers.ORIGIN_STRING); if (origin != null) { exchange.setResponseHeader(Headers.ORIGIN_STRING, origin); } selectSubprotocol(exchange); selectExtensions(exchange); exchange.setResponseHeader(Headers.SEC_WEB_SOCKET_LOCATION_STRING, getWebSocketLocation(exchange)); final String key = exchange.getRequestHeader(Headers.SEC_WEB_SOCKET_KEY_STRING); try { final String solution = solve(key); exchange.setResponseHeader(Headers.SEC_WEB_SOCKET_ACCEPT_STRING, solution); performUpgrade(exchange); } catch (NoSuchAlgorithmException e) { IoUtils.safeClose(exchange); exchange.endExchange(); return; } } @Override public WebSocketChannel createChannel(WebSocketHttpExchange exchange, final StreamConnection channel, final ByteBufferPool pool) { List extensionFunctions = initExtensions(exchange); return new WebSocket13Channel(channel, pool, getWebSocketLocation(exchange), exchange.getResponseHeader(Headers.SEC_WEB_SOCKET_PROTOCOL_STRING), false, !extensionFunctions.isEmpty(), CompositeExtensionFunction.compose(extensionFunctions), exchange.getPeerConnections(), exchange.getOptions()); } } WebSocket13Channel.java000066400000000000000000000034451420065311100352440ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/core/protocol/version13/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol.version13; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketVersion; import io.undertow.websockets.core.protocol.version07.WebSocket07Channel; import io.undertow.websockets.extensions.ExtensionFunction; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import org.xnio.StreamConnection; import java.util.Set; /** * * A WebSocketChannel that handles version 13 * * @author Norman Maurer */ public class WebSocket13Channel extends WebSocket07Channel { public WebSocket13Channel(StreamConnection channel, ByteBufferPool bufferPool, String wsUrl, String subProtocols, final boolean client, boolean allowExtensions, final ExtensionFunction extensionFunction, Set openConnections, OptionMap options) { super(channel, bufferPool, wsUrl, subProtocols, client, allowExtensions, extensionFunction, openConnections, options); } @Override public WebSocketVersion getVersion() { return WebSocketVersion.V13; } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/extensions/000077500000000000000000000000001420065311100267045ustar00rootroot00000000000000CompositeExtensionFunction.java000066400000000000000000000047731420065311100350500ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/extensionspackage io.undertow.websockets.extensions; import io.undertow.connector.PooledByteBuffer; import io.undertow.websockets.core.StreamSinkFrameChannel; import io.undertow.websockets.core.StreamSourceFrameChannel; import java.io.IOException; import java.util.List; public class CompositeExtensionFunction implements ExtensionFunction { private final ExtensionFunction[] delegates; private CompositeExtensionFunction(ExtensionFunction... delegates) { this.delegates = delegates; } public static ExtensionFunction compose(List functions) { if (null == functions) { return NoopExtensionFunction.INSTANCE; } return compose(functions.toArray(new ExtensionFunction[functions.size()])); } public static ExtensionFunction compose(ExtensionFunction... functions) { if (functions == null || functions.length == 0) { return NoopExtensionFunction.INSTANCE; } else if (functions.length == 1) { return functions[0]; } return new CompositeExtensionFunction(functions); } @Override public boolean hasExtensionOpCode() { for (ExtensionFunction delegate : delegates) { if (delegate.hasExtensionOpCode()) { return true; } } return false; } @Override public int writeRsv(int rsv) { for (ExtensionFunction ext : delegates) { rsv = ext.writeRsv(rsv); } return rsv; } @Override public PooledByteBuffer transformForWrite(PooledByteBuffer pooledBuffer, StreamSinkFrameChannel channel, boolean lastFrame) throws IOException { PooledByteBuffer result = pooledBuffer; for (ExtensionFunction delegate : delegates) { result = delegate.transformForWrite(result, channel, lastFrame); } return result; } @Override public PooledByteBuffer transformForRead(PooledByteBuffer pooledBuffer, StreamSourceFrameChannel channel, boolean lastFragementOfMessage) throws IOException { PooledByteBuffer result = pooledBuffer; // TODO do we iterate over functions in the opposite order when reading vs writing? for (ExtensionFunction delegate : delegates) { result = delegate.transformForRead(result, channel, lastFragementOfMessage); } return result; } @Override public void dispose() { for (ExtensionFunction delegate : delegates) { delegate.dispose(); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/extensions/ExtensionFunction.java000066400000000000000000000071411420065311100332340ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.extensions; import io.undertow.connector.PooledByteBuffer; import io.undertow.websockets.core.StreamSinkFrameChannel; import io.undertow.websockets.core.StreamSourceFrameChannel; import java.io.IOException; /** * Base interface for WebSocket Extensions implementation. *

* It interacts at the connection phase. It is responsible to apply extension's logic before to write and after to read to/from * a WebSocket Endpoint. *

* Several extensions can be present in a WebSocket Endpoint being executed in a chain pattern. *

* Extension state is stored per WebSocket connection. * * @author Lucas Ponce */ public interface ExtensionFunction { /** * Bitmask for RSV1 bit used in extensions. */ int RSV1 = 0x04; /** * Bitmask for RSV2 bit used in extensions. */ int RSV2 = 0x02; /** * Bitmask for RSV3 bit used in extensions. */ int RSV3 = 0x01; /** * Validate if current extension defines a new WebSocket Opcode. * * @return {@code true} if current extension defines specific Opcode * {@code false} is current extension does not define specific Opcode * @see WebSocket Base Framing Protocol Reference */ boolean hasExtensionOpCode(); /** * Add RSV bits (RSV1, RSV2, RSV3) to the current rsv status. * * @param rsv current RSV bits status * @return rsv status */ int writeRsv(int rsv); /** * Transform the supplied buffer per this extension. The buffer can be modified in place, or a new pooled buffer * can be returned (in which case be sure to free the original buffer * * @param pooledBuffer Buffer to transform * @param channel working channel * @return transformed buffer (may be the same one, just with modified contents) * @throws IOException */ PooledByteBuffer transformForWrite(PooledByteBuffer pooledBuffer, StreamSinkFrameChannel channel, boolean lastFrame) throws IOException; /** * Transform the supplied buffer per this extension. The buffer can be modified in place, or a new pooled buffer * can be returned (in which case be sure to free the original buffer * * @param pooledBuffer Buffer to transform * @param channel working channel * @param lastFragmentOfMessage If this frame is the last fragment of a message. Note that this may not be received for every message, if the message ends with an empty frame * @return transformed buffer (may be the same one, just with modified contents) * @throws IOException */ PooledByteBuffer transformForRead(PooledByteBuffer pooledBuffer, StreamSourceFrameChannel channel, boolean lastFragmentOfMessage) throws IOException; /** * Dispose this function. Called upon connection closure */ void dispose(); } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/extensions/ExtensionHandshake.java000066400000000000000000000042721420065311100333370ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.extensions; import java.util.List; import io.undertow.websockets.WebSocketExtension; /** * Base interface for WebSocket Extension handshake. *

* It is responsible of the definition and negotiation logic of a WebSocket Extension. It interacts at the handshake phase. *

* It creates new instances of {@link ExtensionFunction} . * * @author Lucas Ponce */ public interface ExtensionHandshake { /** * @return name of the WebSocket Extension */ String getName(); /** * Validate if an extension request is accepted. * * @param extension the extension request representation * @return a new {@link WebSocketExtension} instance with parameters accepted; * {@code null} in case extension request is not accepted */ WebSocketExtension accept(final WebSocketExtension extension); /** * Validate if current extension is compatible with previously negotiated in the server side. * * @param extensions a list of negotiated extensions * @return {@code true} if current extension is compatible; * {@code false} if current extension is not compatible */ boolean isIncompatible(final List extensions); /** * Create a new instance of the {@link ExtensionFunction} associated to this WebSocket Extension. * * @return a new instance {@link ExtensionFunction} */ ExtensionFunction create(); } NoopExtensionFunction.java000066400000000000000000000017611420065311100340130ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/extensionspackage io.undertow.websockets.extensions; import io.undertow.connector.PooledByteBuffer; import io.undertow.websockets.core.StreamSinkFrameChannel; import io.undertow.websockets.core.StreamSourceFrameChannel; import java.io.IOException; public class NoopExtensionFunction implements ExtensionFunction { public static final ExtensionFunction INSTANCE = new NoopExtensionFunction(); @Override public boolean hasExtensionOpCode() { return false; } @Override public int writeRsv(int rsv) { return 0; } @Override public PooledByteBuffer transformForWrite(PooledByteBuffer pooledBuffer, StreamSinkFrameChannel channel, boolean lastFrame) throws IOException { return pooledBuffer; } @Override public PooledByteBuffer transformForRead(PooledByteBuffer pooledBuffer, StreamSourceFrameChannel channel, boolean lastFragmentOfFrame) throws IOException { return pooledBuffer; } @Override public void dispose() { } } PerMessageDeflateFunction.java000066400000000000000000000232461420065311100345250ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/extensions/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.extensions; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import io.undertow.util.ImmediatePooledByteBuffer; import io.undertow.websockets.core.StreamSinkFrameChannel; import io.undertow.websockets.core.StreamSourceFrameChannel; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketLogger; import io.undertow.websockets.core.WebSocketMessages; import org.xnio.Buffers; import org.xnio.IoUtils; import java.io.IOException; import java.nio.ByteBuffer; import java.util.zip.DataFormatException; import java.util.zip.Deflater; import java.util.zip.Inflater; /** * Implementation of {@code permessage-deflate} WebSocket Extension. *

* This implementation supports parameters: {@code server_no_context_takeover, client_no_context_takeover} . *

* This implementation does not support parameters: {@code server_max_window_bits, client_max_window_bits} . *

* It uses the DEFLATE implementation algorithm packaged on {@link Deflater} and {@link Inflater} classes. * * @author Lucas Ponce * @see Compression Extensions for WebSocket */ public class PerMessageDeflateFunction implements ExtensionFunction { private static final byte[] TAIL = new byte[]{0x00, 0x00, (byte) 0xFF, (byte) 0xFF}; private final int deflaterLevel; private final boolean compressContextTakeover; private final boolean decompressContextTakeover; private final Inflater decompress; private final Deflater compress; private StreamSourceFrameChannel currentReadChannel; /** * Create a new {@code PerMessageDeflateExtension} instance. * * @param deflaterLevel the level of configuration of DEFLATE algorithm implementation * @param compressContextTakeover flag for compressor context takeover or without compressor context * @param decompressContextTakeover flag for decompressor context takeover or without decompressor context */ public PerMessageDeflateFunction(final int deflaterLevel, boolean compressContextTakeover, boolean decompressContextTakeover) { this.deflaterLevel = deflaterLevel; this.decompress = new Inflater(true); this.compress = new Deflater(this.deflaterLevel, true); this.compressContextTakeover = compressContextTakeover; this.decompressContextTakeover = decompressContextTakeover; } @Override public int writeRsv(int rsv) { return rsv | RSV1; } @Override public boolean hasExtensionOpCode() { return false; } @Override public synchronized PooledByteBuffer transformForWrite(PooledByteBuffer pooledBuffer, StreamSinkFrameChannel channel, boolean lastFrame) throws IOException { ByteBuffer buffer = pooledBuffer.getBuffer(); PooledByteBuffer inputBuffer = null; if (buffer.hasArray()) { compress.setInput(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); } else { inputBuffer = toArrayBacked(buffer, channel.getWebSocketChannel().getBufferPool()); compress.setInput(inputBuffer.getBuffer().array(), inputBuffer.getBuffer().arrayOffset() + inputBuffer.getBuffer().position(), inputBuffer.getBuffer().remaining()); } PooledByteBuffer output = allocateBufferWithArray(channel.getWebSocketChannel(), 0); // first pass ByteBuffer outputBuffer = output.getBuffer(); boolean onceOnly = true; try { while ((!compress.needsInput() && !compress.finished()) || !outputBuffer.hasRemaining() || (onceOnly && lastFrame)) { onceOnly = false; //we need the hasRemaining check, because if the inflater fails to flush needsInput() will return false but it may have flushed an incomplete deflate block if (!outputBuffer.hasRemaining()) { output = largerBuffer(output, channel.getWebSocketChannel(), outputBuffer.capacity() * 2); outputBuffer = output.getBuffer(); } int n = compress.deflate( outputBuffer.array(), outputBuffer.arrayOffset() + outputBuffer.position(), outputBuffer.remaining(), lastFrame ? Deflater.SYNC_FLUSH : Deflater.NO_FLUSH ); outputBuffer.position(outputBuffer.position() + n); } } finally { // Free the buffer AFTER compression so it doesn't get re-used out from under us IoUtils.safeClose(pooledBuffer, inputBuffer); } if(lastFrame) { outputBuffer.put((byte) 0); if (!compressContextTakeover) { compress.reset(); } } outputBuffer.flip(); return output; } private PooledByteBuffer toArrayBacked(ByteBuffer buffer, ByteBufferPool pool) { if(pool.getBufferSize() < buffer.remaining()) { return new ImmediatePooledByteBuffer(ByteBuffer.wrap(Buffers.take(buffer))); } PooledByteBuffer newBuf = pool.getArrayBackedPool().allocate(); newBuf.getBuffer().put(buffer); newBuf.getBuffer().flip(); return newBuf; } private PooledByteBuffer largerBuffer(PooledByteBuffer smaller, WebSocketChannel channel, int newSize) { ByteBuffer smallerBuffer = smaller.getBuffer(); smallerBuffer.flip(); PooledByteBuffer larger = allocateBufferWithArray(channel, newSize); larger.getBuffer().put(smallerBuffer); smaller.close(); return larger; } private PooledByteBuffer allocateBufferWithArray(WebSocketChannel channel, int size) { if (size > 0) { if(size > channel.getBufferPool().getBufferSize()) { // TODO use newer XNIO sized pool thingies smartly return new ImmediatePooledByteBuffer(ByteBuffer.allocate(size)); } } return channel.getBufferPool().getArrayBackedPool().allocate(); } @Override public synchronized PooledByteBuffer transformForRead(PooledByteBuffer pooledBuffer, StreamSourceFrameChannel channel, boolean lastFragmentOfMessage) throws IOException { if ((channel.getRsv() & 4) == 0) { //rsv bit not set, this message is not compressed return pooledBuffer; } PooledByteBuffer output = allocateBufferWithArray(channel.getWebSocketChannel(), 0); // first pass PooledByteBuffer inputBuffer = null; if (currentReadChannel != null && currentReadChannel != channel) { //new channel, we did not get a last fragment message which can happens sometimes decompress.setInput(TAIL); output = decompress(channel.getWebSocketChannel(), output); } ByteBuffer buffer = pooledBuffer.getBuffer(); if (buffer.hasArray()) { decompress.setInput(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); } else { inputBuffer = toArrayBacked(buffer, channel.getWebSocketChannel().getBufferPool()); decompress.setInput(inputBuffer.getBuffer().array(), inputBuffer.getBuffer().arrayOffset() + inputBuffer.getBuffer().position(), inputBuffer.getBuffer().remaining()); } try { output = decompress(channel.getWebSocketChannel(), output); } finally { // Free the buffer AFTER decompression so it doesn't get re-used out from under us IoUtils.safeClose(inputBuffer, pooledBuffer); } if (lastFragmentOfMessage) { decompress.setInput(TAIL); output = decompress(channel.getWebSocketChannel(), output); currentReadChannel = null; } else { currentReadChannel = channel; } output.getBuffer().flip(); return output; } private PooledByteBuffer decompress(WebSocketChannel channel, PooledByteBuffer pooled) throws IOException { ByteBuffer buffer = pooled.getBuffer(); while (!decompress.needsInput() && !decompress.finished()) { if (!buffer.hasRemaining()) { pooled = largerBuffer(pooled, channel, buffer.capacity() * 2); buffer = pooled.getBuffer(); } int n; try { n = decompress.inflate(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); } catch (DataFormatException e) { WebSocketLogger.EXTENSION_LOGGER.debug(e.getMessage(), e); throw WebSocketMessages.MESSAGES.badCompressedPayload(e); } buffer.position(buffer.position() + n); } return pooled; } @Override public void dispose() { // Call end so that native zlib resources can be immediately released rather than relying on finalizer compress.end(); decompress.end(); } } PerMessageDeflateHandshake.java000066400000000000000000000160031420065311100346170ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/extensions/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.extensions; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.zip.Deflater; import io.undertow.websockets.WebSocketExtension; import io.undertow.websockets.core.WebSocketLogger; /** * Implementation of {@code permessage-deflate} WebSocket Extension handshake. *

* This implementation supports parameters: {@code server_no_context_takeover, client_no_context_takeover} . *

* This implementation does not support parameters: {@code server_max_window_bits, client_max_window_bits} . * * @see Compression Extensions for WebSocket * * @author Lucas Ponce */ public class PerMessageDeflateHandshake implements ExtensionHandshake { private static final String PERMESSAGE_DEFLATE = "permessage-deflate"; private static final String SERVER_NO_CONTEXT_TAKEOVER = "server_no_context_takeover"; private static final String CLIENT_NO_CONTEXT_TAKEOVER = "client_no_context_takeover"; private static final String SERVER_MAX_WINDOW_BITS = "server_max_window_bits"; private static final String CLIENT_MAX_WINDOW_BITS = "client_max_window_bits"; private final Set incompatibleExtensions = new HashSet<>(); private boolean compressContextTakeover; private boolean decompressContextTakeover; private final boolean client; private final int deflaterLevel; /** * Default configuration for DEFLATE algorithm implementation */ public static final int DEFAULT_DEFLATER = Deflater.DEFAULT_COMPRESSION; public PerMessageDeflateHandshake() { this(false); } /** * Create a new {@code PerMessageDeflateHandshake} instance. * * @param client indicate if extension is configured in client ({@code true }) context or server ({@code false }) context. */ public PerMessageDeflateHandshake(final boolean client) { this(client, DEFAULT_DEFLATER); } /** * Create a new {@code PerMessageDeflateHandshake} instance. * * @param client indicate if extension is configured in client ({@code true }) context or server ({@code false }) context * @param deflaterLevel the level of configuration of DEFLATE algorithm implementation */ public PerMessageDeflateHandshake(final boolean client, final int deflaterLevel) { this(client, deflaterLevel, true, true); } /** * Create a new {@code PerMessageDeflateHandshake} instance. * * @param client flag for client ({@code true }) context or server ({@code false }) context * @param compressContextTakeover flag for compressor context takeover or without compressor context * @param decompressContextTakeover flag for decompressor context takeover or without decompressor context */ public PerMessageDeflateHandshake(final boolean client, boolean compressContextTakeover, boolean decompressContextTakeover) { this(client, DEFAULT_DEFLATER, compressContextTakeover, decompressContextTakeover); } /** * Create a new {@code PerMessageDeflateHandshake} instance. * * @param client flag for client ({@code true }) context or server ({@code false }) context * @param deflaterLevel the level of configuration of DEFLATE algorithm implementation * @param compressContextTakeover flag for compressor context takeover or without compressor context * @param decompressContextTakeover flag for decompressor context takeover or without decompressor context */ public PerMessageDeflateHandshake(final boolean client, final int deflaterLevel, boolean compressContextTakeover, boolean decompressContextTakeover) { this.client = client; this.deflaterLevel = deflaterLevel; /* This extension is incompatible with multiple instances of same extension in the same Endpoint. */ incompatibleExtensions.add(PERMESSAGE_DEFLATE); this.compressContextTakeover = compressContextTakeover; this.decompressContextTakeover = decompressContextTakeover; } @Override public String getName() { return PERMESSAGE_DEFLATE; } @Override public WebSocketExtension accept(final WebSocketExtension extension) { if (extension == null || !extension.getName().equals(getName())) return null; WebSocketExtension negotiated = new WebSocketExtension(extension.getName()); if (extension.getParameters() == null || extension.getParameters().size() == 0) return negotiated; for (WebSocketExtension.Parameter parameter : extension.getParameters()) { if (parameter.getName().equals(SERVER_MAX_WINDOW_BITS)) { /* Not supported */ } else if (parameter.getName().equals(CLIENT_MAX_WINDOW_BITS)) { /* Not supported */ } else if (parameter.getName().equals(SERVER_NO_CONTEXT_TAKEOVER)) { negotiated.getParameters().add(parameter); if (client) { decompressContextTakeover = false; } else { compressContextTakeover = false; } } else if (parameter.getName().equals(CLIENT_NO_CONTEXT_TAKEOVER)) { negotiated.getParameters().add(parameter); if (client) { compressContextTakeover = false; } else { decompressContextTakeover = false; } } else { WebSocketLogger.EXTENSION_LOGGER.incorrectExtensionParameter(parameter); return null; } } WebSocketLogger.EXTENSION_LOGGER.debugf("Negotiated extension %s for handshake %s", negotiated, extension); return negotiated; } @Override public boolean isIncompatible(List extensions) { for (ExtensionHandshake extension : extensions) { if (incompatibleExtensions.contains(extension.getName())) { return true; } } return false; } @Override public ExtensionFunction create() { return new PerMessageDeflateFunction(deflaterLevel, compressContextTakeover, decompressContextTakeover); } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/spi/000077500000000000000000000000001420065311100253005ustar00rootroot00000000000000AsyncWebSocketHttpServerExchange.java000066400000000000000000000242611420065311100344470ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/spi/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.spi; import io.undertow.UndertowLogger; import io.undertow.io.IoCallback; import io.undertow.io.Sender; import io.undertow.security.api.SecurityContext; import io.undertow.security.idm.Account; import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpUpgradeListener; import io.undertow.server.session.SessionConfig; import io.undertow.server.session.SessionManager; import io.undertow.util.AttachmentKey; import io.undertow.util.HeaderMap; import io.undertow.util.HttpString; import io.undertow.websockets.core.WebSocketChannel; import org.xnio.ChannelListener; import org.xnio.FinishedIoFuture; import org.xnio.FutureResult; import org.xnio.IoFuture; import org.xnio.IoUtils; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import org.xnio.channels.StreamSourceChannel; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.security.Principal; import java.util.ArrayList; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; /** * @author Stuart Douglas */ public class AsyncWebSocketHttpServerExchange implements WebSocketHttpExchange { private final HttpServerExchange exchange; private Sender sender; private final Set peerConnections; public AsyncWebSocketHttpServerExchange(final HttpServerExchange exchange, Set peerConnections) { this.exchange = exchange; this.peerConnections = peerConnections; } @Override public void putAttachment(final AttachmentKey key, final T value) { exchange.putAttachment(key, value); } @Override public T getAttachment(final AttachmentKey key) { return exchange.getAttachment(key); } @Override public String getRequestHeader(final String headerName) { return exchange.getRequestHeaders().getFirst(HttpString.tryFromString(headerName)); } @Override public Map> getRequestHeaders() { Map> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); for (final HttpString header : exchange.getRequestHeaders().getHeaderNames()) { headers.put(header.toString(), new ArrayList<>(exchange.getRequestHeaders().get(header))); } return Collections.unmodifiableMap(headers); } @Override public String getResponseHeader(final String headerName) { return exchange.getResponseHeaders().getFirst(HttpString.tryFromString(headerName)); } @Override public Map> getResponseHeaders() { Map> headers = new HashMap<>(); for (final HttpString header : exchange.getResponseHeaders().getHeaderNames()) { headers.put(header.toString(), new ArrayList<>(exchange.getResponseHeaders().get(header))); } return Collections.unmodifiableMap(headers); } @Override public void setResponseHeaders(final Map> headers) { HeaderMap map = exchange.getRequestHeaders(); map.clear(); for (Map.Entry> header : headers.entrySet()) { map.addAll(HttpString.tryFromString(header.getKey()), header.getValue()); } } @Override public void setResponseHeader(final String headerName, final String headerValue) { exchange.getResponseHeaders().put(HttpString.tryFromString(headerName), headerValue); } @Override public void upgradeChannel(final HttpUpgradeListener upgradeCallback) { exchange.upgradeChannel(upgradeCallback); } @Override public IoFuture sendData(final ByteBuffer data) { if (sender == null) { this.sender = exchange.getResponseSender(); } final FutureResult future = new FutureResult<>(); sender.send(data, new IoCallback() { @Override public void onComplete(final HttpServerExchange exchange, final Sender sender) { future.setResult(null); } @Override public void onException(final HttpServerExchange exchange, final Sender sender, final IOException exception) { UndertowLogger.REQUEST_IO_LOGGER.ioException(exception); future.setException(exception); } }); return future.getIoFuture(); } @Override public IoFuture readRequestData() { final ByteArrayOutputStream data = new ByteArrayOutputStream(); final PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().allocate(); final ByteBuffer buffer = pooled.getBuffer(); final StreamSourceChannel channel = exchange.getRequestChannel(); int res; for (; ; ) { try { res = channel.read(buffer); if (res == -1) { return new FinishedIoFuture<>(data.toByteArray()); } else if (res == 0) { //callback final FutureResult future = new FutureResult<>(); channel.getReadSetter().set(new ChannelListener() { @Override public void handleEvent(final StreamSourceChannel channel) { int res; try { res = channel.read(buffer); if (res == -1) { future.setResult(data.toByteArray()); channel.suspendReads(); return; } else if (res == 0) { return; } else { buffer.flip(); while (buffer.hasRemaining()) { data.write(buffer.get()); } buffer.clear(); } } catch (IOException e) { future.setException(e); } } }); channel.resumeReads(); return future.getIoFuture(); } else { buffer.flip(); while (buffer.hasRemaining()) { data.write(buffer.get()); } buffer.clear(); } } catch (IOException e) { final FutureResult future = new FutureResult<>(); future.setException(e); return future.getIoFuture(); } } } @Override public void endExchange() { exchange.endExchange(); } @Override public void close() { try { exchange.endExchange(); } finally { IoUtils.safeClose(exchange.getConnection()); } } @Override public String getRequestScheme() { return exchange.getRequestScheme(); } @Override public String getRequestURI() { String q = exchange.getQueryString(); if (q == null || q.isEmpty()) { return exchange.getRequestURI(); } else { return exchange.getRequestURI() + "?" + q; } } @Override public ByteBufferPool getBufferPool() { return exchange.getConnection().getByteBufferPool(); } @Override public String getQueryString() { return exchange.getQueryString(); } @Override public Object getSession() { SessionManager sm = exchange.getAttachment(SessionManager.ATTACHMENT_KEY); SessionConfig sessionCookieConfig = exchange.getAttachment(SessionConfig.ATTACHMENT_KEY); if(sm != null && sessionCookieConfig != null) { return sm.getSession(exchange, sessionCookieConfig); } return null; } @Override public Map> getRequestParameters() { Map> params = new HashMap<>(); for (Map.Entry> param : exchange.getQueryParameters().entrySet()) { params.put(param.getKey(), new ArrayList<>(param.getValue())); } return params; } @Override public Principal getUserPrincipal() { SecurityContext sc = exchange.getSecurityContext(); if(sc == null) { return null; } Account authenticatedAccount = sc.getAuthenticatedAccount(); if(authenticatedAccount == null) { return null; } return authenticatedAccount.getPrincipal(); } @Override public boolean isUserInRole(String role) { SecurityContext sc = exchange.getSecurityContext(); if(sc == null) { return false; } Account authenticatedAccount = sc.getAuthenticatedAccount(); if(authenticatedAccount == null) { return false; } return authenticatedAccount.getRoles().contains(role); } @Override public Set getPeerConnections() { return peerConnections; } @Override public OptionMap getOptions() { return exchange.getConnection().getUndertowOptions(); } } BlockingWebSocketHttpServerExchange.java000066400000000000000000000050201420065311100351120ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/spi/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.spi; import io.undertow.server.HttpServerExchange; import io.undertow.websockets.core.WebSocketChannel; import org.xnio.FinishedIoFuture; import org.xnio.FutureResult; import org.xnio.IoFuture; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.Set; /** * @author Stuart Douglas */ public class BlockingWebSocketHttpServerExchange extends AsyncWebSocketHttpServerExchange { private final OutputStream out; private final InputStream in; public BlockingWebSocketHttpServerExchange(final HttpServerExchange exchange, Set peerConnections) { super(exchange, peerConnections); out = exchange.getOutputStream(); in = exchange.getInputStream(); } @Override public IoFuture sendData(final ByteBuffer data) { try { while (data.hasRemaining()) { out.write(data.get()); } return new FinishedIoFuture<>(null); } catch (IOException e) { final FutureResult ioFuture = new FutureResult<>(); ioFuture.setException(e); return ioFuture.getIoFuture(); } } @Override public IoFuture readRequestData() { final ByteArrayOutputStream data = new ByteArrayOutputStream(); try { byte[] buf = new byte[1024]; int r; while ((r = in.read(buf)) != -1) { data.write(buf, 0, r); } return new FinishedIoFuture<>(data.toByteArray()); } catch (IOException e) { final FutureResult ioFuture = new FutureResult<>(); ioFuture.setException(e); return ioFuture.getIoFuture(); } } } undertow-2.2.16.Final/core/src/main/java/io/undertow/websockets/spi/WebSocketHttpExchange.java000066400000000000000000000102251420065311100323340ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.spi; import io.undertow.server.HttpUpgradeListener; import io.undertow.util.AttachmentKey; import io.undertow.websockets.core.WebSocketChannel; import org.xnio.IoFuture; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import java.io.Closeable; import java.nio.ByteBuffer; import java.security.Principal; import java.util.List; import java.util.Map; import java.util.Set; /** * An abstraction for a Http exchange. Undertow uses 3 different types of exchanges: *

* - async * - blocking * - servlet *

* This class provides a way to operate on the underling exchange while providing the * correct semantics regardless of the underlying exchange type. *

* The main use case for this is web sockets. Web sockets should be able to perform * a handshake regardless of the nature of the underlying request, while still respecting * servlet filters, security etc. * * @author Stuart Douglas */ public interface WebSocketHttpExchange extends Closeable { void putAttachment(final AttachmentKey key, T value); T getAttachment(final AttachmentKey key); /** * gets the first request header with the specified name * * @param headerName The header name * @return The header value, or null */ String getRequestHeader(final String headerName); /** * @return An unmodifiable map of request headers */ Map> getRequestHeaders(); /** * get a response header * * @param headerName The header name * @return The header value, or null */ String getResponseHeader(final String headerName); /** * @return An unmodifiable map of response headers */ Map> getResponseHeaders(); /** * Sets the response headers */ void setResponseHeaders(final Map> headers); /** * Set a response header * * @param headerName The header name * @param headerValue The header value */ void setResponseHeader(final String headerName, final String headerValue); /** * Upgrade the underlying channel * * @param upgradeCallback */ void upgradeChannel(final HttpUpgradeListener upgradeCallback); /** * Send some data * * @param data The data */ IoFuture sendData(final ByteBuffer data); /** * Gets the body of the request. */ IoFuture readRequestData(); /** * End the exchange normally. If this is a blocking exchange this may be a noop, and the exchange * will actually end when the call stack returns */ void endExchange(); /** * Forcibly close the exchange. */ void close(); /** * Get the request scheme, usually http or https * * @return The request scheme */ String getRequestScheme(); /** * @return The request URI, including the query string */ String getRequestURI(); /** * @return The buffer pool */ ByteBufferPool getBufferPool(); /** * @return The query string */ String getQueryString(); /** * Gets the session, if any * * @return The session object, or null */ Object getSession(); Map> getRequestParameters(); Principal getUserPrincipal(); boolean isUserInRole(String role); Set getPeerConnections(); OptionMap getOptions(); } undertow-2.2.16.Final/core/src/main/java9/000077500000000000000000000000001420065311100201475ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java9/io/000077500000000000000000000000001420065311100205565ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java9/io/undertow/000077500000000000000000000000001420065311100224255ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java9/io/undertow/util/000077500000000000000000000000001420065311100234025ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/java9/io/undertow/util/FastConcurrentDirectDeque.java000066400000000000000000001704441420065311100313360ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ /* * Written by Doug Lea and Martin Buchholz with assistance from members of * JCP JSR-166 Expert Group and released to the public domain, as explained * at http://creativecommons.org/publicdomain/zero/1.0/ */ package io.undertow.util; import java.io.Serializable; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.util.Arrays; import java.util.Collection; import java.util.Deque; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Queue; import java.util.Spliterator; import java.util.Spliterators; import java.util.function.Consumer; import java.util.function.Predicate; /** * A modified version of ConcurrentLinkedDeque which includes direct * removal. Like the original, it relies on Unsafe for better performance. * * More specifically, an unbounded concurrent {@linkplain Deque deque} based on linked nodes. * Concurrent insertion, removal, and access operations execute safely * across multiple threads. * A {@code ConcurrentLinkedDeque} is an appropriate choice when * many threads will share access to a common collection. * Like most other concurrent collection implementations, this class * does not permit the use of {@code null} elements. * *

Iterators and spliterators are * weakly consistent. * *

Beware that, unlike in most collections, the {@code size} method * is NOT a constant-time operation. Because of the * asynchronous nature of these deques, determining the current number * of elements requires a traversal of the elements, and so may report * inaccurate results if this collection is modified during traversal. * *

Bulk operations that add, remove, or examine multiple elements, * such as {@link #addAll}, {@link #removeIf} or {@link #forEach}, * are not guaranteed to be performed atomically. * For example, a {@code forEach} traversal concurrent with an {@code * addAll} operation might observe only some of the added elements. * *

This class and its iterator implement all of the optional * methods of the {@link Deque} and {@link Iterator} interfaces. * *

Memory consistency effects: As with other concurrent collections, * actions in a thread prior to placing an object into a * {@code ConcurrentLinkedDeque} * happen-before * actions subsequent to the access or removal of that element from * the {@code ConcurrentLinkedDeque} in another thread. * *

This class is a member of the * * Java Collections Framework. * * Based on revision 1.88 of ConcurrentLinkedDeque * (see http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/main/java/util/concurrent/ConcurrentLinkedDeque.java?revision=1.88&view=markup) * This is the version used in JDK 9 b156. * * @since 1.7 * @author Doug Lea * @author Martin Buchholz * @author Jason T. Grene * @param the type of elements held in this deque */ public class FastConcurrentDirectDeque extends ConcurrentDirectDeque implements Deque, Serializable { /* * This is an implementation of a concurrent lock-free deque * supporting interior removes but not interior insertions, as * required to support the entire Deque interface. * * We extend the techniques developed for ConcurrentLinkedQueue and * LinkedTransferQueue (see the internal docs for those classes). * Understanding the ConcurrentLinkedQueue implementation is a * prerequisite for understanding the implementation of this class. * * The data structure is a symmetrical doubly-linked "GC-robust" * linked list of nodes. We minimize the number of volatile writes * using two techniques: advancing multiple hops with a single CAS * and mixing volatile and non-volatile writes of the same memory * locations. * * A node contains the expected E ("item") and links to predecessor * ("prev") and successor ("next") nodes: * * class Node { volatile Node prev, next; volatile E item; } * * A node p is considered "live" if it contains a non-null item * (p.item != null). When an item is CASed to null, the item is * atomically logically deleted from the collection. * * At any time, there is precisely one "first" node with a null * prev reference that terminates any chain of prev references * starting at a live node. Similarly there is precisely one * "last" node terminating any chain of next references starting at * a live node. The "first" and "last" nodes may or may not be live. * The "first" and "last" nodes are always mutually reachable. * * A new element is added atomically by CASing the null prev or * next reference in the first or last node to a fresh node * containing the element. The element's node atomically becomes * "live" at that point. * * A node is considered "active" if it is a live node, or the * first or last node. Active nodes cannot be unlinked. * * A "self-link" is a next or prev reference that is the same node: * p.prev == p or p.next == p * Self-links are used in the node unlinking process. Active nodes * never have self-links. * * A node p is active if and only if: * * p.item != null || * (p.prev == null && p.next != p) || * (p.next == null && p.prev != p) * * The deque object has two node references, "head" and "tail". * The head and tail are only approximations to the first and last * nodes of the deque. The first node can always be found by * following prev pointers from head; likewise for tail. However, * it is permissible for head and tail to be referring to deleted * nodes that have been unlinked and so may not be reachable from * any live node. * * There are 3 stages of node deletion; * "logical deletion", "unlinking", and "gc-unlinking". * * 1. "logical deletion" by CASing item to null atomically removes * the element from the collection, and makes the containing node * eligible for unlinking. * * 2. "unlinking" makes a deleted node unreachable from active * nodes, and thus eventually reclaimable by GC. Unlinked nodes * may remain reachable indefinitely from an iterator. * * Physical node unlinking is merely an optimization (albeit a * critical one), and so can be performed at our convenience. At * any time, the set of live nodes maintained by prev and next * links are identical, that is, the live nodes found via next * links from the first node is equal to the elements found via * prev links from the last node. However, this is not true for * nodes that have already been logically deleted - such nodes may * be reachable in one direction only. * * 3. "gc-unlinking" takes unlinking further by making active * nodes unreachable from deleted nodes, making it easier for the * GC to reclaim future deleted nodes. This step makes the data * structure "gc-robust", as first described in detail by Boehm * (http://portal.acm.org/citation.cfm?doid=503272.503282). * * GC-unlinked nodes may remain reachable indefinitely from an * iterator, but unlike unlinked nodes, are never reachable from * head or tail. * * Making the data structure GC-robust will eliminate the risk of * unbounded memory retention with conservative GCs and is likely * to improve performance with generational GCs. * * When a node is dequeued at either end, e.g. via poll(), we would * like to break any references from the node to active nodes. We * develop further the use of self-links that was very effective in * other concurrent collection classes. The idea is to replace * prev and next pointers with special values that are interpreted * to mean off-the-list-at-one-end. These are approximations, but * good enough to preserve the properties we want in our * traversals, e.g. we guarantee that a traversal will never visit * the same element twice, but we don't guarantee whether a * traversal that runs out of elements will be able to see more * elements later after enqueues at that end. Doing gc-unlinking * safely is particularly tricky, since any node can be in use * indefinitely (for example by an iterator). We must ensure that * the nodes pointed at by head/tail never get gc-unlinked, since * head/tail are needed to get "back on track" by other nodes that * are gc-unlinked. gc-unlinking accounts for much of the * implementation complexity. * * Since neither unlinking nor gc-unlinking are necessary for * correctness, there are many implementation choices regarding * frequency (eagerness) of these operations. Since volatile * reads are likely to be much cheaper than CASes, saving CASes by * unlinking multiple adjacent nodes at a time may be a win. * gc-unlinking can be performed rarely and still be effective, * since it is most important that long chains of deleted nodes * are occasionally broken. * * The actual representation we use is that p.next == p means to * goto the first node (which in turn is reached by following prev * pointers from head), and p.next == null && p.prev == p means * that the iteration is at an end and that p is a (static final) * dummy node, NEXT_TERMINATOR, and not the last active node. * Finishing the iteration when encountering such a TERMINATOR is * good enough for read-only traversals, so such traversals can use * p.next == null as the termination condition. When we need to * find the last (active) node, for enqueueing a new node, we need * to check whether we have reached a TERMINATOR node; if so, * restart traversal from tail. * * The implementation is completely directionally symmetrical, * except that most public methods that iterate through the list * follow next pointers ("forward" direction). * * We believe (without full proof) that all single-element deque * operations (e.g., addFirst, peekLast, pollLast) are linearizable * (see Herlihy and Shavit's book). However, some combinations of * operations are known not to be linearizable. In particular, * when an addFirst(A) is racing with pollFirst() removing B, it is * possible for an observer iterating over the elements to observe * A B C and subsequently observe A C, even though no interior * removes are ever performed. Nevertheless, iterators behave * reasonably, providing the "weakly consistent" guarantees. * * Empirically, microbenchmarks suggest that this class adds about * 40% overhead relative to ConcurrentLinkedQueue, which feels as * good as we can hope for. */ private static final long serialVersionUID = 876323262645176354L; /** * A node from which the first node on list (that is, the unique node p * with p.prev == null && p.next != p) can be reached in O(1) time. * Invariants: * - the first node is always O(1) reachable from head via prev links * - all live nodes are reachable from the first node via succ() * - head != null * - (tmp = head).next != tmp || tmp != head * - head is never gc-unlinked (but may be unlinked) * Non-invariants: * - head.item may or may not be null * - head may not be reachable from the first or last node, or from tail */ private transient volatile Node head; /** * A node from which the last node on list (that is, the unique node p * with p.next == null && p.prev != p) can be reached in O(1) time. * Invariants: * - the last node is always O(1) reachable from tail via next links * - all live nodes are reachable from the last node via pred() * - tail != null * - tail is never gc-unlinked (but may be unlinked) * Non-invariants: * - tail.item may or may not be null * - tail may not be reachable from the first or last node, or from head */ private transient volatile Node tail; private static final Node PREV_TERMINATOR, NEXT_TERMINATOR; @SuppressWarnings("unchecked") Node prevTerminator() { return (Node) PREV_TERMINATOR; } @SuppressWarnings("unchecked") Node nextTerminator() { return (Node) NEXT_TERMINATOR; } static final class Node { volatile Node prev; volatile E item; volatile Node next; } /** * Returns a new node holding item. Uses relaxed write because item * can only be seen after piggy-backing publication via CAS. */ static Node newNode(E item) { Node node = new Node(); ITEM.set(node, item); return node; } /** * Links e as first element. */ private Node linkFirst(E e) { final Node newNode = newNode(Objects.requireNonNull(e)); restartFromHead: for (;;) for (Node h = head, p = h, q;;) { if ((q = p.prev) != null && (q = (p = q).prev) != null) // Check for head updates every other hop. // If p == q, we are sure to follow head instead. p = (h != (h = head)) ? h : q; else if (p.next == p) // PREV_TERMINATOR continue restartFromHead; else { // p is first node NEXT.set(newNode, p); // CAS piggyback if (PREV.compareAndSet(p, null, newNode)) { // Successful CAS is the linearization point // for e to become an element of this deque, // and for newNode to become "live". if (p != h) // hop two nodes at a time; failure is OK HEAD.weakCompareAndSet(this, h, newNode); return newNode; } // Lost CAS race to another thread; re-read prev } } } /** * Links e as last element. */ private Node linkLast(E e) { final Node newNode = newNode(Objects.requireNonNull(e)); restartFromTail: for (;;) for (Node t = tail, p = t, q;;) { if ((q = p.next) != null && (q = (p = q).next) != null) // Check for tail updates every other hop. // If p == q, we are sure to follow tail instead. p = (t != (t = tail)) ? t : q; else if (p.prev == p) // NEXT_TERMINATOR continue restartFromTail; else { // p is last node PREV.set(newNode, p); // CAS piggyback if (NEXT.compareAndSet(p, null, newNode)) { // Successful CAS is the linearization point // for e to become an element of this deque, // and for newNode to become "live". if (p != t) // hop two nodes at a time; failure is OK TAIL.weakCompareAndSet(this, t, newNode); return newNode; } // Lost CAS race to another thread; re-read next } } } private static final int HOPS = 2; /** * Unlinks non-null node x. */ void unlink(Node x) { // assert x != null; // assert x.item == null; // assert x != PREV_TERMINATOR; // assert x != NEXT_TERMINATOR; final Node prev = x.prev; final Node next = x.next; if (prev == null) { unlinkFirst(x, next); } else if (next == null) { unlinkLast(x, prev); } else { // Unlink interior node. // // This is the common case, since a series of polls at the // same end will be "interior" removes, except perhaps for // the first one, since end nodes cannot be unlinked. // // At any time, all active nodes are mutually reachable by // following a sequence of either next or prev pointers. // // Our strategy is to find the unique active predecessor // and successor of x. Try to fix up their links so that // they point to each other, leaving x unreachable from // active nodes. If successful, and if x has no live // predecessor/successor, we additionally try to gc-unlink, // leaving active nodes unreachable from x, by rechecking // that the status of predecessor and successor are // unchanged and ensuring that x is not reachable from // tail/head, before setting x's prev/next links to their // logical approximate replacements, self/TERMINATOR. Node activePred, activeSucc; boolean isFirst, isLast; int hops = 1; // Find active predecessor for (Node p = prev; ; ++hops) { if (p.item != null) { activePred = p; isFirst = false; break; } Node q = p.prev; if (q == null) { if (p.next == p) return; activePred = p; isFirst = true; break; } else if (p == q) return; else p = q; } // Find active successor for (Node p = next; ; ++hops) { if (p.item != null) { activeSucc = p; isLast = false; break; } Node q = p.next; if (q == null) { if (p.prev == p) return; activeSucc = p; isLast = true; break; } else if (p == q) return; else p = q; } // TODO: better HOP heuristics if (hops < HOPS // always squeeze out interior deleted nodes && (isFirst | isLast)) return; // Squeeze out deleted nodes between activePred and // activeSucc, including x. skipDeletedSuccessors(activePred); skipDeletedPredecessors(activeSucc); // Try to gc-unlink, if possible if ((isFirst | isLast) && // Recheck expected state of predecessor and successor (activePred.next == activeSucc) && (activeSucc.prev == activePred) && (isFirst ? activePred.prev == null : activePred.item != null) && (isLast ? activeSucc.next == null : activeSucc.item != null)) { updateHead(); // Ensure x is not reachable from head updateTail(); // Ensure x is not reachable from tail // Finally, actually gc-unlink PREV.setRelease(x, isFirst ? prevTerminator() : x); NEXT.setRelease(x, isLast ? nextTerminator() : x); } } } /** * Unlinks non-null first node. */ private void unlinkFirst(Node first, Node next) { // assert first != null; // assert next != null; // assert first.item == null; for (Node o = null, p = next, q;;) { if (p.item != null || (q = p.next) == null) { if (o != null && p.prev != p && NEXT.compareAndSet(first, next, p)) { skipDeletedPredecessors(p); if (first.prev == null && (p.next == null || p.item != null) && p.prev == first) { updateHead(); // Ensure o is not reachable from head updateTail(); // Ensure o is not reachable from tail // Finally, actually gc-unlink NEXT.setRelease(o, o); PREV.setRelease(o, prevTerminator()); } } return; } else if (p == q) return; else { o = p; p = q; } } } /** * Unlinks non-null last node. */ private void unlinkLast(Node last, Node prev) { // assert last != null; // assert prev != null; // assert last.item == null; for (Node o = null, p = prev, q;;) { if (p.item != null || (q = p.prev) == null) { if (o != null && p.next != p && PREV.compareAndSet(last, prev, p)) { skipDeletedSuccessors(p); if (last.next == null && (p.prev == null || p.item != null) && p.next == last) { updateHead(); // Ensure o is not reachable from head updateTail(); // Ensure o is not reachable from tail // Finally, actually gc-unlink PREV.setRelease(o, o); NEXT.setRelease(o, nextTerminator()); } } return; } else if (p == q) return; else { o = p; p = q; } } } /** * Guarantees that any node which was unlinked before a call to * this method will be unreachable from head after it returns. * Does not guarantee to eliminate slack, only that head will * point to a node that was active while this method was running. */ private void updateHead() { // Either head already points to an active node, or we keep // trying to cas it to the first node until it does. Node h, p, q; restartFromHead: while ((h = head).item == null && (p = h.prev) != null) { for (;;) { if ((q = p.prev) == null || (q = (p = q).prev) == null) { // It is possible that p is PREV_TERMINATOR, // but if so, the CAS is guaranteed to fail. if (HEAD.compareAndSet(this, h, p)) return; else continue restartFromHead; } else if (h != head) continue restartFromHead; else p = q; } } } /** * Guarantees that any node which was unlinked before a call to * this method will be unreachable from tail after it returns. * Does not guarantee to eliminate slack, only that tail will * point to a node that was active while this method was running. */ private void updateTail() { // Either tail already points to an active node, or we keep // trying to cas it to the last node until it does. Node t, p, q; restartFromTail: while ((t = tail).item == null && (p = t.next) != null) { for (;;) { if ((q = p.next) == null || (q = (p = q).next) == null) { // It is possible that p is NEXT_TERMINATOR, // but if so, the CAS is guaranteed to fail. if (TAIL.compareAndSet(this, t, p)) return; else continue restartFromTail; } else if (t != tail) continue restartFromTail; else p = q; } } } private void skipDeletedPredecessors(Node x) { whileActive: do { Node prev = x.prev; // assert prev != null; // assert x != NEXT_TERMINATOR; // assert x != PREV_TERMINATOR; Node p = prev; findActive: for (;;) { if (p.item != null) break findActive; Node q = p.prev; if (q == null) { if (p.next == p) continue whileActive; break findActive; } else if (p == q) continue whileActive; else p = q; } // found active CAS target if (prev == p || PREV.compareAndSet(x, prev, p)) return; } while (x.item != null || x.next == null); } private void skipDeletedSuccessors(Node x) { whileActive: do { Node next = x.next; // assert next != null; // assert x != NEXT_TERMINATOR; // assert x != PREV_TERMINATOR; Node p = next; findActive: for (;;) { if (p.item != null) break findActive; Node q = p.next; if (q == null) { if (p.prev == p) continue whileActive; break findActive; } else if (p == q) continue whileActive; else p = q; } // found active CAS target if (next == p || NEXT.compareAndSet(x, next, p)) return; } while (x.item != null || x.prev == null); } /** * Returns the successor of p, or the first node if p.next has been * linked to self, which will only be true if traversing with a * stale pointer that is now off the list. */ final Node succ(Node p) { // TODO: should we skip deleted nodes here? if (p == (p = p.next)) p = first(); return p; } /** * Returns the predecessor of p, or the last node if p.prev has been * linked to self, which will only be true if traversing with a * stale pointer that is now off the list. */ final Node pred(Node p) { Node q = p.prev; return (p == q) ? last() : q; } /** * Returns the first node, the unique node p for which: * p.prev == null && p.next != p * The returned node may or may not be logically deleted. * Guarantees that head is set to the returned node. */ Node first() { restartFromHead: for (;;) for (Node h = head, p = h, q;;) { if ((q = p.prev) != null && (q = (p = q).prev) != null) // Check for head updates every other hop. // If p == q, we are sure to follow head instead. p = (h != (h = head)) ? h : q; else if (p == h // It is possible that p is PREV_TERMINATOR, // but if so, the CAS is guaranteed to fail. || HEAD.compareAndSet(this, h, p)) return p; else continue restartFromHead; } } /** * Returns the last node, the unique node p for which: * p.next == null && p.prev != p * The returned node may or may not be logically deleted. * Guarantees that tail is set to the returned node. */ Node last() { restartFromTail: for (;;) for (Node t = tail, p = t, q;;) { if ((q = p.next) != null && (q = (p = q).next) != null) // Check for tail updates every other hop. // If p == q, we are sure to follow tail instead. p = (t != (t = tail)) ? t : q; else if (p == t // It is possible that p is NEXT_TERMINATOR, // but if so, the CAS is guaranteed to fail. || TAIL.compareAndSet(this, t, p)) return p; else continue restartFromTail; } } // Minor convenience utilities /** * Returns element unless it is null, in which case throws * NoSuchElementException. * * @param v the element * @return the element */ private E screenNullResult(E v) { if (v == null) throw new NoSuchElementException(); return v; } /** * Constructs an empty deque. */ public FastConcurrentDirectDeque() { head = tail = new Node(); } /** * Constructs a deque initially containing the elements of * the given collection, added in traversal order of the * collection's iterator. * * @param c the collection of elements to initially contain * @throws NullPointerException if the specified collection or any * of its elements are null */ public FastConcurrentDirectDeque(Collection c) { // Copy c into a private chain of Nodes Node h = null, t = null; for (E e : c) { Node newNode = newNode(Objects.requireNonNull(e)); if (h == null) h = t = newNode; else { NEXT.set(t, newNode); PREV.set(newNode, t); t = newNode; } } initHeadTail(h, t); } /** * Initializes head and tail, ensuring invariants hold. */ private void initHeadTail(Node h, Node t) { if (h == t) { if (h == null) h = t = new Node(); else { // Avoid edge case of a single Node with non-null item. Node newNode = new Node(); NEXT.set(t, newNode); PREV.set(newNode, t); t = newNode; } } head = h; tail = t; } /** * Inserts the specified element at the front of this deque. * As the deque is unbounded, this method will never throw * {@link IllegalStateException}. * * @throws NullPointerException if the specified element is null */ public void addFirst(E e) { linkFirst(e); } /** * Inserts the specified element at the end of this deque. * As the deque is unbounded, this method will never throw * {@link IllegalStateException}. * *

This method is equivalent to {@link #add}. * * @throws NullPointerException if the specified element is null */ public void addLast(E e) { linkLast(e); } /** * Inserts the specified element at the front of this deque. * As the deque is unbounded, this method will never return {@code false}. * * @return {@code true} (as specified by {@link Deque#offerFirst}) * @throws NullPointerException if the specified element is null */ public boolean offerFirst(E e) { linkFirst(e); return true; } public Object offerFirstAndReturnToken(E e) { return linkFirst(e); } public Object offerLastAndReturnToken(E e) { return linkLast(e); } public void removeToken(Object token) { if (!(token instanceof Node)) { throw new IllegalArgumentException(); } Node node = (Node) (token); while (! ITEM.compareAndSet(node, node.item, null)) {} unlink(node); } /** * Inserts the specified element at the end of this deque. * As the deque is unbounded, this method will never return {@code false}. * *

This method is equivalent to {@link #add}. * * @return {@code true} (as specified by {@link Deque#offerLast}) * @throws NullPointerException if the specified element is null */ public boolean offerLast(E e) { linkLast(e); return true; } public E peekFirst() { for (Node p = first(); p != null; p = succ(p)) { final E item; if ((item = p.item) != null) return item; } return null; } public E peekLast() { for (Node p = last(); p != null; p = pred(p)) { final E item; if ((item = p.item) != null) return item; } return null; } /** * @throws NoSuchElementException {@inheritDoc} */ public E getFirst() { return screenNullResult(peekFirst()); } /** * @throws NoSuchElementException {@inheritDoc} */ public E getLast() { return screenNullResult(peekLast()); } public E pollFirst() { for (Node p = first(); p != null; p = succ(p)) { final E item; if ((item = p.item) != null && ITEM.compareAndSet(p, item, null)) { unlink(p); return item; } } return null; } public E pollLast() { for (Node p = last(); p != null; p = pred(p)) { final E item; if ((item = p.item) != null && ITEM.compareAndSet(p, item, null)) { unlink(p); return item; } } return null; } /** * @throws NoSuchElementException {@inheritDoc} */ public E removeFirst() { return screenNullResult(pollFirst()); } /** * @throws NoSuchElementException {@inheritDoc} */ public E removeLast() { return screenNullResult(pollLast()); } // *** Queue and stack methods *** /** * Inserts the specified element at the tail of this deque. * As the deque is unbounded, this method will never return {@code false}. * * @return {@code true} (as specified by {@link Queue#offer}) * @throws NullPointerException if the specified element is null */ public boolean offer(E e) { return offerLast(e); } /** * Inserts the specified element at the tail of this deque. * As the deque is unbounded, this method will never throw * {@link IllegalStateException} or return {@code false}. * * @return {@code true} (as specified by {@link Collection#add}) * @throws NullPointerException if the specified element is null */ public boolean add(E e) { return offerLast(e); } public E poll() { return pollFirst(); } public E peek() { return peekFirst(); } /** * @throws NoSuchElementException {@inheritDoc} */ public E remove() { return removeFirst(); } /** * @throws NoSuchElementException {@inheritDoc} */ public E pop() { return removeFirst(); } /** * @throws NoSuchElementException {@inheritDoc} */ public E element() { return getFirst(); } /** * @throws NullPointerException {@inheritDoc} */ public void push(E e) { addFirst( e ); } /** * Removes the first occurrence of the specified element from this deque. * If the deque does not contain the element, it is unchanged. * More formally, removes the first element {@code e} such that * {@code o.equals(e)} (if such an element exists). * Returns {@code true} if this deque contained the specified element * (or equivalently, if this deque changed as a result of the call). * * @param o element to be removed from this deque, if present * @return {@code true} if the deque contained the specified element * @throws NullPointerException if the specified element is null */ public boolean removeFirstOccurrence(Object o) { Objects.requireNonNull(o); for (Node p = first(); p != null; p = succ(p)) { final E item; if ((item = p.item) != null && o.equals(item) && ITEM.compareAndSet(p, item, null)) { unlink(p); return true; } } return false; } /** * Removes the last occurrence of the specified element from this deque. * If the deque does not contain the element, it is unchanged. * More formally, removes the last element {@code e} such that * {@code o.equals(e)} (if such an element exists). * Returns {@code true} if this deque contained the specified element * (or equivalently, if this deque changed as a result of the call). * * @param o element to be removed from this deque, if present * @return {@code true} if the deque contained the specified element * @throws NullPointerException if the specified element is null */ public boolean removeLastOccurrence(Object o) { Objects.requireNonNull(o); for (Node p = last(); p != null; p = pred(p)) { final E item; if ((item = p.item) != null && o.equals(item) && ITEM.compareAndSet(p, item, null)) { unlink(p); return true; } } return false; } /** * Returns {@code true} if this deque contains the specified element. * More formally, returns {@code true} if and only if this deque contains * at least one element {@code e} such that {@code o.equals(e)}. * * @param o element whose presence in this deque is to be tested * @return {@code true} if this deque contains the specified element */ public boolean contains(Object o) { if (o != null) { for (Node p = first(); p != null; p = succ(p)) { final E item; if ((item = p.item) != null && o.equals(item)) return true; } } return false; } /** * Returns {@code true} if this collection contains no elements. * * @return {@code true} if this collection contains no elements */ public boolean isEmpty() { return peekFirst() == null; } /** * Returns the number of elements in this deque. If this deque * contains more than {@code Integer.MAX_VALUE} elements, it * returns {@code Integer.MAX_VALUE}. * *

Beware that, unlike in most collections, this method is * NOT a constant-time operation. Because of the * asynchronous nature of these deques, determining the current * number of elements requires traversing them all to count them. * Additionally, it is possible for the size to change during * execution of this method, in which case the returned result * will be inaccurate. Thus, this method is typically not very * useful in concurrent applications. * * @return the number of elements in this deque */ public int size() { restartFromHead: for (;;) { int count = 0; for (Node p = first(); p != null;) { if (p.item != null) if (++count == Integer.MAX_VALUE) break; // @see Collection.size() if (p == (p = p.next)) continue restartFromHead; } return count; } } /** * Removes the first occurrence of the specified element from this deque. * If the deque does not contain the element, it is unchanged. * More formally, removes the first element {@code e} such that * {@code o.equals(e)} (if such an element exists). * Returns {@code true} if this deque contained the specified element * (or equivalently, if this deque changed as a result of the call). * *

This method is equivalent to {@link #removeFirstOccurrence(Object)}. * * @param o element to be removed from this deque, if present * @return {@code true} if the deque contained the specified element * @throws NullPointerException if the specified element is null */ public boolean remove(Object o) { return removeFirstOccurrence(o); } /** * Appends all of the elements in the specified collection to the end of * this deque, in the order that they are returned by the specified * collection's iterator. Attempts to {@code addAll} of a deque to * itself result in {@code IllegalArgumentException}. * * @param c the elements to be inserted into this deque * @return {@code true} if this deque changed as a result of the call * @throws NullPointerException if the specified collection or any * of its elements are null * @throws IllegalArgumentException if the collection is this deque */ public boolean addAll(Collection c) { if (c == this) // As historically specified in AbstractQueue#addAll throw new IllegalArgumentException(); // Copy c into a private chain of Nodes Node beginningOfTheEnd = null, last = null; for (E e : c) { Node newNode = newNode(Objects.requireNonNull(e)); if (beginningOfTheEnd == null) beginningOfTheEnd = last = newNode; else { NEXT.set(last, newNode); PREV.set(newNode, last); last = newNode; } } if (beginningOfTheEnd == null) return false; // Atomically append the chain at the tail of this collection restartFromTail: for (;;) for (Node t = tail, p = t, q;;) { if ((q = p.next) != null && (q = (p = q).next) != null) // Check for tail updates every other hop. // If p == q, we are sure to follow tail instead. p = (t != (t = tail)) ? t : q; else if (p.prev == p) // NEXT_TERMINATOR continue restartFromTail; else { // p is last node PREV.set(beginningOfTheEnd, p); // CAS piggyback if (NEXT.compareAndSet(p, null, beginningOfTheEnd)) { // Successful CAS is the linearization point // for all elements to be added to this deque. if (!TAIL.weakCompareAndSet(this, t, last)) { // Try a little harder to update tail, // since we may be adding many elements. t = tail; if (last.next == null) TAIL.weakCompareAndSet(this, t, last); } return true; } // Lost CAS race to another thread; re-read next } } } /** * Removes all of the elements from this deque. */ public void clear() { while (pollFirst() != null) {} } public String toString() { String[] a = null; restartFromHead: for (;;) { int charLength = 0; int size = 0; for (Node p = first(); p != null;) { final E item; if ((item = p.item) != null) { if (a == null) a = new String[4]; else if (size == a.length) a = Arrays.copyOf(a, 2 * size); String s = item.toString(); a[size++] = s; charLength += s.length(); } if (p == (p = p.next)) continue restartFromHead; } if (size == 0) return "[]"; return toString(a, size, charLength); } } private Object[] toArrayInternal(Object[] a) { Object[] x = a; restartFromHead: for (;;) { int size = 0; for (Node p = first(); p != null;) { final E item; if ((item = p.item) != null) { if (x == null) x = new Object[4]; else if (size == x.length) x = Arrays.copyOf(x, 2 * (size + 4)); x[size++] = item; } if (p == (p = p.next)) continue restartFromHead; } if (x == null) return new Object[0]; else if (a != null && size <= a.length) { if (a != x) System.arraycopy(x, 0, a, 0, size); if (size < a.length) a[size] = null; return a; } return (size == x.length) ? x : Arrays.copyOf(x, size); } } /** * Returns an array containing all of the elements in this deque, in * proper sequence (from first to last element). * *

The returned array will be "safe" in that no references to it are * maintained by this deque. (In other words, this method must allocate * a new array). The caller is thus free to modify the returned array. * *

This method acts as bridge between array-based and collection-based * APIs. * * @return an array containing all of the elements in this deque */ public Object[] toArray() { return toArrayInternal(null); } /** * Returns an array containing all of the elements in this deque, * in proper sequence (from first to last element); the runtime * type of the returned array is that of the specified array. If * the deque fits in the specified array, it is returned therein. * Otherwise, a new array is allocated with the runtime type of * the specified array and the size of this deque. * *

If this deque fits in the specified array with room to spare * (i.e., the array has more elements than this deque), the element in * the array immediately following the end of the deque is set to * {@code null}. * *

Like the {@link #toArray()} method, this method acts as * bridge between array-based and collection-based APIs. Further, * this method allows precise control over the runtime type of the * output array, and may, under certain circumstances, be used to * save allocation costs. * *

Suppose {@code x} is a deque known to contain only strings. * The following code can be used to dump the deque into a newly * allocated array of {@code String}: * *

 {@code String[] y = x.toArray(new String[0]);}
* * Note that {@code toArray(new Object[0])} is identical in function to * {@code toArray()}. * * @param a the array into which the elements of the deque are to * be stored, if it is big enough; otherwise, a new array of the * same runtime type is allocated for this purpose * @return an array containing all of the elements in this deque * @throws ArrayStoreException if the runtime type of the specified array * is not a supertype of the runtime type of every element in * this deque * @throws NullPointerException if the specified array is null */ @SuppressWarnings("unchecked") public T[] toArray(T[] a) { if (a == null) throw new NullPointerException(); return (T[]) toArrayInternal(a); } /** * Returns an iterator over the elements in this deque in proper sequence. * The elements will be returned in order from first (head) to last (tail). * *

The returned iterator is * weakly consistent. * * @return an iterator over the elements in this deque in proper sequence */ public Iterator iterator() { return new Itr(); } /** * Returns an iterator over the elements in this deque in reverse * sequential order. The elements will be returned in order from * last (tail) to first (head). * *

The returned iterator is * weakly consistent. * * @return an iterator over the elements in this deque in reverse order */ public Iterator descendingIterator() { return new DescendingItr(); } // From Helpers 1.2 // http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/main/java/util/concurrent/Helpers.java?revision=1.2 /** * Like Arrays.toString(), but caller guarantees that size > 0, * each element with index 0 <= i < size is a non-null String, * and charLength is the sum of the lengths of the input Strings. */ private String toString(Object[] a, int size, int charLength) { // assert a != null; // assert size > 0; // Copy each string into a perfectly sized char[] // Length of [ , , , ] == 2 * size final char[] chars = new char[charLength + 2 * size]; chars[0] = '['; int j = 1; for (int i = 0; i < size; i++) { if (i > 0) { chars[j++] = ','; chars[j++] = ' '; } String s = (String) a[i]; int len = s.length(); s.getChars(0, len, chars, j); j += len; } chars[j] = ']'; // assert j == chars.length - 1; return new String(chars); } private abstract class AbstractItr implements Iterator { /** * Next node to return item for. */ private Node nextNode; /** * nextItem holds on to item fields because once we claim * that an element exists in hasNext(), we must return it in * the following next() call even if it was in the process of * being removed when hasNext() was called. */ private E nextItem; /** * Node returned by most recent call to next. Needed by remove. * Reset to null if this element is deleted by a call to remove. */ private Node lastRet; abstract Node startNode(); abstract Node nextNode(Node p); AbstractItr() { advance(); } /** * Sets nextNode and nextItem to next valid node, or to null * if no such. */ private void advance() { lastRet = nextNode; Node p = (nextNode == null) ? startNode() : nextNode(nextNode); for (;; p = nextNode(p)) { if (p == null) { // might be at active end or TERMINATOR node; both are OK nextNode = null; nextItem = null; break; } final E item; if ((item = p.item) != null) { nextNode = p; nextItem = item; break; } } } public boolean hasNext() { return nextItem != null; } public E next() { E item = nextItem; if (item == null) throw new NoSuchElementException(); advance(); return item; } public void remove() { Node l = lastRet; if (l == null) throw new IllegalStateException(); l.item = null; unlink(l); lastRet = null; } } /** * Forward iterator */ private class Itr extends AbstractItr { Itr() {} // prevent access constructor creation Node startNode() { return first(); } Node nextNode(Node p) { return succ( p ); } } /** * Descending iterator */ private class DescendingItr extends AbstractItr { DescendingItr() {} // prevent access constructor creation Node startNode() { return last(); } Node nextNode(Node p) { return pred( p ); } } /** A customized variant of Spliterators.IteratorSpliterator */ final class CLDSpliterator implements Spliterator { static final int MAX_BATCH = 1 << 25; // max batch array size; Node current; // current node; null until initialized int batch; // batch size for splits boolean exhausted; // true when no more nodes public Spliterator trySplit() { Node p, q; if ((p = current()) == null || (q = p.next) == null) return null; int i = 0, n = batch = Math.min(batch + 1, MAX_BATCH); Object[] a = null; do { final E e; if ((e = p.item) != null) { if (a == null) a = new Object[n]; a[i++] = e; } if (p == (p = q)) p = first(); } while (p != null && (q = p.next) != null && i < n); setCurrent(p); return (i == 0) ? null : Spliterators.spliterator(a, 0, i, (Spliterator.ORDERED | Spliterator.NONNULL | Spliterator.CONCURRENT)); } public void forEachRemaining(Consumer action) { Objects.requireNonNull(action); Node p; if ((p = current()) != null) { current = null; exhausted = true; do { final E e; if ((e = p.item) != null) action.accept(e); if (p == (p = p.next)) p = first(); } while (p != null); } } public boolean tryAdvance(Consumer action) { Objects.requireNonNull(action); Node p; if ((p = current()) != null) { E e; do { e = p.item; if (p == (p = p.next)) p = first(); } while (e == null && p != null); setCurrent(p); if (e != null) { action.accept(e); return true; } } return false; } private void setCurrent(Node p) { if ((current = p) == null) exhausted = true; } private Node current() { Node p; if ((p = current) == null && !exhausted) setCurrent(p = first()); return p; } public long estimateSize() { return Long.MAX_VALUE; } public int characteristics() { return (Spliterator.ORDERED | Spliterator.NONNULL | Spliterator.CONCURRENT); } } /** * Returns a {@link Spliterator} over the elements in this deque. * *

The returned spliterator is * weakly consistent. * *

The {@code Spliterator} reports {@link Spliterator#CONCURRENT}, * {@link Spliterator#ORDERED}, and {@link Spliterator#NONNULL}. * * @implNote * The {@code Spliterator} implements {@code trySplit} to permit limited * parallelism. * * @return a {@code Spliterator} over the elements in this deque * @since 1.8 */ public Spliterator spliterator() { return new CLDSpliterator(); } /** * Saves this deque to a stream (that is, serializes it). * * @param s the stream * @throws java.io.IOException if an I/O error occurs * @serialData All of the elements (each an {@code E}) in * the proper order, followed by a null */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { // Write out any hidden stuff s.defaultWriteObject(); // Write out all elements in the proper order. for (Node p = first(); p != null; p = succ(p)) { final E item; if ((item = p.item) != null) s.writeObject(item); } // Use trailing null as sentinel s.writeObject(null); } /** * Reconstitutes this deque from a stream (that is, deserializes it). * @param s the stream * @throws ClassNotFoundException if the class of a serialized object * could not be found * @throws java.io.IOException if an I/O error occurs */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); // Read in elements until trailing null sentinel found Node h = null, t = null; for (Object item; (item = s.readObject()) != null; ) { @SuppressWarnings("unchecked") Node newNode = newNode((E) item); if (h == null) h = t = newNode; else { NEXT.set(t, newNode); PREV.set(newNode, t); t = newNode; } } initHeadTail(h, t); } /** * @throws NullPointerException {@inheritDoc} */ public boolean removeIf(Predicate filter) { Objects.requireNonNull(filter); return bulkRemove(filter); } /** * @throws NullPointerException {@inheritDoc} */ public boolean removeAll(Collection c) { Objects.requireNonNull(c); return bulkRemove(e -> c.contains(e)); } /** * @throws NullPointerException {@inheritDoc} */ public boolean retainAll(Collection c) { Objects.requireNonNull(c); return bulkRemove(e -> !c.contains(e)); } /** Implementation of bulk remove methods. */ private boolean bulkRemove(Predicate filter) { boolean removed = false; for (Node p = first(), succ; p != null; p = succ) { succ = succ(p); final E item; if ((item = p.item) != null && filter.test(item) && ITEM.compareAndSet(p, item, null)) { unlink(p); removed = true; } } return removed; } /** * @throws NullPointerException {@inheritDoc} */ public void forEach(Consumer action) { Objects.requireNonNull(action); E item; for (Node p = first(); p != null; p = succ(p)) if ((item = p.item) != null) action.accept(item); } // VarHandle mechanics private static final VarHandle HEAD; private static final VarHandle TAIL; private static final VarHandle PREV; private static final VarHandle NEXT; private static final VarHandle ITEM; static { PREV_TERMINATOR = new Node(); PREV_TERMINATOR.next = PREV_TERMINATOR; NEXT_TERMINATOR = new Node(); NEXT_TERMINATOR.prev = NEXT_TERMINATOR; try { MethodHandles.Lookup l = MethodHandles.lookup(); HEAD = l.findVarHandle(FastConcurrentDirectDeque.class, "head", Node.class); TAIL = l.findVarHandle(FastConcurrentDirectDeque.class, "tail", Node.class); PREV = l.findVarHandle(Node.class, "prev", Node.class); NEXT = l.findVarHandle(Node.class, "next", Node.class); ITEM = l.findVarHandle(Node.class, "item", Object.class); } catch (ReflectiveOperationException e) { throw new Error(e); } } } undertow-2.2.16.Final/core/src/main/resources/000077500000000000000000000000001420065311100211475ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/resources/META-INF/000077500000000000000000000000001420065311100223075ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/resources/META-INF/services/000077500000000000000000000000001420065311100241325ustar00rootroot00000000000000io.undertow.attribute.ExchangeAttributeBuilder000066400000000000000000000040431420065311100351720ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/resources/META-INF/servicesio.undertow.attribute.RelativePathAttribute$Builder io.undertow.attribute.RemoteIPAttribute$Builder io.undertow.attribute.LocalIPAttribute$Builder io.undertow.attribute.RequestProtocolAttribute$Builder io.undertow.attribute.LocalPortAttribute$Builder io.undertow.attribute.IdentUsernameAttribute$Builder io.undertow.attribute.RequestMethodAttribute$Builder io.undertow.attribute.QueryStringAttribute$Builder io.undertow.attribute.RequestLineAttribute$Builder io.undertow.attribute.BytesSentAttribute$Builder io.undertow.attribute.DateTimeAttribute$Builder io.undertow.attribute.RemoteUserAttribute$Builder io.undertow.attribute.RequestURLAttribute$Builder io.undertow.attribute.ThreadNameAttribute$Builder io.undertow.attribute.LocalServerNameAttribute$Builder io.undertow.attribute.RequestHeaderAttribute$Builder io.undertow.attribute.ResponseHeaderAttribute$Builder io.undertow.attribute.CookieAttribute$Builder io.undertow.attribute.RequestCookieAttribute$Builder io.undertow.attribute.ResponseCookieAttribute$Builder io.undertow.attribute.ResponseCodeAttribute$Builder io.undertow.attribute.PredicateContextAttribute$Builder io.undertow.attribute.QueryParameterAttribute$Builder io.undertow.attribute.SslClientCertAttribute$Builder io.undertow.attribute.SslCipherAttribute$Builder io.undertow.attribute.SslSessionIdAttribute$Builder io.undertow.attribute.ResponseTimeAttribute$Builder io.undertow.attribute.PathParameterAttribute$Builder io.undertow.attribute.TransportProtocolAttribute$Builder io.undertow.attribute.RequestSchemeAttribute$Builder io.undertow.attribute.HostAndPortAttribute$Builder io.undertow.attribute.AuthenticationTypeExchangeAttribute$Builder io.undertow.attribute.SecureExchangeAttribute$Builder io.undertow.attribute.RemoteHostAttribute$Builder io.undertow.attribute.RequestPathAttribute$Builder io.undertow.attribute.ResolvedPathAttribute$Builder io.undertow.attribute.NullAttribute$Builder io.undertow.attribute.StoredResponse$Builder io.undertow.attribute.ResponseReasonPhraseAttribute$Builder io.undertow.attribute.RemoteObfuscatedIPAttribute$Builder undertow-2.2.16.Final/core/src/main/resources/META-INF/services/io.undertow.client.ClientProvider000066400000000000000000000003561420065311100325430ustar00rootroot00000000000000io.undertow.client.http.HttpClientProvider io.undertow.client.ajp.AjpClientProvider io.undertow.client.http2.Http2ClientProvider io.undertow.client.http2.Http2ClearClientProvider io.undertow.client.http2.Http2PriorKnowledgeClientProvider io.undertow.predicate.PredicateBuilder000066400000000000000000000014701420065311100334220ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/resources/META-INF/servicesio.undertow.predicate.PathMatchPredicate$Builder io.undertow.predicate.PathPrefixPredicate$Builder io.undertow.predicate.ContainsPredicate$Builder io.undertow.predicate.ExistsPredicate$Builder io.undertow.predicate.RegularExpressionPredicate$Builder io.undertow.predicate.PathSuffixPredicate$Builder io.undertow.predicate.EqualsPredicate$Builder io.undertow.predicate.PathTemplatePredicate$Builder io.undertow.predicate.MethodPredicate$Builder io.undertow.predicate.AuthenticationRequiredPredicate$Builder io.undertow.predicate.MaxContentSizePredicate$Builder io.undertow.predicate.MinContentSizePredicate$Builder io.undertow.predicate.SecurePredicate$Builder io.undertow.predicate.IdempotentPredicate$Builder io.undertow.predicate.RequestLargerThanPredicate$Builder io.undertow.predicate.RequestSmallerThanPredicate$Builderio.undertow.protocols.alpn.ALPNEngineManager000066400000000000000000000014151420065311100344020ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/resources/META-INF/services# # JBoss, Home of Professional Open Source. # Copyright 2018 Red Hat, Inc., and individual contributors # as indicated by the @author tags. # 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. # io.undertow.protocols.ssl.SNIAlpnEngineManager io.undertow.protocols.alpn.DefaultAlpnEngineManagerio.undertow.protocols.alpn.ALPNProvider000066400000000000000000000015571420065311100335030ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/resources/META-INF/services# # JBoss, Home of Professional Open Source. # Copyright 2014 Red Hat, Inc., and individual contributors # as indicated by the @author tags. # # 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. # io.undertow.protocols.alpn.JDK8HackAlpnProvider io.undertow.protocols.alpn.JettyAlpnProvider io.undertow.protocols.alpn.JDK9AlpnProvider io.undertow.protocols.alpn.OpenSSLAlpnProvider io.undertow.server.handlers.builder.HandlerBuilder000066400000000000000000000050011420065311100356630ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/resources/META-INF/servicesio.undertow.server.handlers.builder.RewriteHandlerBuilder io.undertow.server.handlers.SetAttributeHandler$Builder io.undertow.server.handlers.SetAttributeHandler$ClearBuilder io.undertow.server.handlers.builder.ResponseCodeHandlerBuilder io.undertow.server.handlers.DisableCacheHandler$Builder io.undertow.server.handlers.ProxyPeerAddressHandler$Builder io.undertow.server.handlers.proxy.ProxyHandlerBuilder io.undertow.server.handlers.RedirectHandler$Builder io.undertow.server.handlers.accesslog.AccessLogHandler$Builder io.undertow.server.handlers.AllowedMethodsHandler$Builder io.undertow.server.handlers.BlockingHandler$Builder io.undertow.server.handlers.CanonicalPathHandler$Builder io.undertow.server.handlers.DisallowedMethodsHandler$Builder io.undertow.server.handlers.error.FileErrorPageHandler$Builder io.undertow.server.handlers.HttpTraceHandler$Builder io.undertow.server.JvmRouteHandler$Builder io.undertow.server.handlers.PeerNameResolvingHandler$Builder io.undertow.server.handlers.RequestDumpingHandler$Builder io.undertow.server.handlers.RequestLimitingHandler$Builder io.undertow.server.handlers.resource.ResourceHandler$Builder io.undertow.server.handlers.SSLHeaderHandler$Builder io.undertow.server.handlers.ResponseRateLimitingHandler$Builder io.undertow.server.handlers.URLDecodingHandler$Builder io.undertow.server.handlers.PathSeparatorHandler$Builder io.undertow.server.handlers.IPAddressAccessControlHandler$Builder io.undertow.server.handlers.ByteRangeHandler$Builder io.undertow.server.handlers.encoding.EncodingHandler$Builder io.undertow.server.handlers.encoding.RequestEncodingHandler$Builder io.undertow.server.handlers.LearningPushHandler$Builder io.undertow.server.handlers.SetHeaderHandler$Builder io.undertow.predicate.PredicatesHandler$DoneHandlerBuilder io.undertow.predicate.PredicatesHandler$RestartHandlerBuilder io.undertow.server.handlers.RequestBufferingHandler$Builder io.undertow.server.handlers.StuckThreadDetectionHandler$Builder io.undertow.server.handlers.AccessControlListHandler$Builder io.undertow.server.handlers.JDBCLogHandler$Builder io.undertow.server.handlers.LocalNameResolvingHandler$Builder io.undertow.server.handlers.StoredResponseHandler$Builder io.undertow.server.handlers.SecureCookieHandler$Builder io.undertow.server.handlers.ForwardedHandler$Builder io.undertow.server.handlers.HttpContinueAcceptingHandler$Builder io.undertow.server.handlers.form.EagerFormParsingHandler$Builder io.undertow.server.handlers.SameSiteCookieHandler$Builder io.undertow.server.handlers.SetErrorHandler$Builder undertow-2.2.16.Final/core/src/main/resources/io/000077500000000000000000000000001420065311100215565ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/resources/io/undertow/000077500000000000000000000000001420065311100234255ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/main/resources/io/undertow/version.properties000066400000000000000000000000431420065311100272250ustar00rootroot00000000000000undertow.version=${project.version}undertow-2.2.16.Final/core/src/test/000077500000000000000000000000001420065311100171705ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/000077500000000000000000000000001420065311100201115ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/000077500000000000000000000000001420065311100205205ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/000077500000000000000000000000001420065311100223675ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/client/000077500000000000000000000000001420065311100236455ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/client/http/000077500000000000000000000000001420065311100246245ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/client/http/AjpClientTestCase.java000066400000000000000000000361411420065311100310010ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client.http; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import io.undertow.Undertow; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientExchange; import io.undertow.client.ClientRequest; import io.undertow.client.ClientResponse; import io.undertow.client.UndertowClient; import io.undertow.io.Sender; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.PathHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.util.AttachmentKey; import io.undertow.util.Headers; import io.undertow.util.Methods; import io.undertow.util.StatusCodes; import io.undertow.util.StringReadChannelListener; import io.undertow.util.StringWriteChannelListener; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.ChannelListeners; import org.xnio.FutureResult; import org.xnio.IoUtils; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.Xnio; import org.xnio.XnioWorker; import org.xnio.channels.StreamSinkChannel; import static io.undertow.testutils.StopServerWithExternalWorkerUtils.stopWorker; import static org.junit.Assert.assertTrue; /** * @author Emanuel Muckenhuber */ @RunWith(DefaultServer.class) @HttpOneOnly public class AjpClientTestCase { private static final String message = "Hello World!"; public static final String MESSAGE = "/message"; public static final String POST = "/post"; private static final int AJP_PORT = DefaultServer.getHostPort() + 10; private static XnioWorker worker; private static Undertow undertow; private static final OptionMap DEFAULT_OPTIONS; private static final URI ADDRESS; private static final AttachmentKey RESPONSE_BODY = AttachmentKey.create(String.class); static { final OptionMap.Builder builder = OptionMap.builder() .set(Options.WORKER_IO_THREADS, 8) .set(Options.TCP_NODELAY, true) .set(Options.KEEP_ALIVE, true) .set(Options.WORKER_NAME, "Client"); DEFAULT_OPTIONS = builder.getMap(); try { ADDRESS = new URI("ajp://" + DefaultServer.getHostAddress() + ":" + AJP_PORT); } catch (URISyntaxException e) { throw new RuntimeException(e); } } static void sendMessage(final HttpServerExchange exchange) { exchange.setStatusCode(StatusCodes.OK); exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, message.length() + ""); final Sender sender = exchange.getResponseSender(); sender.send(message); } @BeforeClass public static void beforeClass() throws IOException { // Create xnio worker worker = Xnio.getInstance().createWorker(null, DEFAULT_OPTIONS); undertow = Undertow.builder().addListener(new Undertow.ListenerBuilder().setType(Undertow.ListenerType.AJP).setPort(AJP_PORT)) .setHandler(new PathHandler() .addExactPath(MESSAGE, AjpClientTestCase::sendMessage) .addExactPath(POST, exchange -> exchange.getRequestReceiver().receiveFullString( (exchange1, message) -> exchange1.getResponseSender().send(message)))) .build(); undertow.start(); } @AfterClass public static void afterClass() throws InterruptedException { undertow.stop(); stopWorker(worker); // sleep 1 s to prevent BindException (Address already in use) when running the CI try { Thread.sleep(1000); } catch (InterruptedException ignore) {} } static UndertowClient createClient() { return UndertowClient.getInstance(); } @Test public void testSimpleBasic() throws Exception { // final UndertowClient client = createClient(); final List responses = new CopyOnWriteArrayList<>(); final CountDownLatch latch = new CountDownLatch(10); final ClientConnection connection = client.connect(ADDRESS, worker, DefaultServer.getBufferPool(), OptionMap.EMPTY).get(); try { connection.getIoThread().execute(() -> { for (int i = 0; i < 10; i++) { final ClientRequest request = new ClientRequest().setMethod(Methods.GET).setPath(MESSAGE); request.getRequestHeaders().put(Headers.HOST, DefaultServer.getHostAddress()); connection.sendRequest(request, createClientCallback(responses, latch)); } }); assertTrue(latch.await(10, TimeUnit.SECONDS)); Assert.assertEquals(10, responses.size()); for (final ClientResponse response : responses) { Assert.assertEquals(message, response.getAttachment(RESPONSE_BODY)); } } finally { IoUtils.safeClose(connection); } } @Test public void testSendPing() throws Exception { // final UndertowClient client = createClient(); final List responses = new CopyOnWriteArrayList<>(); final FutureResult result = new FutureResult<>(); final CountDownLatch latch = new CountDownLatch(3); final ClientConnection connection = client.connect(ADDRESS, worker, DefaultServer.getBufferPool(), OptionMap.EMPTY).get(); assertTrue(connection.isPingSupported()); try { connection.getIoThread().execute(() -> { final ClientRequest request = new ClientRequest().setMethod(Methods.GET).setPath(MESSAGE); request.getRequestHeaders().put(Headers.HOST, DefaultServer.getHostAddress()); connection.sendRequest(request, createClientCallback(responses, latch)); connection.sendPing(new ClientConnection.PingListener() { @Override public void acknowledged() { result.setResult(true); latch.countDown(); } @Override public void failed(IOException e) { result.setException(e); latch.countDown(); } }, 5, TimeUnit.SECONDS); connection.sendRequest(request, createClientCallback(responses, latch)); }); assertTrue(latch.await(10, TimeUnit.SECONDS)); Assert.assertEquals(2, responses.size()); assertTrue(result.getIoFuture().get()); for (final ClientResponse response : responses) { Assert.assertEquals(message, response.getAttachment(RESPONSE_BODY)); } //now try a failed ping try { undertow.stop(); final FutureResult failResult = new FutureResult<>(); connection.getIoThread().execute(() -> connection.sendPing(new ClientConnection.PingListener() { @Override public void acknowledged() { failResult.setResult(true); } @Override public void failed(IOException e) { failResult.setException(e); } }, 4, TimeUnit.SECONDS)); try { failResult.getIoFuture().get(); Assert.fail("ping should have failed"); } catch (IOException e) { //ignored } } finally { // add an extra sleep time to make sure we are not getting a BindException try { Thread.sleep(1000); } catch (InterruptedException e) { // ignore } undertow.start(); } } finally { IoUtils.safeClose(connection); } } @Test public void testPostRequest() throws Exception { // final UndertowClient client = createClient(); final String postMessage = "This is a post request"; final List responses = new CopyOnWriteArrayList<>(); final CountDownLatch latch = new CountDownLatch(10); final ClientConnection connection = client.connect(ADDRESS, worker, DefaultServer.getBufferPool(), OptionMap.EMPTY).get(); try { connection.getIoThread().execute(() -> { for (int i = 0; i < 10; i++) { final ClientRequest request = new ClientRequest().setMethod(Methods.POST).setPath(POST); request.getRequestHeaders().put(Headers.HOST, DefaultServer.getHostAddress()); request.getRequestHeaders().put(Headers.TRANSFER_ENCODING, "chunked"); connection.sendRequest(request, new ClientCallback() { @Override public void completed(ClientExchange result) { new StringWriteChannelListener(postMessage).setup(result.getRequestChannel()); result.setResponseListener(new ClientCallback() { @Override public void completed(ClientExchange result) { new StringReadChannelListener(DefaultServer.getBufferPool()) { @Override protected void stringDone(String string) { responses.add(string); latch.countDown(); } @Override protected void error(IOException e) { e.printStackTrace(); latch.countDown(); } }.setup(result.getResponseChannel()); } @Override public void failed(IOException e) { e.printStackTrace(); latch.countDown(); } }); } @Override public void failed(IOException e) { e.printStackTrace(); latch.countDown(); } }); } }); assertTrue(latch.await(10, TimeUnit.SECONDS)); Assert.assertEquals(10, responses.size()); for (final String response : responses) { Assert.assertEquals(postMessage, response); } } finally { IoUtils.safeClose(connection); } } @Test public void testConnectionClose() throws Exception { // final UndertowClient client = createClient(); final CountDownLatch latch = new CountDownLatch(1); final ClientConnection connection = client.connect(ADDRESS, worker, DefaultServer.getBufferPool(), OptionMap.EMPTY).get(); try { ClientRequest request = new ClientRequest().setPath(MESSAGE).setMethod(Methods.GET); request.getRequestHeaders().put(Headers.HOST, DefaultServer.getHostAddress()); final List responses = new CopyOnWriteArrayList<>(); request.getRequestHeaders().add(Headers.CONNECTION, Headers.CLOSE.toString()); connection.sendRequest(request, createClientCallback(responses, latch)); latch.await(); final ClientResponse response = responses.iterator().next(); Assert.assertEquals(message, response.getAttachment(RESPONSE_BODY)); Assert.assertFalse(connection.isOpen()); } finally { IoUtils.safeClose(connection); } } private ClientCallback createClientCallback(final List responses, final CountDownLatch latch) { return new ClientCallback() { @Override public void completed(ClientExchange result) { result.setResponseListener(new ClientCallback() { @Override public void completed(final ClientExchange result) { responses.add(result.getResponse()); new StringReadChannelListener(result.getConnection().getBufferPool()) { @Override protected void stringDone(String string) { result.getResponse().putAttachment(RESPONSE_BODY, string); latch.countDown(); } @Override protected void error(IOException e) { e.printStackTrace(); latch.countDown(); } }.setup(result.getResponseChannel()); } @Override public void failed(IOException e) { e.printStackTrace(); latch.countDown(); } }); try { result.getRequestChannel().shutdownWrites(); if(!result.getRequestChannel().flush()) { result.getRequestChannel().getWriteSetter().set(ChannelListeners.flushingChannelListener(null, null)); result.getRequestChannel().resumeWrites(); } } catch (IOException e) { e.printStackTrace(); latch.countDown(); } } @Override public void failed(IOException e) { e.printStackTrace(); latch.countDown(); } }; } } undertow-2.2.16.Final/core/src/test/java/io/undertow/client/http/H2CUpgradeContinuationTestCase.java000066400000000000000000000320301420065311100334000ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client.http; import io.undertow.Undertow; import io.undertow.UndertowOptions; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientExchange; import io.undertow.client.ClientRequest; import io.undertow.client.ClientResponse; import io.undertow.client.UndertowClient; import io.undertow.connector.ByteBufferPool; import io.undertow.io.Receiver; import io.undertow.io.Sender; import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.server.DefaultByteBufferPool; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.PathHandler; import io.undertow.server.protocol.http2.Http2UpgradeHandler; import io.undertow.testutils.DebuggingSlicePool; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.util.AttachmentKey; import io.undertow.util.HeaderValues; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.Methods; import io.undertow.util.StatusCodes; import io.undertow.util.StringReadChannelListener; import io.undertow.util.StringWriteChannelListener; import java.io.IOException; import java.net.URI; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.Xnio; import org.xnio.XnioWorker; import org.xnio.channels.StreamSinkChannel; /** *

Test that uses H2C upgrade and tries to send different number of headers * for a GET/POST request. The idea is that the byte buffer used in the client * and server is small. That way when sending a big number of headers the * HEADERS frame is not enough to contain all the data and some CONTINUATION * frames are needed. The test method also tries with different sizes of DATA * to force several DATA frames to be sent when using POST method.

* * @author rmartinc */ @RunWith(DefaultServer.class) @HttpOneOnly public class H2CUpgradeContinuationTestCase { private static final String HEADER_PREFFIX = "custom-header-"; private static final String ECHO_PATH = "/echo"; private static final AttachmentKey RESPONSE_BODY = AttachmentKey.create(String.class); private static ByteBufferPool smallPool; private static XnioWorker worker; private static Undertow server; /** * Just a handler that receives the request and sends back all the custom * headers received and (if data received) returns the same data (empty * response otherwise). * @param exchange The HttpServerExchange */ private static void sendEchoResponse(final HttpServerExchange exchange) { exchange.setStatusCode(StatusCodes.OK); // add the custom headers received for (HeaderValues header : exchange.getRequestHeaders()) { if (header.getFirst().startsWith(HEADER_PREFFIX)) { exchange.getResponseHeaders().putAll(header.getHeaderName(), header.subList(0, header.size())); } } // response using echo or empty string if (exchange.getRequestContentLength() > 0) { exchange.getRequestReceiver().receiveFullString(new Receiver.FullStringCallback() { @Override public void handle(HttpServerExchange exchange, String message) { exchange.getResponseSender().send(message); } }); } else { final Sender sender = exchange.getResponseSender(); sender.send(""); } } /** * Initializes the server with the H2C handler and adds the echo handler to * manage the requests. * @throws IOException Some error */ @BeforeClass public static void beforeClass() throws IOException { // server and client pool is using 1024 for the buffer size smallPool = new DebuggingSlicePool(new DefaultByteBufferPool(true, 1024, 1000, 10, 100)); final PathHandler path = new PathHandler() .addExactPath(ECHO_PATH, new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { sendEchoResponse(exchange); } }); server = Undertow.builder() .setByteBufferPool(smallPool) .addHttpListener(DefaultServer.getHostPort("default") + 1, DefaultServer.getHostAddress("default"), new Http2UpgradeHandler(path)) .setSocketOption(Options.REUSE_ADDRESSES, true) .build(); server.start(); // Create xnio worker final Xnio xnio = Xnio.getInstance(); final XnioWorker xnioWorker = xnio.createWorker(null, OptionMap.builder() .set(Options.WORKER_IO_THREADS, 8) .set(Options.TCP_NODELAY, true) .set(Options.KEEP_ALIVE, true) .getMap()); worker = xnioWorker; } /** * Stops server and worker. */ @AfterClass public static void afterClass() { if (server != null) { server.stop(); } if (worker != null) { worker.shutdown(); } if (smallPool != null) { smallPool.close(); smallPool = null; } } /** * Method that sends a GET or POST request adding count number of custom * headers and sending contentLength data. GET is used if no content length * is passed, POST if contentLength is greater than 0. * @param connection The connection to use * @param requestCount The number of requests to send * @param headersCount The number of custom headers to send * @param contentLength The content length to send (POST method used if >0) * @throws Exception Some error */ private void sendRequest(ClientConnection connection, int requestCount, int headersCount, int contentLength) throws Exception { final CountDownLatch latch = new CountDownLatch(requestCount); final List responses = new CopyOnWriteArrayList<>(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < contentLength; i++) { sb.append(i % 10); } final String content = sb.length() > 0? sb.toString() : null; connection.getIoThread().execute(new Runnable() { @Override public void run() { for (int i = 0; i < requestCount; i++) { final ClientRequest request = new ClientRequest() .setMethod(contentLength > 0 ? Methods.POST : Methods.GET) .setPath(ECHO_PATH); request.getRequestHeaders().put(Headers.HOST, DefaultServer.getHostAddress()); if (contentLength > 0) { request.getRequestHeaders().put(Headers.CONTENT_LENGTH, contentLength); } for (int j = 0; j < headersCount; j++) { request.getRequestHeaders().put(new HttpString(HEADER_PREFFIX + j), HEADER_PREFFIX + j); } connection.sendRequest(request, createClientCallback(responses, latch, content)); } } }); latch.await(10, TimeUnit.SECONDS); Assert.assertEquals("No responses received from server in 10s", requestCount, responses.size()); for (int i = 0; i < requestCount; i++) { Assert.assertEquals("Response " + i + " code was not OK", StatusCodes.OK, responses.get(i).getResponseCode()); Assert.assertEquals("Incorrect data received for response " + i, contentLength > 0 ? content : "", responses.get(i).getAttachment(RESPONSE_BODY)); int headersReturned = 0; for (HeaderValues header : responses.get(i).getResponseHeaders()) { if (header.getFirst().startsWith(HEADER_PREFFIX)) { headersReturned += header.size(); } } Assert.assertEquals("Incorrect number of headers returned for response " + i, headersCount, headersReturned); } } /** * The real test that sends several GET and POST requests with different * number of headers and different content length. * @throws Exception Some error */ @Test public void testDifferentSizes() throws Exception { final UndertowClient client = UndertowClient.getInstance(); // the client connection uses the small byte-buffer of 1024 to force the continuation frames final ClientConnection connection = client.connect( new URI("http://" + DefaultServer.getHostAddress() + ":" + (DefaultServer.getHostPort("default") + 1)), worker, new UndertowXnioSsl(worker.getXnio(), OptionMap.EMPTY, DefaultServer.getClientSSLContext()), smallPool, OptionMap.create(UndertowOptions.ENABLE_HTTP2, true)).get(); try { // the first request triggers the upgrade to H2C sendRequest(connection, 1, 0, 0); // send several requests with different sizes for headers and data sendRequest(connection, 10, 10, 0); sendRequest(connection, 10, 100, 0); sendRequest(connection, 10, 150, 0); sendRequest(connection, 10, 1, 10); sendRequest(connection, 10, 0, 2000); sendRequest(connection, 10, 150, 2000); } finally { IoUtils.safeClose(connection); } } /** * Create the callback to receive the response and assign it to the list. * @param responses The list where the response will be added * @param latch The latch to count down when the response is received * @param message The message to send if it's a POST message (if null nothing is send) * @return The created callback */ private static ClientCallback createClientCallback(final List responses, final CountDownLatch latch, String message) { return new ClientCallback() { @Override public void completed(ClientExchange result) { if (message != null) { new StringWriteChannelListener(message).setup(result.getRequestChannel()); } result.setResponseListener(new ClientCallback() { @Override public void completed(final ClientExchange result) { responses.add(result.getResponse()); new StringReadChannelListener(result.getConnection().getBufferPool()) { @Override protected void stringDone(String string) { result.getResponse().putAttachment(RESPONSE_BODY, string); latch.countDown(); } @Override protected void error(IOException e) { e.printStackTrace(); latch.countDown(); } }.setup(result.getResponseChannel()); } @Override public void failed(IOException e) { e.printStackTrace(); latch.countDown(); } }); try { result.getRequestChannel().shutdownWrites(); if (!result.getRequestChannel().flush()) { result.getRequestChannel().getWriteSetter().set(ChannelListeners.flushingChannelListener(null, null)); result.getRequestChannel().resumeWrites(); } } catch (IOException e) { e.printStackTrace(); latch.countDown(); } } @Override public void failed(IOException e) { e.printStackTrace(); latch.countDown(); } }; } } undertow-2.2.16.Final/core/src/test/java/io/undertow/client/http/H2CUpgradeResetTestCase.java000066400000000000000000000246671420065311100320310ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client.http; import io.undertow.Undertow; import io.undertow.UndertowOptions; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientExchange; import io.undertow.client.ClientRequest; import io.undertow.client.ClientResponse; import io.undertow.client.UndertowClient; import io.undertow.io.Receiver; import io.undertow.io.Sender; import io.undertow.protocols.http2.Http2Channel; import io.undertow.protocols.http2.Http2StreamSourceChannel; import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.PathHandler; import io.undertow.server.protocol.http2.Http2UpgradeHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.util.AttachmentKey; import io.undertow.util.Headers; import io.undertow.util.Methods; import io.undertow.util.StatusCodes; import io.undertow.util.StringReadChannelListener; import io.undertow.util.StringWriteChannelListener; import java.io.IOException; import java.net.URI; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.IoUtils; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.Xnio; import org.xnio.XnioWorker; /** *

Test for H2C that can send a RST to the just received request.

* * @author rmartinc */ @RunWith(DefaultServer.class) @HttpOneOnly public class H2CUpgradeResetTestCase { private static final String ECHO_PATH = "/echo"; private static final AttachmentKey RESPONSE_BODY = AttachmentKey.create(String.class); private static XnioWorker worker; private static Undertow server; /** * Just a handler that receives the request and sends back the same data * (empty response otherwise). * @param exchange The HttpServerExchange */ private static void sendEchoResponse(final HttpServerExchange exchange) { exchange.setStatusCode(StatusCodes.OK); // response using echo or empty string if (exchange.getRequestContentLength() > 0) { exchange.getRequestReceiver().receiveFullString(new Receiver.FullStringCallback() { @Override public void handle(HttpServerExchange exchange, String message) { exchange.getResponseSender().send(message); } }); } else { final Sender sender = exchange.getResponseSender(); sender.send(""); } } /** * Initializes the server with the H2C handler and adds the echo handler to * manage the requests. * @throws IOException Some error */ @BeforeClass public static void beforeClass() throws IOException { final PathHandler path = new PathHandler() .addExactPath(ECHO_PATH, new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { sendEchoResponse(exchange); } }); server = Undertow.builder() .addHttpListener(DefaultServer.getHostPort() + 1, DefaultServer.getHostAddress(), new Http2UpgradeHandler(path)) .setSocketOption(Options.REUSE_ADDRESSES, true) .build(); server.start(); // Create xnio worker final Xnio xnio = Xnio.getInstance(); final XnioWorker xnioWorker = xnio.createWorker(null, OptionMap.builder() .set(Options.WORKER_IO_THREADS, 8) .set(Options.TCP_NODELAY, true) .set(Options.KEEP_ALIVE, true) .getMap()); worker = xnioWorker; } /** * Stops server and worker. */ @AfterClass public static void afterClass() { if (server != null) { server.stop(); } if (worker != null) { worker.shutdown(); } } /** * Method that sends a POST request to the server with a message of * size equals to contentLength. If reset is true the callback will send * a RST after the response is received. * @param connection The connection to use * @param contentLength The content length to send * @param reset If true a reset is send to the received frame ID after the response is received * @throws Exception Some error */ private void sendRequest(ClientConnection connection, int contentLength, boolean reset) throws Exception { final CountDownLatch latch = new CountDownLatch(1); final List responses = new CopyOnWriteArrayList<>(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < contentLength; i++) { sb.append(i % 10); } final String content = sb.length() > 0? sb.toString() : null; connection.getIoThread().execute(new Runnable() { @Override public void run() { final ClientRequest request = new ClientRequest() .setMethod(Methods.POST) .setPath(ECHO_PATH); request.getRequestHeaders().put(Headers.HOST, DefaultServer.getHostAddress()); request.getRequestHeaders().put(Headers.CONTENT_LENGTH, contentLength); connection.sendRequest(request, createClientCallback(responses, latch, content, reset)); } }); latch.await(10, TimeUnit.SECONDS); Assert.assertEquals("No response received from server in 10s", 1, responses.size()); Assert.assertEquals("Response code was not OK", StatusCodes.OK, responses.get(0).getResponseCode()); Assert.assertEquals("Incorrect data received for response", contentLength > 0 ? content : "", responses.get(0).getAttachment(RESPONSE_BODY)); } /** * The real test that sends several POST requests with and without reset. * @throws Exception Some error */ @Test public void testUpgradeWithReset() throws Exception { final UndertowClient client = UndertowClient.getInstance(); // the client connection uses the small byte-buffer of 1024 to force the continuation frames final ClientConnection connection = client.connect( new URI("http://" + DefaultServer.getHostAddress() + ":" + (DefaultServer.getHostPort() + 1)), worker, new UndertowXnioSsl(worker.getXnio(), OptionMap.EMPTY, DefaultServer.getClientSSLContext()), DefaultServer.getBufferPool(), OptionMap.create(UndertowOptions.ENABLE_HTTP2, true)).get(); try { // the first request triggers the upgrade to H2C and sends a RST sendRequest(connection, 10, true); // send several requests with and without reset sendRequest(connection, 10, false); sendRequest(connection, 10, true); sendRequest(connection, 10, false); } finally { IoUtils.safeClose(connection); } } /** * Create the callback to receive the response and assign it to the list. * @param responses The list where the response will be added * @param latch The latch to count down when the response is received * @param message The message to send if it's a POST message (if null nothing is send) * @param boolean reset if true a RST is sent for the received the frame ID after completed * @return The created callback */ private static ClientCallback createClientCallback(final List responses, final CountDownLatch latch, String message, boolean reset) { return new ClientCallback() { @Override public void completed(ClientExchange result) { if (message != null) { new StringWriteChannelListener(message).setup(result.getRequestChannel()); } result.setResponseListener(new ClientCallback() { @Override public void completed(final ClientExchange result) { responses.add(result.getResponse()); new StringReadChannelListener(result.getConnection().getBufferPool()) { @Override protected void stringDone(String string) { result.getResponse().putAttachment(RESPONSE_BODY, string); if (reset) { Http2StreamSourceChannel res = (Http2StreamSourceChannel) result.getResponseChannel(); res.getHttp2Channel().sendRstStream(res.getStreamId(), Http2Channel.ERROR_STREAM_CLOSED); } latch.countDown(); } @Override protected void error(IOException e) { e.printStackTrace(); latch.countDown(); } }.setup(result.getResponseChannel()); } @Override public void failed(IOException e) { e.printStackTrace(); latch.countDown(); } }); } @Override public void failed(IOException e) { e.printStackTrace(); latch.countDown(); } }; } }undertow-2.2.16.Final/core/src/test/java/io/undertow/client/http/Http2ClientTestCase.java000066400000000000000000000342571420065311100312760ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2018 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * 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. */ package io.undertow.client.http; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.Xnio; import org.xnio.XnioWorker; import org.xnio.channels.StreamSinkChannel; import io.undertow.Undertow; import io.undertow.UndertowOptions; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientExchange; import io.undertow.client.ClientRequest; import io.undertow.client.ClientResponse; import io.undertow.client.UndertowClient; import io.undertow.io.Receiver; import io.undertow.io.Sender; import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.PathHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.util.AttachmentKey; import io.undertow.util.Headers; import io.undertow.util.Methods; import io.undertow.util.Protocols; import io.undertow.util.StatusCodes; import io.undertow.util.StringReadChannelListener; import io.undertow.util.StringWriteChannelListener; import static io.undertow.testutils.StopServerWithExternalWorkerUtils.stopWorker; /** * @author Emanuel Muckenhuber */ @RunWith(DefaultServer.class) @HttpOneOnly public class Http2ClientTestCase { private static final String message = "Hello World!"; public static final String MESSAGE = "/message"; public static final String POST = "/post"; private static XnioWorker worker; private static Undertow server; private static final OptionMap DEFAULT_OPTIONS; private static URI ADDRESS; private static final AttachmentKey RESPONSE_BODY = AttachmentKey.create(String.class); static { final OptionMap.Builder builder = OptionMap.builder() .set(Options.WORKER_IO_THREADS, 8) .set(Options.TCP_NODELAY, true) .set(Options.KEEP_ALIVE, true) .set(Options.WORKER_NAME, "Client"); DEFAULT_OPTIONS = builder.getMap(); } static void sendMessage(final HttpServerExchange exchange) { exchange.setStatusCode(StatusCodes.OK); final Sender sender = exchange.getResponseSender(); sender.send(message); } @BeforeClass public static void beforeClass() throws IOException { int port = DefaultServer.getHostPort("default"); final PathHandler path = new PathHandler() .addExactPath(MESSAGE, new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { sendMessage(exchange); } }) .addExactPath(POST, new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getRequestReceiver().receiveFullString(new Receiver.FullStringCallback() { @Override public void handle(HttpServerExchange exchange, String message) { exchange.getResponseSender().send(message); } }); } }); server = Undertow.builder() .addHttpsListener(port + 1, DefaultServer.getHostAddress("default"), DefaultServer.getServerSslContext()) .setServerOption(UndertowOptions.ENABLE_HTTP2, true) .setSocketOption(Options.REUSE_ADDRESSES, true) .setHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if(!exchange.getProtocol().equals(Protocols.HTTP_2_0)) { throw new RuntimeException("Not HTTP/2"); } path.handleRequest(exchange); } }) .build(); server.start(); try { ADDRESS = new URI("https://" + DefaultServer.getHostAddress() + ":" + (port + 1)); } catch (URISyntaxException e) { throw new RuntimeException(e); } // Create xnio worker final Xnio xnio = Xnio.getInstance(); final XnioWorker xnioWorker = xnio.createWorker(null, DEFAULT_OPTIONS); worker = xnioWorker; } @AfterClass public static void afterClass() { if (server != null) server.stop(); if (worker != null) stopWorker(worker); } static UndertowClient createClient() { return createClient(OptionMap.EMPTY); } static UndertowClient createClient(final OptionMap options) { return UndertowClient.getInstance(); } @Test public void testSimpleBasic() throws Exception { // final UndertowClient client = createClient(); final List responses = new CopyOnWriteArrayList<>(); final CountDownLatch latch = new CountDownLatch(10); final ClientConnection connection = client.connect(ADDRESS, worker, new UndertowXnioSsl(worker.getXnio(), OptionMap.EMPTY, DefaultServer.getClientSSLContext()), DefaultServer.getBufferPool(), OptionMap.create(UndertowOptions.ENABLE_HTTP2, true)).get(); try { connection.getIoThread().execute(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { final ClientRequest request = new ClientRequest().setMethod(Methods.GET).setPath(MESSAGE); request.getRequestHeaders().put(Headers.HOST, DefaultServer.getHostAddress()); connection.sendRequest(request, createClientCallback(responses, latch)); } } }); latch.await(10, TimeUnit.SECONDS); Assert.assertEquals(10, responses.size()); for (final ClientResponse response : responses) { Assert.assertEquals(message, response.getAttachment(RESPONSE_BODY)); } } finally { IoUtils.safeClose(connection); } } @Test public void testHeadRequest() throws Exception { // final UndertowClient client = createClient(); final List responses = new CopyOnWriteArrayList<>(); final CountDownLatch latch = new CountDownLatch(10); final ClientConnection connection = client.connect(ADDRESS, worker, new UndertowXnioSsl(worker.getXnio(), OptionMap.EMPTY, DefaultServer.getClientSSLContext()), DefaultServer.getBufferPool(), OptionMap.create(UndertowOptions.ENABLE_HTTP2, true)).get(); try { connection.getIoThread().execute(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { final ClientRequest request = new ClientRequest().setMethod(Methods.HEAD).setPath(MESSAGE); request.getRequestHeaders().put(Headers.HOST, DefaultServer.getHostAddress()); connection.sendRequest(request, createClientCallback(responses, latch)); } } }); latch.await(10, TimeUnit.SECONDS); Assert.assertEquals(10, responses.size()); for (final ClientResponse response : responses) { Assert.assertEquals("", response.getAttachment(RESPONSE_BODY)); } } finally { IoUtils.safeClose(connection); } } @Test public void testPostRequest() throws Exception { // final UndertowClient client = createClient(); final String postMessage = "This is a post request"; final List responses = new CopyOnWriteArrayList<>(); final CountDownLatch latch = new CountDownLatch(10); final ClientConnection connection = client.connect(ADDRESS, worker, new UndertowXnioSsl(worker.getXnio(), OptionMap.EMPTY, DefaultServer.getClientSSLContext()), DefaultServer.getBufferPool(), OptionMap.create(UndertowOptions.ENABLE_HTTP2, true)).get(); try { connection.getIoThread().execute(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { final ClientRequest request = new ClientRequest().setMethod(Methods.POST).setPath(POST); request.getRequestHeaders().put(Headers.HOST, DefaultServer.getHostAddress()); request.getRequestHeaders().put(Headers.TRANSFER_ENCODING, "chunked"); connection.sendRequest(request, new ClientCallback() { @Override public void completed(ClientExchange result) { new StringWriteChannelListener(postMessage).setup(result.getRequestChannel()); result.setResponseListener(new ClientCallback() { @Override public void completed(ClientExchange result) { new StringReadChannelListener(DefaultServer.getBufferPool()) { @Override protected void stringDone(String string) { responses.add(string); latch.countDown(); } @Override protected void error(IOException e) { e.printStackTrace(); latch.countDown(); } }.setup(result.getResponseChannel()); } @Override public void failed(IOException e) { e.printStackTrace(); latch.countDown(); } }); } @Override public void failed(IOException e) { e.printStackTrace(); latch.countDown(); } }); } } }); latch.await(10, TimeUnit.SECONDS); Assert.assertEquals(10, responses.size()); for (final String response : responses) { Assert.assertEquals(postMessage, response); } } finally { IoUtils.safeClose(connection); } } private ClientCallback createClientCallback(final List responses, final CountDownLatch latch) { return new ClientCallback() { @Override public void completed(ClientExchange result) { result.setResponseListener(new ClientCallback() { @Override public void completed(final ClientExchange result) { responses.add(result.getResponse()); new StringReadChannelListener(result.getConnection().getBufferPool()) { @Override protected void stringDone(String string) { result.getResponse().putAttachment(RESPONSE_BODY, string); latch.countDown(); } @Override protected void error(IOException e) { e.printStackTrace(); latch.countDown(); } }.setup(result.getResponseChannel()); } @Override public void failed(IOException e) { e.printStackTrace(); latch.countDown(); } }); try { result.getRequestChannel().shutdownWrites(); if (!result.getRequestChannel().flush()) { result.getRequestChannel().getWriteSetter().set(ChannelListeners.flushingChannelListener(null, null)); result.getRequestChannel().resumeWrites(); } } catch (IOException e) { e.printStackTrace(); latch.countDown(); } } @Override public void failed(IOException e) { e.printStackTrace(); latch.countDown(); } }; } } undertow-2.2.16.Final/core/src/test/java/io/undertow/client/http/HttpClientTestCase.java000066400000000000000000000436331420065311100312120ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client.http; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLContext; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientExchange; import io.undertow.client.ClientRequest; import io.undertow.client.ClientResponse; import io.undertow.client.UndertowClient; import io.undertow.io.Receiver; import io.undertow.io.Sender; import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.PathHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.util.AttachmentKey; import io.undertow.util.Headers; import io.undertow.util.Methods; import io.undertow.util.StatusCodes; import io.undertow.util.StringReadChannelListener; import io.undertow.util.StringWriteChannelListener; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.Xnio; import org.xnio.XnioWorker; import org.xnio.channels.StreamSinkChannel; import org.xnio.ssl.XnioSsl; import static io.undertow.testutils.StopServerWithExternalWorkerUtils.stopWorker; /** * @author Emanuel Muckenhuber * @author Flavia Rainone */ @RunWith(DefaultServer.class) @HttpOneOnly public class HttpClientTestCase { private static final String message = "Hello World!"; public static final String MESSAGE = "/message"; public static final String READTIMEOUT = "/readtimeout"; public static final String POST = "/post"; private static XnioWorker worker; private static final OptionMap DEFAULT_OPTIONS; private static final URI ADDRESS; private static final AttachmentKey RESPONSE_BODY = AttachmentKey.create(String.class); private IOException exception; static { final OptionMap.Builder builder = OptionMap.builder() .set(Options.WORKER_IO_THREADS, 8) .set(Options.TCP_NODELAY, true) .set(Options.KEEP_ALIVE, true) .set(Options.WORKER_NAME, "Client"); DEFAULT_OPTIONS = builder.getMap(); try { ADDRESS = new URI(DefaultServer.getDefaultServerURL()); } catch (URISyntaxException e) { throw new RuntimeException(e); } } static void sendMessage(final HttpServerExchange exchange) { exchange.setStatusCode(StatusCodes.OK); exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, message.length() + ""); final Sender sender = exchange.getResponseSender(); sender.send(message); } @BeforeClass public static void beforeClass() throws IOException { // Create xnio worker final Xnio xnio = Xnio.getInstance(); final XnioWorker xnioWorker = xnio.createWorker(null, DEFAULT_OPTIONS); worker = xnioWorker; DefaultServer.setRootHandler(new PathHandler() .addExactPath(MESSAGE, new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { sendMessage(exchange); } }) .addExactPath(READTIMEOUT, new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.setStatusCode(StatusCodes.OK); exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, 5 + ""); StreamSinkChannel responseChannel = exchange.getResponseChannel(); responseChannel.write(ByteBuffer.wrap(new byte[]{'a', 'b', 'c'})); responseChannel.flush(); try { //READ_TIMEOUT set as 600ms on the client side //On the server side intentionally sleep 2000ms //to make READ_TIMEOUT happening at client side Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } responseChannel.write(ByteBuffer.wrap(new byte[]{'d', 'e'})); responseChannel.close(); } }) .addExactPath(POST, new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getRequestReceiver().receiveFullString(new Receiver.FullStringCallback() { @Override public void handle(HttpServerExchange exchange, String message) { exchange.getResponseSender().send(message); } }); } })); } @AfterClass public static void afterClass() { stopWorker(worker); } static UndertowClient createClient() { return createClient(OptionMap.EMPTY); } static UndertowClient createClient(final OptionMap options) { return UndertowClient.getInstance(); } @Test public void testSimpleBasic() throws Exception { // final UndertowClient client = createClient(); final List responses = new CopyOnWriteArrayList<>(); final CountDownLatch latch = new CountDownLatch(10); final ClientConnection connection = client.connect(ADDRESS, worker, DefaultServer.getBufferPool(), OptionMap.EMPTY).get(); try { connection.getIoThread().execute(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { final ClientRequest request = new ClientRequest().setMethod(Methods.GET).setPath(MESSAGE); request.getRequestHeaders().put(Headers.HOST, DefaultServer.getHostAddress()); connection.sendRequest(request, createClientCallback(responses, latch)); } } }); latch.await(10, TimeUnit.SECONDS); Assert.assertEquals(10, responses.size()); for (final ClientResponse response : responses) { Assert.assertEquals(message, response.getAttachment(RESPONSE_BODY)); } } finally { IoUtils.safeClose(connection); } } @Test public void testPostRequest() throws Exception { // final UndertowClient client = createClient(); final String postMessage = "This is a post request"; final List responses = new CopyOnWriteArrayList<>(); final CountDownLatch latch = new CountDownLatch(10); final ClientConnection connection = client.connect(ADDRESS, worker, DefaultServer.getBufferPool(), OptionMap.EMPTY).get(); try { connection.getIoThread().execute(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { final ClientRequest request = new ClientRequest().setMethod(Methods.POST).setPath(POST); request.getRequestHeaders().put(Headers.HOST, DefaultServer.getHostAddress()); request.getRequestHeaders().put(Headers.TRANSFER_ENCODING, "chunked"); connection.sendRequest(request, new ClientCallback() { @Override public void completed(ClientExchange result) { new StringWriteChannelListener(postMessage).setup(result.getRequestChannel()); result.setResponseListener(new ClientCallback() { @Override public void completed(ClientExchange result) { new StringReadChannelListener(DefaultServer.getBufferPool()) { @Override protected void stringDone(String string) { responses.add(string); latch.countDown(); } @Override protected void error(IOException e) { e.printStackTrace(); latch.countDown(); } }.setup(result.getResponseChannel()); } @Override public void failed(IOException e) { e.printStackTrace(); latch.countDown(); } }); } @Override public void failed(IOException e) { e.printStackTrace(); latch.countDown(); } }); } } }); latch.await(10, TimeUnit.SECONDS); Assert.assertEquals(10, responses.size()); for (final String response : responses) { Assert.assertEquals(postMessage, response); } } finally { IoUtils.safeClose(connection); } } @Test public void testSsl() throws Exception { // final UndertowClient client = createClient(); final List responses = new CopyOnWriteArrayList<>(); final CountDownLatch latch = new CountDownLatch(10); DefaultServer.startSSLServer(); SSLContext context = DefaultServer.getClientSSLContext(); XnioSsl ssl = new UndertowXnioSsl(DefaultServer.getWorker().getXnio(), OptionMap.EMPTY, DefaultServer.SSL_BUFFER_POOL, context); final ClientConnection connection = client.connect(new URI(DefaultServer.getDefaultServerSSLAddress()), worker, ssl, DefaultServer.getBufferPool(), OptionMap.EMPTY).get(); try { connection.getIoThread().execute(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { final ClientRequest request = new ClientRequest().setMethod(Methods.GET).setPath(MESSAGE); request.getRequestHeaders().put(Headers.HOST, DefaultServer.getHostAddress()); connection.sendRequest(request, createClientCallback(responses, latch)); } } }); latch.await(10, TimeUnit.SECONDS); Assert.assertEquals(10, responses.size()); for (final ClientResponse response : responses) { Assert.assertEquals(message, response.getAttachment(RESPONSE_BODY)); } } finally { connection.getIoThread().execute(new Runnable() { @Override public void run() { IoUtils.safeClose(connection); } }); DefaultServer.stopSSLServer(); } } @Test public void testConnectionClose() throws Exception { // final UndertowClient client = createClient(); final CountDownLatch latch = new CountDownLatch(1); final ClientConnection connection = client.connect(ADDRESS, worker, DefaultServer.getBufferPool(), OptionMap.EMPTY).get(); try { ClientRequest request = new ClientRequest().setPath(MESSAGE).setMethod(Methods.GET); request.getRequestHeaders().put(Headers.HOST, DefaultServer.getHostAddress()); final List responses = new CopyOnWriteArrayList<>(); request.getRequestHeaders().add(Headers.CONNECTION, Headers.CLOSE.toString()); connection.sendRequest(request, createClientCallback(responses, latch)); latch.await(); final ClientResponse response = responses.iterator().next(); Assert.assertEquals(message, response.getAttachment(RESPONSE_BODY)); Assert.assertEquals(false, connection.isOpen()); } finally { IoUtils.safeClose(connection); } } @Test public void testReadTimeout() throws Exception { // final UndertowClient client = createClient(); exception = null; final List responses = new CopyOnWriteArrayList<>(); final CountDownLatch latch = new CountDownLatch(1); OptionMap.Builder builder = OptionMap.builder(); builder.set(Options.READ_TIMEOUT, 600); final ClientConnection connection = client.connect(ADDRESS, worker, DefaultServer.getBufferPool(), builder.getMap()).get(); final ClientRequest request = new ClientRequest().setMethod(Methods.GET).setPath(READTIMEOUT); request.getRequestHeaders().put(Headers.HOST, DefaultServer.getHostAddress()); connection.sendRequest(request, createClientCallback(responses, latch, false)); try { connection.getIoThread().execute(new Runnable() { @Override public void run() { for (int i = 0; i < 1; i++) { final ClientRequest request = new ClientRequest().setMethod(Methods.GET).setPath(READTIMEOUT); request.getRequestHeaders().put(Headers.HOST, DefaultServer.getHostAddress()); connection.sendRequest(request, createClientCallback(responses, latch, false)); } } }); latch.await(10, TimeUnit.SECONDS); //exception expected because of read timeout Assert.assertNotNull(exception); } finally { IoUtils.safeClose(connection); } } private ClientCallback createClientCallback(final List responses, final CountDownLatch latch) { return createClientCallback(responses, latch, true); } private ClientCallback createClientCallback(final List responses, final CountDownLatch latch, final boolean expectedResponse) { return new ClientCallback() { @Override public void completed(ClientExchange result) { result.setResponseListener(new ClientCallback() { @Override public void completed(final ClientExchange result) { new StringReadChannelListener(result.getConnection().getBufferPool()) { @Override protected void stringDone(String string) { result.getResponse().putAttachment(RESPONSE_BODY, string); // add response only if there is a string or error, or else // we risk adding keep alive messages in timeout tests responses.add(result.getResponse()); if (expectedResponse) latch.countDown(); } @Override protected void error(IOException e) { e.printStackTrace(); exception = e; // add response only if there is a string or error, or else // we risk adding keep alive messages in timeout tests responses.add(result.getResponse()); latch.countDown(); } }.setup(result.getResponseChannel()); } @Override public void failed(IOException e) { e.printStackTrace(); exception = e; latch.countDown(); } }); try { result.getRequestChannel().shutdownWrites(); if(!result.getRequestChannel().flush()) { result.getRequestChannel().getWriteSetter().set(ChannelListeners.flushingChannelListener(null, null)); result.getRequestChannel().resumeWrites(); } } catch (IOException e) { exception = e; e.printStackTrace(); latch.countDown(); } } @Override public void failed(IOException e) { exception = e; e.printStackTrace(); latch.countDown(); } }; } } undertow-2.2.16.Final/core/src/test/java/io/undertow/client/http/ResponseParserResumeTestCase.java000066400000000000000000000074211420065311100332630ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.client.http; import io.undertow.testutils.category.UnitTest; import io.undertow.util.BadRequestException; import io.undertow.util.HttpString; import io.undertow.util.Protocols; import io.undertow.util.StatusCodes; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import java.nio.ByteBuffer; /** * Tests that the parser can resume when it is given partial input * * @author Stuart Douglas */ @Category(UnitTest.class) public class ResponseParserResumeTestCase { public static final String DATA = "HTTP/1.1 200 OK\r\nHost: www.somehost.net\r\nOtherHeader: some\r\n value\r\nHostee:another\r\nAccept-garbage: a\r\n\r\ntttt"; @Test public void testMethodSplit() { byte[] in = DATA.getBytes(); for (int i = 0; i < in.length - 4; ++i) { try { testResume(i, in); } catch (Throwable e) { throw new RuntimeException("Test failed at split " + i, e); } } } @Test public void testOneCharacterAtATime() throws BadRequestException { byte[] in = DATA.getBytes(); final ResponseParseState context = new ResponseParseState(); HttpResponseBuilder result = new HttpResponseBuilder(); ByteBuffer buffer = ByteBuffer.wrap(in); buffer.limit(1); while (context.state != ResponseParseState.PARSE_COMPLETE) { HttpResponseParser.INSTANCE.handle(buffer, context, result); buffer.limit(buffer.limit() + 1); } runAssertions(result, context); } private void testResume(final int split, byte[] in) throws BadRequestException { final ResponseParseState context = new ResponseParseState(); HttpResponseBuilder result = new HttpResponseBuilder(); ByteBuffer buffer = ByteBuffer.wrap(in); buffer.limit(split); HttpResponseParser.INSTANCE.handle(buffer, context, result); Assert.assertEquals(0, buffer.remaining()); buffer.limit(buffer.capacity()); HttpResponseParser.INSTANCE.handle(buffer,context, result); runAssertions(result, context); Assert.assertEquals(4, buffer.remaining()); } private void runAssertions(final HttpResponseBuilder result, final ResponseParseState context) { Assert.assertEquals(StatusCodes.OK, result.getStatusCode()); Assert.assertEquals("OK", result.getReasonPhrase()); Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol()); Assert.assertEquals("www.somehost.net", result.getResponseHeaders().getFirst(new HttpString("Host"))); Assert.assertEquals("some value", result.getResponseHeaders().getFirst(new HttpString("OtherHeader"))); Assert.assertEquals("another", result.getResponseHeaders().getFirst(new HttpString("Hostee"))); Assert.assertEquals("a", result.getResponseHeaders().getFirst(new HttpString("Accept-garbage"))); Assert.assertEquals(4, result.getResponseHeaders().getHeaderNames().size()); Assert.assertEquals(ResponseParseState.PARSE_COMPLETE, context.state); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/predicate/000077500000000000000000000000001420065311100243275ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/predicate/PredicateParsingTestCase.java000066400000000000000000000141651420065311100320610ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.predicate; import io.undertow.testutils.category.UnitTest; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; import io.undertow.util.HttpString; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import java.util.HashMap; /** * @author Stuart Douglas */ @Category(UnitTest.class) public class PredicateParsingTestCase { @Test public void testPredicateParser() { Predicate predicate = PredicateParser.parse("path[foo]", PredicateParsingTestCase.class.getClassLoader()); Assert.assertTrue(predicate instanceof PathMatchPredicate); HttpServerExchange e = new HttpServerExchange(null); e.setRelativePath("/foo"); Assert.assertTrue(predicate.resolve(e)); e.setRelativePath("/bob"); Assert.assertFalse(predicate.resolve(e)); for (String string : new String[]{ "not path[\"/foo\"]", "not path[foo] and true", "false or not path[path=/foo]", "false or not path[/foo]", "true and not path[foo] or not path[foo] and false"}) { try { predicate = PredicateParser.parse(string, PredicateParsingTestCase.class.getClassLoader()); e = new HttpServerExchange(null); e.setRelativePath("/foo"); Assert.assertFalse(predicate.resolve(e)); e.setRelativePath("/bob"); Assert.assertTrue(predicate.resolve(e)); } catch (Throwable ex) { throw new RuntimeException("String " + string, ex); } } } @Test public void testPredicateContextVariable() { Predicate predicate = PredicateParser.parse("regex[pattern=\"/publicdb/(.*?)/.*\", value=\"%R\", full-match=false] and equals[%{i,username}, ${1}]", PredicateParsingTestCase.class.getClassLoader()); HttpServerExchange e = new HttpServerExchange(null); e.setRelativePath("/publicdb/foo/bar"); Assert.assertFalse(predicate.resolve(e)); e.getRequestHeaders().add(new HttpString("username"), "foo"); Assert.assertTrue(predicate.resolve(e)); } @Test public void testRegularExpressionsWithPredicateContext() { Predicate predicate = PredicateParser.parse("regex[pattern=a* , value=%{RELATIVE_PATH}] and equals[{$0, aaa}]", PredicateParsingTestCase.class.getClassLoader()); HttpServerExchange e = new HttpServerExchange(null); e.putAttachment(Predicate.PREDICATE_CONTEXT, new HashMap()); e.setRelativePath("aaab"); Assert.assertTrue(predicate.resolve(e)); e.setRelativePath("aaaab"); Assert.assertFalse(predicate.resolve(e)); predicate = PredicateParser.parse("regex[pattern='a(b*)a*' , value=%{RELATIVE_PATH}] and equals[$1, bb]", PredicateParsingTestCase.class.getClassLoader()); e.putAttachment(Predicate.PREDICATE_CONTEXT, new HashMap()); e.setRelativePath("abb"); Assert.assertTrue(predicate.resolve(e)); e.setRelativePath("abbaaa"); Assert.assertTrue(predicate.resolve(e)); e.setRelativePath("abbb"); Assert.assertFalse(predicate.resolve(e)); } @Test public void testArrayValues() { Predicate predicate; for (String string : new String[]{ "contains[value=%{i,Content-Type}, search=text]", "contains[value=\"%{i,Content-Type}\", search={text}]", "contains[value=\"%{i,Content-Type}\", search={text, \"other text\"}]", }) { try { predicate = PredicateParser.parse(string, PredicateParsingTestCase.class.getClassLoader()); HttpServerExchange e = new HttpServerExchange(null); Assert.assertFalse(predicate.resolve(e)); e.getRequestHeaders().add(Headers.CONTENT_TYPE, "text"); Assert.assertTrue(predicate.resolve(e)); } catch (Throwable ex) { throw new RuntimeException("String " + string, ex); } } } @Test public void testDefaultArrayValue() { Predicate predicate = PredicateParser.parse("equals[%{i,Content-Type},\"text/plain\"]", PredicateParsingTestCase.class.getClassLoader()); HttpServerExchange e = new HttpServerExchange(null); Assert.assertFalse(predicate.resolve(e)); e.getRequestHeaders().add(Headers.CONTENT_TYPE, "text/plain"); Assert.assertTrue(predicate.resolve(e)); } @Test public void testOrderOfOperations() { expect("exists[%{i,Content-Length}] or exists[value=%{i,Trailer}] and exists[%{i,Other}]", false, true); expect("(exists[%{i,Content-Length}] or exists[value=%{i,Trailer}]) and exists[%{i,Other}]", false, false); } private void expect(String string, boolean result1, boolean result2) { try { Predicate predicate = PredicateParser.parse(string, PredicateParsingTestCase.class.getClassLoader()); HttpServerExchange e = new HttpServerExchange(null); e.getRequestHeaders().add(Headers.TRAILER, "a"); Assert.assertEquals(result1, predicate.resolve(e)); e.getRequestHeaders().add(Headers.CONTENT_LENGTH, "a"); Assert.assertEquals(result2, predicate.resolve(e)); } catch (Throwable ex) { throw new RuntimeException("String " + string, ex); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/protocols/000077500000000000000000000000001420065311100244135ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/protocols/http2/000077500000000000000000000000001420065311100254545ustar00rootroot00000000000000HpackHuffmanEncodingUnitTestCase.java000066400000000000000000000045431420065311100345440ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/protocols/http2/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import io.undertow.testutils.category.UnitTest; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import java.nio.ByteBuffer; /** * @author Stuart Douglas */ @Category(UnitTest.class) public class HpackHuffmanEncodingUnitTestCase { @Test public void testHuffmanEncoding() throws HpackException { runTest("Hello World", ByteBuffer.allocate(100), true); runTest("Hello World", ByteBuffer.allocate(3), false); runTest("\\randomSpecialsChars~\u001D", ByteBuffer.allocate(100), true); runTest("\\~\u001D", ByteBuffer.allocate(100), false); //encoded form is larger than the original string } @Test public void testHuffmanEncodingLargeString() throws HpackException { StringBuilder sb = new StringBuilder(); for(int i = 0; i < 100; ++i) { sb.append("Hello World"); } runTest(sb.toString(), ByteBuffer.allocate(10000), true); //encoded form is larger than the original string } void runTest(String string, ByteBuffer buffer, boolean bufferBigEnough) throws HpackException { boolean res = HPackHuffman.encode(buffer, string, false); if(!bufferBigEnough) { Assert.assertFalse(res); return; } Assert.assertTrue(res); buffer.flip(); Assert.assertTrue(((1 << 7) & buffer.get(0)) != 0); //make sure the huffman bit is set int length = Hpack.decodeInteger(buffer, 7); StringBuilder sb = new StringBuilder(); HPackHuffman.decode(buffer, length, sb); Assert.assertEquals(string, sb.toString()); } } HpackSpecExamplesUnitTestCase.java000066400000000000000000000554541420065311100341110ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/protocols/http2/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.protocols.http2; import io.undertow.testutils.category.UnitTest; import io.undertow.util.HeaderMap; import io.undertow.util.HttpString; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import java.nio.ByteBuffer; /** * HPACK unit test case, based on examples from the spec * * @author Stuart Douglas */ @Category(UnitTest.class) public class HpackSpecExamplesUnitTestCase { @Test public void testExample_D_2_1() throws HpackException { //custom-key: custom-header byte[] data = { 0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72}; HpackDecoder decoder = new HpackDecoder(Hpack.DEFAULT_TABLE_SIZE); HeaderMapEmitter emitter = new HeaderMapEmitter(); decoder.setHeaderEmitter(emitter); decoder.decode(ByteBuffer.wrap(data), false); Assert.assertEquals(1, emitter.map.size()); Assert.assertEquals("custom-header", emitter.map.getFirst(new HttpString("custom-key"))); Assert.assertEquals(1, decoder.getFilledTableSlots()); Assert.assertEquals(55, decoder.getCurrentMemorySize()); assertTableState(decoder, 1, "custom-key", "custom-header"); } @Test public void testExample_D_2_2() throws HpackException { //:path: /sample/path byte[] data = {0x04, 0x0c, 0x2f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68}; HpackDecoder decoder = new HpackDecoder(Hpack.DEFAULT_TABLE_SIZE); HeaderMapEmitter emitter = new HeaderMapEmitter(); decoder.setHeaderEmitter(emitter); decoder.decode(ByteBuffer.wrap(data), false); Assert.assertEquals(1, emitter.map.size()); Assert.assertEquals("/sample/path", emitter.map.getFirst(new HttpString(":path"))); Assert.assertEquals(0, decoder.getFilledTableSlots()); Assert.assertEquals(0, decoder.getCurrentMemorySize()); } @Test public void testExample_D_2_3() throws HpackException { //password: secret byte[] data = {0x10, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74}; HpackDecoder decoder = new HpackDecoder(Hpack.DEFAULT_TABLE_SIZE); HeaderMapEmitter emitter = new HeaderMapEmitter(); decoder.setHeaderEmitter(emitter); decoder.decode(ByteBuffer.wrap(data), false); Assert.assertEquals(1, emitter.map.size()); Assert.assertEquals("secret", emitter.map.getFirst(new HttpString("password"))); Assert.assertEquals(0, decoder.getFilledTableSlots()); Assert.assertEquals(0, decoder.getCurrentMemorySize()); } @Test public void testExample_D_2_4() throws HpackException { //:method: GET byte[] data = {(byte) 0x82}; HpackDecoder decoder = new HpackDecoder(Hpack.DEFAULT_TABLE_SIZE); HeaderMapEmitter emitter = new HeaderMapEmitter(); decoder.setHeaderEmitter(emitter); decoder.decode(ByteBuffer.wrap(data), false); Assert.assertEquals(1, emitter.map.size()); Assert.assertEquals("GET", emitter.map.getFirst(new HttpString(":method"))); Assert.assertEquals(0, decoder.getFilledTableSlots()); Assert.assertEquals(0, decoder.getCurrentMemorySize()); } @Test public void testExample_D_3() throws HpackException { //d 3.1 byte[] data = {(byte) 0x82, (byte) 0x86, (byte) 0x84, 0x41, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d}; HpackDecoder decoder = new HpackDecoder(Hpack.DEFAULT_TABLE_SIZE); HeaderMapEmitter emitter = new HeaderMapEmitter(); decoder.setHeaderEmitter(emitter); decoder.decode(ByteBuffer.wrap(data) , false); Assert.assertEquals(4, emitter.map.size()); Assert.assertEquals("GET", emitter.map.getFirst(new HttpString(":method"))); Assert.assertEquals("http", emitter.map.getFirst(new HttpString(":scheme"))); Assert.assertEquals("/", emitter.map.getFirst(new HttpString(":path"))); Assert.assertEquals("www.example.com", emitter.map.getFirst(new HttpString(":authority"))); Assert.assertEquals(1, decoder.getFilledTableSlots()); Assert.assertEquals(57, decoder.getCurrentMemorySize()); assertTableState(decoder, 1, ":authority", "www.example.com"); //d 3.2 data = new byte[]{(byte) 0x82, (byte) 0x86, (byte) 0x84, (byte) 0xbe, 0x58, 0x08, 0x6e, 0x6f, 0x2d, 0x63, 0x61, 0x63, 0x68, 0x65}; emitter = new HeaderMapEmitter(); decoder.setHeaderEmitter(emitter); decoder.decode(ByteBuffer.wrap(data) , false); Assert.assertEquals(5, emitter.map.size()); Assert.assertEquals("GET", emitter.map.getFirst(new HttpString(":method"))); Assert.assertEquals("http", emitter.map.getFirst(new HttpString(":scheme"))); Assert.assertEquals("/", emitter.map.getFirst(new HttpString(":path"))); Assert.assertEquals("www.example.com", emitter.map.getFirst(new HttpString(":authority"))); Assert.assertEquals("no-cache", emitter.map.getFirst(new HttpString("cache-control"))); Assert.assertEquals(2, decoder.getFilledTableSlots()); Assert.assertEquals(110, decoder.getCurrentMemorySize()); assertTableState(decoder, 1, "cache-control", "no-cache"); assertTableState(decoder, 2, ":authority", "www.example.com"); //d 3.3 data = new byte[]{(byte) 0x82, (byte) 0x87, (byte) 0x85, (byte) 0xbf, 0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65}; emitter = new HeaderMapEmitter(); decoder.setHeaderEmitter(emitter); decoder.decode(ByteBuffer.wrap(data) , false); Assert.assertEquals(5, emitter.map.size()); Assert.assertEquals("GET", emitter.map.getFirst(new HttpString(":method"))); Assert.assertEquals("https", emitter.map.getFirst(new HttpString(":scheme"))); Assert.assertEquals("/index.html", emitter.map.getFirst(new HttpString(":path"))); Assert.assertEquals("www.example.com", emitter.map.getFirst(new HttpString(":authority"))); Assert.assertEquals("custom-value", emitter.map.getFirst(new HttpString("custom-key"))); Assert.assertEquals(3, decoder.getFilledTableSlots()); Assert.assertEquals(164, decoder.getCurrentMemorySize()); assertTableState(decoder, 1, "custom-key", "custom-value"); assertTableState(decoder, 2, "cache-control", "no-cache"); assertTableState(decoder, 3, ":authority", "www.example.com"); } @Test public void testExample_D_4() throws HpackException { //d 4.1 byte[] data = {(byte) 0x82, (byte) 0x86, (byte) 0x84, 0x41, (byte) 0x8c, (byte) 0xf1, (byte) 0xe3, (byte) 0xc2, (byte) 0xe5, (byte) 0xf2, 0x3a, 0x6b, (byte) 0xa0, (byte) 0xab, (byte) 0x90, (byte) 0xf4, (byte) 0xff}; HpackDecoder decoder = new HpackDecoder(Hpack.DEFAULT_TABLE_SIZE); HeaderMapEmitter emitter = new HeaderMapEmitter(); decoder.setHeaderEmitter(emitter); decoder.decode(ByteBuffer.wrap(data) , false); Assert.assertEquals(4, emitter.map.size()); Assert.assertEquals("GET", emitter.map.getFirst(new HttpString(":method"))); Assert.assertEquals("http", emitter.map.getFirst(new HttpString(":scheme"))); Assert.assertEquals("/", emitter.map.getFirst(new HttpString(":path"))); Assert.assertEquals("www.example.com", emitter.map.getFirst(new HttpString(":authority"))); Assert.assertEquals(1, decoder.getFilledTableSlots()); Assert.assertEquals(57, decoder.getCurrentMemorySize()); assertTableState(decoder, 1, ":authority", "www.example.com"); //d 4.2 data = new byte[]{(byte) 0x82, (byte) 0x86, (byte) 0x84, (byte) 0xbe, 0x58, (byte) 0x86, (byte) 0xa8, (byte) 0xeb, 0x10, 0x64, (byte) 0x9c, (byte) 0xbf}; emitter = new HeaderMapEmitter(); decoder.setHeaderEmitter(emitter); decoder.decode(ByteBuffer.wrap(data) , false); Assert.assertEquals(5, emitter.map.size()); Assert.assertEquals("GET", emitter.map.getFirst(new HttpString(":method"))); Assert.assertEquals("http", emitter.map.getFirst(new HttpString(":scheme"))); Assert.assertEquals("/", emitter.map.getFirst(new HttpString(":path"))); Assert.assertEquals("www.example.com", emitter.map.getFirst(new HttpString(":authority"))); Assert.assertEquals("no-cache", emitter.map.getFirst(new HttpString("cache-control"))); Assert.assertEquals(2, decoder.getFilledTableSlots()); Assert.assertEquals(110, decoder.getCurrentMemorySize()); assertTableState(decoder, 1, "cache-control", "no-cache"); assertTableState(decoder, 2, ":authority", "www.example.com"); //d 4.3 data = new byte[]{(byte) 0x82, (byte) 0x87, (byte) 0x85, (byte) 0xbf, 0x40, (byte) 0x88, 0x25, (byte) 0xa8, 0x49, (byte) 0xe9, 0x5b, (byte) 0xa9, 0x7d, 0x7f, (byte) 0x89, 0x25, (byte) 0xa8, 0x49, (byte) 0xe9, 0x5b, (byte) 0xb8, (byte) 0xe8, (byte) 0xb4, (byte) 0xbf}; emitter = new HeaderMapEmitter(); decoder.setHeaderEmitter(emitter); decoder.decode(ByteBuffer.wrap(data) , false); Assert.assertEquals(5, emitter.map.size()); Assert.assertEquals("GET", emitter.map.getFirst(new HttpString(":method"))); Assert.assertEquals("https", emitter.map.getFirst(new HttpString(":scheme"))); Assert.assertEquals("/index.html", emitter.map.getFirst(new HttpString(":path"))); Assert.assertEquals("www.example.com", emitter.map.getFirst(new HttpString(":authority"))); Assert.assertEquals("custom-value", emitter.map.getFirst(new HttpString("custom-key"))); Assert.assertEquals(3, decoder.getFilledTableSlots()); Assert.assertEquals(164, decoder.getCurrentMemorySize()); assertTableState(decoder, 1, "custom-key", "custom-value"); assertTableState(decoder, 2, "cache-control", "no-cache"); assertTableState(decoder, 3, ":authority", "www.example.com"); } @Test public void testExample_D_5() throws HpackException { byte[] data = {0x48, 0x03, 0x33, 0x30, 0x32, 0x58, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x61, 0x1d , 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, 0x4f, 0x63, 0x74, 0x20, 0x32, 0x30, 0x31, 0x33 , 0x20, 0x32, 0x30, 0x3a, 0x31, 0x33, 0x3a, 0x32, 0x31, 0x20, 0x47, 0x4d, 0x54, 0x6e, 0x17, 0x68 , 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70 , 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d}; HpackDecoder decoder = new HpackDecoder(256); //d 5.1 HeaderMapEmitter emitter = new HeaderMapEmitter(); decoder.setHeaderEmitter(emitter); decoder.decode(ByteBuffer.wrap(data) , false); Assert.assertEquals(4, emitter.map.size()); Assert.assertEquals("302", emitter.map.getFirst(new HttpString(":status"))); Assert.assertEquals("private", emitter.map.getFirst(new HttpString("cache-control"))); Assert.assertEquals("Mon, 21 Oct 2013 20:13:21 GMT", emitter.map.getFirst(new HttpString("date"))); Assert.assertEquals("https://www.example.com", emitter.map.getFirst(new HttpString("location"))); Assert.assertEquals(4, decoder.getFilledTableSlots()); Assert.assertEquals(222, decoder.getCurrentMemorySize()); assertTableState(decoder, 1, "location", "https://www.example.com"); assertTableState(decoder, 2, "date", "Mon, 21 Oct 2013 20:13:21 GMT"); assertTableState(decoder, 3, "cache-control", "private"); assertTableState(decoder, 4, ":status", "302"); //d 5.2 data = new byte[]{(byte) 0x48, 0x03, 0x33, 0x30, 0x37, (byte) 0xc1, (byte) 0xc0, (byte) 0xbf}; emitter = new HeaderMapEmitter(); decoder.setHeaderEmitter(emitter); decoder.decode(ByteBuffer.wrap(data) , false); Assert.assertEquals(4, emitter.map.size()); Assert.assertEquals("307", emitter.map.getFirst(new HttpString(":status"))); Assert.assertEquals("private", emitter.map.getFirst(new HttpString("cache-control"))); Assert.assertEquals("Mon, 21 Oct 2013 20:13:21 GMT", emitter.map.getFirst(new HttpString("date"))); Assert.assertEquals("https://www.example.com", emitter.map.getFirst(new HttpString("location"))); Assert.assertEquals(4, decoder.getFilledTableSlots()); Assert.assertEquals(222, decoder.getCurrentMemorySize()); assertTableState(decoder, 1, ":status", "307"); assertTableState(decoder, 2, "location", "https://www.example.com"); assertTableState(decoder, 3, "date", "Mon, 21 Oct 2013 20:13:21 GMT"); assertTableState(decoder, 4, "cache-control", "private"); data = new byte[]{(byte) 0x88, (byte) 0xc1, 0x61, 0x1d, 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, 0x4f, 0x63, 0x74, 0x20 , 0x32, 0x30, 0x31, 0x33, 0x20, 0x32, 0x30, 0x3a, 0x31, 0x33, 0x3a, 0x32, 0x32, 0x20, 0x47, 0x4d , 0x54, (byte) 0xc0, 0x5a, 0x04, 0x67, 0x7a, 0x69, 0x70, 0x77, 0x38, 0x66, 0x6f, 0x6f, 0x3d, 0x41, 0x53 , 0x44, 0x4a, 0x4b, 0x48, 0x51, 0x4b, 0x42, 0x5a, 0x58, 0x4f, 0x51, 0x57, 0x45, 0x4f, 0x50, 0x49 , 0x55, 0x41, 0x58, 0x51, 0x57, 0x45, 0x4f, 0x49, 0x55, 0x3b, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x61 , 0x67, 0x65, 0x3d, 0x33, 0x36, 0x30, 0x30, 0x3b, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e , 0x3d, 0x31}; emitter = new HeaderMapEmitter(); decoder.setHeaderEmitter(emitter); decoder.decode(ByteBuffer.wrap(data) , false); Assert.assertEquals(6, emitter.map.size()); Assert.assertEquals("200", emitter.map.getFirst(new HttpString(":status"))); Assert.assertEquals("private", emitter.map.getFirst(new HttpString("cache-control"))); Assert.assertEquals("Mon, 21 Oct 2013 20:13:22 GMT", emitter.map.getFirst(new HttpString("date"))); Assert.assertEquals("https://www.example.com", emitter.map.getFirst(new HttpString("location"))); Assert.assertEquals("foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", emitter.map.getFirst(new HttpString("set-cookie"))); Assert.assertEquals("gzip", emitter.map.getFirst(new HttpString("content-encoding"))); Assert.assertEquals(3, decoder.getFilledTableSlots()); Assert.assertEquals(215, decoder.getCurrentMemorySize()); assertTableState(decoder, 1, "set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"); assertTableState(decoder, 2, "content-encoding", "gzip"); assertTableState(decoder, 3, "date", "Mon, 21 Oct 2013 20:13:22 GMT"); } @Test public void testExample_D_6() throws HpackException { byte[] data = {0x48, (byte) 0x82, 0x64, 0x02, 0x58, (byte) 0x85, (byte) 0xae, (byte) 0xc3, 0x77, 0x1a, 0x4b, 0x61, (byte) 0x96, (byte) 0xd0, 0x7a, (byte) 0xbe , (byte) 0x94, 0x10, 0x54, (byte) 0xd4, 0x44, (byte) 0xa8, 0x20, 0x05, (byte) 0x95, 0x04, 0x0b, (byte) 0x81, 0x66, (byte) 0xe0, (byte) 0x82, (byte) 0xa6 , 0x2d, 0x1b, (byte) 0xff, 0x6e, (byte) 0x91, (byte) 0x9d, 0x29, (byte) 0xad, 0x17, 0x18, 0x63, (byte) 0xc7, (byte) 0x8f, 0x0b, (byte) 0x97, (byte) 0xc8 , (byte) 0xe9, (byte) 0xae, (byte) 0x82, (byte) 0xae, 0x43, (byte) 0xd3 }; HpackDecoder decoder = new HpackDecoder(256); //d 5.1 HeaderMapEmitter emitter = new HeaderMapEmitter(); decoder.setHeaderEmitter(emitter); decoder.decode(ByteBuffer.wrap(data) , false); Assert.assertEquals(4, emitter.map.size()); Assert.assertEquals("302", emitter.map.getFirst(new HttpString(":status"))); Assert.assertEquals("private", emitter.map.getFirst(new HttpString("cache-control"))); Assert.assertEquals("Mon, 21 Oct 2013 20:13:21 GMT", emitter.map.getFirst(new HttpString("date"))); Assert.assertEquals("https://www.example.com", emitter.map.getFirst(new HttpString("location"))); Assert.assertEquals(4, decoder.getFilledTableSlots()); Assert.assertEquals(222, decoder.getCurrentMemorySize()); assertTableState(decoder, 1, "location", "https://www.example.com"); assertTableState(decoder, 2, "date", "Mon, 21 Oct 2013 20:13:21 GMT"); assertTableState(decoder, 3, "cache-control", "private"); assertTableState(decoder, 4, ":status", "302"); //d 5.2 data = new byte[]{(byte) 0x48, (byte) 0x83, 0x64, 0x0e, (byte) 0xff, (byte) 0xc1, (byte) 0xc0, (byte) 0xbf}; emitter = new HeaderMapEmitter(); decoder.setHeaderEmitter(emitter); decoder.decode(ByteBuffer.wrap(data) , false); Assert.assertEquals(4, emitter.map.size()); Assert.assertEquals("307", emitter.map.getFirst(new HttpString(":status"))); Assert.assertEquals("private", emitter.map.getFirst(new HttpString("cache-control"))); Assert.assertEquals("Mon, 21 Oct 2013 20:13:21 GMT", emitter.map.getFirst(new HttpString("date"))); Assert.assertEquals("https://www.example.com", emitter.map.getFirst(new HttpString("location"))); Assert.assertEquals(4, decoder.getFilledTableSlots()); Assert.assertEquals(222, decoder.getCurrentMemorySize()); assertTableState(decoder, 1, ":status", "307"); assertTableState(decoder, 2, "location", "https://www.example.com"); assertTableState(decoder, 3, "date", "Mon, 21 Oct 2013 20:13:21 GMT"); assertTableState(decoder, 4, "cache-control", "private"); data = new byte[]{(byte) 0x88, (byte) 0xc1, 0x61, (byte) 0x96, (byte) 0xd0, 0x7a, (byte) 0xbe, (byte) 0x94, 0x10, 0x54, (byte) 0xd4, 0x44, (byte) 0xa8, 0x20, 0x05, (byte) 0x95 , 0x04, 0x0b, (byte) 0x81, 0x66, (byte) 0xe0, (byte) 0x84, (byte) 0xa6, 0x2d, 0x1b, (byte) 0xff, (byte) 0xc0, 0x5a, (byte) 0x83, (byte) 0x9b, (byte) 0xd9, (byte) 0xab , 0x77, (byte) 0xad, (byte) 0x94, (byte) 0xe7, (byte) 0x82, 0x1d, (byte) 0xd7, (byte) 0xf2, (byte) 0xe6, (byte) 0xc7, (byte) 0xb3, 0x35, (byte) 0xdf, (byte) 0xdf, (byte) 0xcd, 0x5b , 0x39, 0x60, (byte) 0xd5, (byte) 0xaf, 0x27, 0x08, 0x7f, 0x36, 0x72, (byte) 0xc1, (byte) 0xab, 0x27, 0x0f, (byte) 0xb5, 0x29, 0x1f , (byte) 0x95, (byte) 0x87, 0x31, 0x60, 0x65, (byte) 0xc0, 0x03, (byte) 0xed, 0x4e, (byte) 0xe5, (byte) 0xb1, 0x06, 0x3d, 0x50, 0x07 }; emitter = new HeaderMapEmitter(); decoder.setHeaderEmitter(emitter); decoder.decode(ByteBuffer.wrap(data) , false); Assert.assertEquals(6, emitter.map.size()); Assert.assertEquals("200", emitter.map.getFirst(new HttpString(":status"))); Assert.assertEquals("private", emitter.map.getFirst(new HttpString("cache-control"))); Assert.assertEquals("Mon, 21 Oct 2013 20:13:22 GMT", emitter.map.getFirst(new HttpString("date"))); Assert.assertEquals("https://www.example.com", emitter.map.getFirst(new HttpString("location"))); Assert.assertEquals("foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", emitter.map.getFirst(new HttpString("set-cookie"))); Assert.assertEquals("gzip", emitter.map.getFirst(new HttpString("content-encoding"))); Assert.assertEquals(3, decoder.getFilledTableSlots()); Assert.assertEquals(215, decoder.getCurrentMemorySize()); assertTableState(decoder, 1, "set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"); assertTableState(decoder, 2, "content-encoding", "gzip"); assertTableState(decoder, 3, "date", "Mon, 21 Oct 2013 20:13:22 GMT"); } @Test public void testExample_D_2_110() throws HpackException { // my, wrong header field without indexing data test byte[] data = { 0x00 }; HpackDecoder decoder = new HpackDecoder(Hpack.DEFAULT_TABLE_SIZE); HeaderMapEmitter emitter = new HeaderMapEmitter(); decoder.setHeaderEmitter(emitter); try { decoder.decode(ByteBuffer.wrap(data) , false); } catch (HpackException e) { // This exception is expected return; } Assert.fail("Didn't get expected HPackException!"); } @Test public void testExample_D_2_111() throws HpackException { // my, wrong header field with incremental indexing data test (last byte is not passed) byte[] data = { 0x60, 0x01, 0x78, 0x01 }; // , 0x79}; HpackDecoder decoder = new HpackDecoder(Hpack.DEFAULT_TABLE_SIZE); HeaderMapEmitter emitter = new HeaderMapEmitter(); decoder.setHeaderEmitter(emitter); try { decoder.decode(ByteBuffer.wrap(data) , false); } catch (HpackException e) { // This exception is expected return; } Assert.fail("Didn't get expected HPackException!"); } @Test public void testExample_D_2_112() throws HpackException { // my, wrong header field with incremental indexing data test (last byte is not passed) byte[] data = { 0x60, 0x02, 0x78 }; //, 0x79}; HpackDecoder decoder = new HpackDecoder(Hpack.DEFAULT_TABLE_SIZE); HeaderMapEmitter emitter = new HeaderMapEmitter(); decoder.setHeaderEmitter(emitter); try { decoder.decode(ByteBuffer.wrap(data) , false); } catch (HpackException e) { // This exception is expected return; } Assert.fail("Didn't get expected HPackException!"); } private static void assertTableState(HpackDecoder decoder, int index, String name, String value) throws HpackException { int idx = decoder.getRealIndex(index); Hpack.HeaderField val = decoder.getHeaderTable()[idx]; Assert.assertEquals(name, val.name.toString()); Assert.assertEquals(value, val.value); } private static class HeaderMapEmitter implements HpackDecoder.HeaderEmitter { HeaderMap map = new HeaderMap(); @Override public void emitHeader(HttpString name, String value, boolean neverIndex) { map.add(name, value); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/000077500000000000000000000000001420065311100236755ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/ConnectionTerminationTestCase.java000066400000000000000000000071551420065311100325150ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.IoUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.testutils.ProxyIgnore; import io.undertow.util.FileUtils; /** * Tests abnormal connection termination * * @author Stuart Douglas */ @RunWith(DefaultServer.class) @ProxyIgnore @HttpOneOnly public class ConnectionTerminationTestCase { private volatile boolean completionListenerCalled = false; private final CountDownLatch completionListenerCalledLatch = new CountDownLatch(1); @Test public void testAbnormalRequestTermination() throws IOException, InterruptedException { DefaultServer.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { if (exchange.isInIoThread()) { exchange.dispatch(this); return; } exchange.startBlocking(); exchange.addExchangeCompleteListener(new ExchangeCompletionListener() { @Override public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { completionListenerCalled = true; completionListenerCalledLatch.countDown(); nextListener.proceed(); } }); final InputStream request = exchange.getInputStream(); String data = FileUtils.readFile(request); exchange.getOutputStream().write(data.getBytes("UTF-8")); } }); Socket socket = new Socket(); socket.connect(DefaultServer.getDefaultServerAddress()); try { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10000; ++i) { sb.append("hello world\r\n"); } //send a large request that is too small, then kill the socket String request = "POST / HTTP/1.1\r\nHost:localhost\r\nContent-Length:" + sb.length() + 100 + "\r\n\r\n" + sb.toString(); OutputStream outputStream = socket.getOutputStream(); outputStream.write(request.getBytes("US-ASCII")); socket.getInputStream().close(); outputStream.close(); //make sure the completion listener is still called //this is important, as this can be used for resource cleanup completionListenerCalledLatch.await(5, TimeUnit.SECONDS); Assert.assertTrue(completionListenerCalled); } finally { IoUtils.safeClose(socket); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/DirectByteBufferDeallocatorTestCase.java000066400000000000000000000036411420065311100335420ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import org.junit.Test; import java.security.Permission; import java.security.Policy; import java.security.ProtectionDomain; import org.junit.Assert; /** * Test for UNDERTOW-1558, it cannot instantiate the class if a security manager is set on JDK1.8 * * @author tmiyar * */ public class DirectByteBufferDeallocatorTestCase { @Test public void directByteBufferDeallocatorInstantiationTest() { Exception exception = null; Policy.setPolicy(new Policy() { @Override public boolean implies(ProtectionDomain pd, Permission perm) { return true; } }); System.setSecurityManager(new SecurityManager()); try { DirectByteBufferDeallocator.free(null); } catch (Exception e) { exception = e; } Assert.assertNull("An exception was thrown with security manager enabled", exception); System.setSecurityManager(null); try { DirectByteBufferDeallocator.free(null); } catch (Exception e) { exception = e; } Assert.assertNull("An exception was thrown without security manager enabled", exception); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/EncodedEncodedSlashTestCase.java000066400000000000000000000055671420065311100320270ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.OptionMap; import io.undertow.UndertowOptions; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.ProxyIgnore; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; @RunWith(DefaultServer.class) public class EncodedEncodedSlashTestCase { @BeforeClass public static void setup() { DefaultServer.setRootHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send(exchange.getRequestPath()); } }); } @Test public void testSlashNotDecoded() throws Exception { final TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/%2f%5c"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("/%2f%5c", HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } @Test @ProxyIgnore public void testSlashDecoded() throws Exception { final TestHttpClient client = new TestHttpClient(); OptionMap old = DefaultServer.getUndertowOptions(); DefaultServer.setUndertowOptions(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/%2f%5c"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("//\\", HttpClientUtils.readResponse(result)); } finally { DefaultServer.setUndertowOptions(old); client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/HttpServerExchangeTestCase.java000066400000000000000000000067451420065311100317610ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Protocols; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class HttpServerExchangeTestCase { @BeforeClass public static void setup() { DefaultServer.setRootHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send(exchange.getHostName() + ":" + exchange.getProtocol() + ":" + exchange.getRequestMethod() + ":" + exchange.getHostPort() + ":" + exchange.getRequestURI() + ":" + exchange.getRelativePath() + ":" + exchange.getQueryString()); } }); } @Test public void testHttpServerExchange() throws IOException { String port = DefaultServer.isAjp() && !DefaultServer.isProxy() ? "9080" : "7777"; String protocol = DefaultServer.isH2() ? Protocols.HTTP_2_0_STRING : Protocols.HTTP_1_1_STRING; final TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/somepath"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(DefaultServer.getHostAddress() + ":" + protocol + ":GET:" + port + ":/somepath:/somepath:", HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/somepath?a=b"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(DefaultServer.getHostAddress() + ":" + protocol + ":GET:" + port + ":/somepath:/somepath:a=b", HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/somepath?a=b"); get.addHeader("Host", "[::1]:8080"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("::1:" + protocol + ":GET:8080:/somepath:/somepath:a=b", HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/InvalidHtpRequestTestCase.java000066400000000000000000000115771420065311100316220ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpRequestBase; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.server.handlers.ResponseCodeHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.testutils.ProxyIgnore; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) @ProxyIgnore @HttpOneOnly public class InvalidHtpRequestTestCase { @BeforeClass public static void setup() { DefaultServer.setRootHandler(ResponseCodeHandler.HANDLE_200); } @Test public void testInvalidCharacterInMethod() throws IOException { final TestHttpClient client = new TestHttpClient(); try { HttpRequestBase method = new HttpRequestBase() { @Override public String getMethod() { return "GET;POST"; } @Override public URI getURI() { try { return new URI(DefaultServer.getDefaultServerURL()); } catch (URISyntaxException e) { throw new RuntimeException(e); } } }; HttpResponse result = client.execute(method); Assert.assertEquals(StatusCodes.BAD_REQUEST, result.getStatusLine().getStatusCode()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testInvalidCharacterInHeader() throws IOException { final TestHttpClient client = new TestHttpClient(); try { HttpRequestBase method = new HttpGet(DefaultServer.getDefaultServerURL()); method.addHeader("fake;header", "value"); HttpResponse result = client.execute(method); Assert.assertEquals(StatusCodes.BAD_REQUEST, result.getStatusLine().getStatusCode()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testMultipleContentLengths() throws IOException { final TestHttpClient client = new TestHttpClient(); try { HttpRequestBase method = new HttpGet(DefaultServer.getDefaultServerURL()); method.addHeader(Headers.CONTENT_LENGTH_STRING, "0"); method.addHeader(Headers.CONTENT_LENGTH_STRING, "10"); HttpResponse result = client.execute(method); Assert.assertEquals(StatusCodes.BAD_REQUEST, result.getStatusLine().getStatusCode()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testContentLengthAndTransferEncoding() throws IOException { final TestHttpClient client = new TestHttpClient(); try { HttpRequestBase method = new HttpGet(DefaultServer.getDefaultServerURL()); method.addHeader(Headers.CONTENT_LENGTH_STRING, "0"); method.addHeader(Headers.TRANSFER_ENCODING_STRING, "chunked"); HttpResponse result = client.execute(method); Assert.assertEquals(StatusCodes.BAD_REQUEST, result.getStatusLine().getStatusCode()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testMultipleTransferEncoding() throws IOException { final TestHttpClient client = new TestHttpClient(); try { HttpRequestBase method = new HttpGet(DefaultServer.getDefaultServerURL()); method.addHeader(Headers.TRANSFER_ENCODING_STRING, "chunked"); method.addHeader(Headers.TRANSFER_ENCODING_STRING, "gzip, chunked"); HttpResponse result = client.execute(method); Assert.assertEquals(StatusCodes.BAD_REQUEST, result.getStatusLine().getStatusCode()); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/MaxRequestSizeTestCase.java000066400000000000000000000135151420065311100311320ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.junit.Assert; import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.OptionMap; import io.undertow.UndertowOptions; import io.undertow.server.handlers.BlockingHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.ProxyIgnore; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Headers; /** * @author Stuart Douglas */ @HttpOneOnly @ProxyIgnore @RunWith(DefaultServer.class) public class MaxRequestSizeTestCase { public static final String A_MESSAGE = "A message"; @BeforeClass public static void setup() { final BlockingHandler blockingHandler = new BlockingHandler(); DefaultServer.setRootHandler(blockingHandler); blockingHandler.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final OutputStream outputStream = exchange.getOutputStream(); final InputStream inputStream = exchange.getInputStream(); String m = HttpClientUtils.readResponse(inputStream); Assert.assertEquals(A_MESSAGE, m); inputStream.close(); outputStream.close(); } }); } @Test public void testMaxRequestHeaderSize() throws IOException { // FIXME UNDERTOW-1938 (returns 500 response instead of 200, sporadic) Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows")); OptionMap existing = DefaultServer.getUndertowOptions(); final TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/notamatchingpath"); post.setEntity(new StringEntity(A_MESSAGE)); post.addHeader(Headers.CONNECTION_STRING, "close"); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); OptionMap maxSize = OptionMap.create(UndertowOptions.MAX_HEADER_SIZE, 10); DefaultServer.setUndertowOptions(maxSize); try { HttpResponse response = client.execute(post); HttpClientUtils.readResponse(response); Assert.assertEquals(StatusCodes.BAD_REQUEST, response.getStatusLine().getStatusCode()); } catch (IOException e) { //expected } maxSize = OptionMap.create(UndertowOptions.MAX_HEADER_SIZE, 1000); DefaultServer.setUndertowOptions(maxSize); result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { DefaultServer.setUndertowOptions(existing); client.getConnectionManager().shutdown(); } } @Test public void testMaxRequestEntitySize() throws IOException { OptionMap existing = DefaultServer.getUndertowOptions(); final TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/notamatchingpath"); post.setEntity(new StringEntity(A_MESSAGE)); post.addHeader(Headers.CONNECTION_STRING, "close"); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); OptionMap maxSize = OptionMap.create(UndertowOptions.MAX_ENTITY_SIZE, (long) A_MESSAGE.length() - 1); DefaultServer.setUndertowOptions(maxSize); post = new HttpPost(DefaultServer.getDefaultServerURL() + "/notamatchingpath"); post.setEntity(new StringEntity(A_MESSAGE)); result = client.execute(post); Assert.assertEquals(StatusCodes.INTERNAL_SERVER_ERROR, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); maxSize = OptionMap.create(UndertowOptions.MAX_HEADER_SIZE, 1000); DefaultServer.setUndertowOptions(maxSize); post = new HttpPost(DefaultServer.getDefaultServerURL() + "/notamatchingpath"); post.setEntity(new StringEntity(A_MESSAGE)); post.addHeader(Headers.CONNECTION_STRING, "close"); result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { DefaultServer.setUndertowOptions(existing); client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/NewlineInHeadersTestCase.java000066400000000000000000000061301420065311100313600ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import java.io.IOException; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.io.Receiver; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.HttpString; import io.undertow.util.StatusCodes; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class NewlineInHeadersTestCase { private static final String RESPONSE = "response"; private static final String ECHO = "echo"; @Test public void testNewlineInHeaders() throws IOException { DefaultServer.setRootHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getRequestReceiver().receiveFullString(new Receiver.FullStringCallback() { @Override public void handle(HttpServerExchange exchange, String message) { exchange.getResponseHeaders().put(HttpString.tryFromString(ECHO), message); exchange.getResponseSender().send(RESPONSE); } }); } }); final TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL()); post.setEntity(new StringEntity("test")); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("test", result.getFirstHeader(ECHO).getValue()); Assert.assertEquals(RESPONSE, HttpClientUtils.readResponse(result)); post = new HttpPost(DefaultServer.getDefaultServerURL()); post.setEntity(new StringEntity("test\nnewline")); result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("test newline", result.getFirstHeader(ECHO).getValue()); Assert.assertEquals(RESPONSE, HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/ParseTimeoutTestCase.java000066400000000000000000000055371420065311100306270ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import io.undertow.UndertowOptions; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.testutils.ProxyIgnore; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.IoUtils; import org.xnio.OptionMap; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @RunWith(DefaultServer.class) @ProxyIgnore @HttpOneOnly public class ParseTimeoutTestCase { private Socket client; private OutputStream clientOutputStream; private InputStream clientInputStream; @Before public void before() throws Exception { client = new Socket(); client.connect(DefaultServer.getDefaultServerAddress()); clientOutputStream = client.getOutputStream(); clientInputStream = client.getInputStream(); } @After public void after() throws Exception { IoUtils.safeClose(client); DefaultServer.setUndertowOptions(OptionMap.EMPTY); } @BeforeClass public static void beforeClass() throws Exception { DefaultServer.setUndertowOptions(OptionMap.create(UndertowOptions.REQUEST_PARSE_TIMEOUT, 10)); } @AfterClass public static void afterClass() throws Exception { DefaultServer.setUndertowOptions(OptionMap.EMPTY); } @Test(timeout = 10000) public void testClosingConnectionWhenParsingHeadersForTooLong() throws Exception { //given DefaultServer.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) { fail("Parser should never end its job, since we are streaming headers."); } }); String request = "GET / HTTP/1.1\r\nHost:localhost"; //when clientOutputStream.write(request.getBytes()); clientOutputStream.flush(); Thread.sleep(100); //then assertEquals(-1, clientInputStream.read()); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/ReadTimeoutTestCase.java000066400000000000000000000146671420065311100304340ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.net.SocketException; import java.nio.channels.Channel; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Headers; import io.undertow.util.StringWriteChannelListener; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.AbstractHttpEntity; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.ChannelListeners; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.channels.ReadTimeoutException; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; /** * * Tests read timeout with a slow request * * @author Stuart Douglas * @author Flavia Rainone */ @RunWith(DefaultServer.class) @HttpOneOnly public class ReadTimeoutTestCase { private volatile Exception exception; @DefaultServer.BeforeServerStarts public static void beforeClass() { DefaultServer.setServerOptions(OptionMap.create(Options.READ_TIMEOUT, 10)); } @DefaultServer.AfterServerStops public static void afterClass() { DefaultServer.setServerOptions(OptionMap.EMPTY); } @Test public void testReadTimeout() throws InterruptedException, IOException { final CountDownLatch errorLatch = new CountDownLatch(1); DefaultServer.setRootHandler((final HttpServerExchange exchange) -> { final StreamSinkChannel response = exchange.getResponseChannel(); final StreamSourceChannel request = exchange.getRequestChannel(); request.getReadSetter().set(ChannelListeners.drainListener(Long.MAX_VALUE, (final Channel channel) -> { new StringWriteChannelListener("COMPLETED") { @Override protected void writeDone(final StreamSinkChannel channel) { exchange.endExchange(); } }.setup(response); }, (final StreamSourceChannel channel, final IOException e) -> { e.printStackTrace(); exchange.endExchange(); exception = e; errorLatch.countDown(); } )); request.wakeupReads(); }); final TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL()); post.setEntity(new AbstractHttpEntity() { @Override public InputStream getContent() throws IllegalStateException { return null; } @Override public void writeTo(final OutputStream outstream) throws IOException { for (int i = 0; i < 5; ++i) { outstream.write('*'); outstream.flush(); try { Thread.sleep(200); } catch (InterruptedException e) { throw new RuntimeException(e); } } } @Override public boolean isStreaming() { return true; } @Override public boolean isRepeatable() { return false; } @Override public long getContentLength() { return 5; } }); post.addHeader(Headers.CONNECTION_STRING, "close"); boolean socketFailure = false; try { client.execute(post); } catch (SocketException e) { Assert.assertTrue(e.getMessage(), e.getMessage().contains("Broken pipe") || e.getMessage().contains("connection abort")); socketFailure = true; } Assert.assertTrue("Test sent request without any exception", socketFailure); if (errorLatch.await(5, TimeUnit.SECONDS)) { Assert.assertTrue(getExceptionDescription(exception), exception instanceof ReadTimeoutException || (DefaultServer.isProxy() && exception instanceof IOException)); if (exception.getSuppressed() != null && exception.getSuppressed().length > 0) { for (Throwable supressed : exception.getSuppressed()) { Assert.assertEquals(getExceptionDescription(supressed), ReadTimeoutException.class, exception.getClass()); } } } else if (!DefaultServer.isProxy()) { // ignore if proxy, because when we're on proxy, we might not be able to see the exception Assert.fail("Did not get ReadTimeoutException"); } } finally { client.getConnectionManager().shutdown(); } } // TODO move this to an utility class private String getExceptionDescription(Throwable exception) { try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) { exception.printStackTrace(pw); return pw.toString(); } catch (IOException ioe) { throw new IllegalStateException(ioe); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/StopTestCase.java000066400000000000000000000016661420065311100271320ustar00rootroot00000000000000package io.undertow.server; import org.junit.After; import org.junit.Test; import org.xnio.Options; import io.undertow.Undertow; public class StopTestCase { @After public void waitServerStopCompletely() { // sleep 1 s to prevent BindException (Address already in use) when running the tests try { Thread.sleep(1000); } catch (InterruptedException ignore) {} } @Test public void testStopUndertowNotStarted() { Undertow.builder().build().stop(); } @Test public void testStopUndertowAfterExceptionDuringStart() { // Making the NioXnioWorker constructor throw an exception, resulting in the Undertow.worker field not getting set. Undertow undertow = Undertow.builder().setWorkerOption(Options.WORKER_IO_THREADS, -1).build(); try { undertow.start(); } catch (RuntimeException ignore) { } undertow.stop(); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/WriteTimeoutTestCase.java000066400000000000000000000121651420065311100306420ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.channels.Channel; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.testutils.TestHttpClient; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.ChannelListener; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.WriteTimeoutException; /** * Tests write timeout with a client that is slow to read the response * * @author Stuart Douglas */ @RunWith(DefaultServer.class) @HttpOneOnly @Ignore("UNDERTOW-1859 this test freezes") //FIXME public class WriteTimeoutTestCase { private volatile Exception exception; private static final CountDownLatch errorLatch = new CountDownLatch(1); @DefaultServer.BeforeServerStarts public static void setup() { DefaultServer.setServerOptions(OptionMap.builder().set(Options.WRITE_TIMEOUT, 10).getMap()); } @DefaultServer.AfterServerStops public static void cleanup() { DefaultServer.setServerOptions(OptionMap.EMPTY); } @Test public void testWriteTimeout() throws IOException, InterruptedException { DefaultServer.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final StreamSinkChannel response = exchange.getResponseChannel(); try { response.setOption(Options.WRITE_TIMEOUT, 10); } catch (IOException e) { throw new RuntimeException(e); } final int capacity = 1 * 1024 * 1024; //1mb final ByteBuffer originalBuffer = ByteBuffer.allocateDirect(capacity); for (int i = 0; i < capacity; ++i) { originalBuffer.put((byte) '*'); } originalBuffer.flip(); response.getWriteSetter().set(new ChannelListener() { private ByteBuffer buffer = originalBuffer.duplicate(); int count = 0; @Override public void handleEvent(final Channel channel) { do { try { int res = response.write(buffer); if (res == 0) { return; } } catch (IOException e) { exception = e; errorLatch.countDown(); } if(!buffer.hasRemaining()) { count++; buffer = originalBuffer.duplicate(); } } while (count < 1000); exchange.endExchange(); } }); response.wakeupWrites(); } }); final TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); try { HttpResponse result = client.execute(get); InputStream content = result.getEntity().getContent(); byte[] buffer = new byte[512]; int r = 0; while ((r = content.read(buffer)) > 0) { Thread.sleep(200); if (exception != null) { Assert.assertEquals(WriteTimeoutException.class, exception.getClass()); return; } } Assert.fail("Write did not time out"); } catch (IOException e) { if (errorLatch.await(5, TimeUnit.SECONDS)) { Assert.assertEquals(WriteTimeoutException.class, exception.getClass()); } else { Assert.fail("Write did not time out"); } } } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/000077500000000000000000000000001420065311100254755ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/AllowedMethodsTestCase.java000066400000000000000000000066271420065311100327220ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.IOException; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.Methods; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; /** * Tests that the allowed and disallowed method handlers work as expected * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class AllowedMethodsTestCase { @Test public void testAllowedMethods() throws IOException { TestHttpClient client = new TestHttpClient(); try { final AllowedMethodsHandler handler = new AllowedMethodsHandler(ResponseCodeHandler.HANDLE_200, Methods.POST); DefaultServer.setRootHandler(handler); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.METHOD_NOT_ALLOWED, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); post.setEntity(new StringEntity("foo")); result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } @Test public void testDisallowedMethods() throws IOException { TestHttpClient client = new TestHttpClient(); try { final DisallowedMethodsHandler handler = new DisallowedMethodsHandler(ResponseCodeHandler.HANDLE_200, Methods.GET); DefaultServer.setRootHandler(handler); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.METHOD_NOT_ALLOWED, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); post.setEntity(new StringEntity("foo")); result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/BadRequestTestCase.java000066400000000000000000000056271420065311100320450ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.IOException; import java.net.Socket; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.util.StatusCodes; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) @HttpOneOnly public class BadRequestTestCase { @BeforeClass public static void setup() { DefaultServer.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) { exchange.setStatusCode(StatusCodes.OK); } }); } /** * We send our request manually, as apache HTTP client does not support this. * * @throws java.io.IOException */ @Test public void testBadRequest() throws IOException { String request = "POST /\r HTTP/1.1\r\nTrailer:foo, bar\r\nTransfer-Encoding: chunked\r\n\r\n9\r\nabcdefghi\r\n0\r\nfoo: fooVal\r\n bar: barVal\r\n\r\n"; String response1 = "HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"; Socket s = new Socket(DefaultServer.getDefaultServerAddress().getAddress(), DefaultServer.getDefaultServerAddress().getPort()); try { s.getOutputStream().write(request.getBytes()); StringBuilder sb = new StringBuilder(); int read = 0; byte[] buf = new byte[100]; while (read < response1.length()) { int r = s.getInputStream().read(buf); if (r <= 0) break; if (r > 0) { read += r; sb.append(new String(buf, 0, r)); } } Assert.assertEquals(response1, sb.toString()); } catch (IOException expected) { //this can happen as well, as in some cases we may not have fully consumed the read side //before the connection is shutdown, namely when we are running in test.single } finally { s.close(); } } } BlockingReadTimeoutHandlerTestCase.java000066400000000000000000000107661420065311100351200ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Headers; import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.AbstractHttpEntity; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.channels.ReadTimeoutException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * * Tests blocking read timeout with a slow request * * @author Carter Kozak */ @RunWith(DefaultServer.class) @HttpOneOnly public class BlockingReadTimeoutHandlerTestCase { private static final OutputStream STUB_OUTPUT_STREAM = new OutputStream() { @Override public void write(byte[] var1, int var2, int var3) throws IOException { } @Override public void write(int b) throws IOException { } }; private volatile Exception exception; private static final CountDownLatch errorLatch = new CountDownLatch(1); @Test public void testReadTimeout() throws InterruptedException { DefaultServer.setRootHandler(BlockingReadTimeoutHandler.builder().nextHandler(new BlockingHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { try { IOUtils.copyLarge(exchange.getInputStream(), STUB_OUTPUT_STREAM); exchange.getOutputStream().write("COMPLETED".getBytes(StandardCharsets.UTF_8)); } catch (IOException e) { exception = e; errorLatch.countDown(); } } })).readTimeout(Duration.ofMillis(1)).build()); final TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL()); post.setEntity(new AbstractHttpEntity() { @Override public InputStream getContent() throws IOException, IllegalStateException { return null; } @Override public void writeTo(final OutputStream outstream) throws IOException { for (int i = 0; i < 5; ++i) { outstream.write('*'); outstream.flush(); try { Thread.sleep(200); } catch (InterruptedException e) { throw new RuntimeException(e); } } } @Override public boolean isStreaming() { return true; } @Override public boolean isRepeatable() { return false; } @Override public long getContentLength() { return 5; } }); post.addHeader(Headers.CONNECTION_STRING, "close"); try { client.execute(post); } catch (IOException e) { } if (errorLatch.await(5, TimeUnit.SECONDS)) { Assert.assertEquals(ReadTimeoutException.class, exception.getClass()); } else { Assert.fail("Read did not time out"); } } finally { client.getConnectionManager().shutdown(); } } } BlockingWriteTimeoutHandlerTestCase.java000066400000000000000000000075571420065311100353430ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.testutils.ProxyIgnore; import io.undertow.testutils.TestHttpClient; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.channels.WriteTimeoutException; import java.io.IOException; import java.io.InputStream; import java.time.Duration; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Tests blocking write timeout with a client that is slow to read the response * * @author Carter Kozak */ @RunWith(DefaultServer.class) @HttpOneOnly @ProxyIgnore public class BlockingWriteTimeoutHandlerTestCase { private volatile Exception exception; private static final CountDownLatch errorLatch = new CountDownLatch(1); @Test public void testWriteTimeout() throws InterruptedException { DefaultServer.setRootHandler(BlockingWriteTimeoutHandler.builder().nextHandler(new BlockingHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { final int capacity = 1 * 1024 * 1024; // 1mb final byte[] data = new byte[capacity]; for (int i = 0; i < capacity; ++i) { data[i] = (byte) '*'; } try { // Must write enough data that it's not buffered for (int i = 0; i < 20; i++) { exchange.getOutputStream().write(data); } } catch (IOException e) { exception = e; errorLatch.countDown(); } } })).writeTimeout(Duration.ofMillis(1)).build()); final TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); try { HttpResponse result = client.execute(get); Assert.assertFalse("The result entity is buffered", result.getEntity().isRepeatable()); InputStream content = result.getEntity().getContent(); byte[] buffer = new byte[512]; int r = 0; while ((r = content.read(buffer)) > 0) { Thread.sleep(200); if (exception != null) { Assert.assertEquals(WriteTimeoutException.class, exception.getClass()); return; } } Assert.fail("Write did not time out"); } catch (IOException e) { if (errorLatch.await(5, TimeUnit.SECONDS)) { Assert.assertEquals(WriteTimeoutException.class, exception.getClass()); } else { Assert.fail("Write did not time out"); } } } finally { client.getConnectionManager().shutdown(); } } } ChunkedRequestNotConsumedTestCase.java000066400000000000000000000111211420065311100350220ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.Random; import java.util.concurrent.TimeUnit; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.io.IoCallback; import io.undertow.io.Sender; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.SameThreadExecutor; import io.undertow.util.StatusCodes; /** * * See https://issues.jboss.org/browse/UNDERTOW-1011 * * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ChunkedRequestNotConsumedTestCase { private static final String MESSAGE = "My HTTP Request!"; @BeforeClass public static void setup() { DefaultServer.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws InterruptedException { exchange.setResponseContentLength("message".length()); exchange.getResponseSender().send("message", new IoCallback() { @Override public void onComplete(HttpServerExchange exchange, Sender sender) { exchange.dispatch(SameThreadExecutor.INSTANCE, new Runnable() { @Override public void run() { exchange.getIoThread().executeAfter(new Runnable() { @Override public void run() { exchange.endExchange(); } }, 300, TimeUnit.MILLISECONDS); } }); } @Override public void onException(HttpServerExchange exchange, Sender sender, IOException exception) { exchange.endExchange(); } }); } }); } @Test public void testChunkedRequestNotConsumed() throws IOException { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); TestHttpClient client = new TestHttpClient(); try { final Random random = new Random(); final int seed = random.nextInt(); System.out.print("Using Seed " + seed); random.setSeed(seed); for (int i = 0; i < 3; ++i) { post.setEntity(new StringEntity("") { @Override public long getContentLength() { return -1; } @Override public boolean isChunked() { return true; } @Override public void writeTo(OutputStream outstream) throws IOException { outstream.flush(); try { Thread.sleep(100); } catch (InterruptedException e) { } outstream.write(MESSAGE.getBytes(StandardCharsets.US_ASCII)); } }); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } } finally { client.getConnectionManager().shutdown(); } } } ChunkedRequestTrailersTestCase.java000066400000000000000000000150241420065311100343570ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.UndertowOptions; import io.undertow.server.HttpHandler; import io.undertow.server.ServerConnection; import io.undertow.server.protocol.http.HttpAttachments; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.AjpIgnore; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.HeaderMap; import io.undertow.util.HeaderValues; import io.undertow.util.StatusCodes; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.OptionMap; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) @AjpIgnore public class ChunkedRequestTrailersTestCase { private static volatile ServerConnection connection; private static OptionMap existing; @BeforeClass public static void setup() { final BlockingHandler blockingHandler = new BlockingHandler(); existing = DefaultServer.getUndertowOptions(); DefaultServer.setUndertowOptions(OptionMap.create(UndertowOptions.ALWAYS_SET_DATE, false)); DefaultServer.setRootHandler(blockingHandler); blockingHandler.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) { try { if (connection == null) { connection = exchange.getConnection(); } else if (!DefaultServer.isProxy() && connection != exchange.getConnection()) { exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); final OutputStream outputStream = exchange.getOutputStream(); outputStream.write("Connection not persistent".getBytes()); outputStream.close(); return; } final OutputStream outputStream = exchange.getOutputStream(); final InputStream inputStream = exchange.getInputStream(); String m = HttpClientUtils.readResponse(inputStream); Assert.assertEquals("abcdefghi", m); HeaderMap headers = exchange.getAttachment(HttpAttachments.REQUEST_TRAILERS); for (HeaderValues header : headers) { for (String val : header) { outputStream.write(header.getHeaderName().toString().getBytes()); outputStream.write(": ".getBytes()); outputStream.write(val.getBytes()); outputStream.write("\r\n".getBytes()); } } inputStream.close(); outputStream.close(); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e); } } }); } @AfterClass public static void cleanup() { DefaultServer.setUndertowOptions(existing); } /** * We send our request manually, as apache HTTP client does not support this. * * @throws IOException */ @Test public void testChunkedRequestsWithTrailers() throws IOException { connection = null; String request = "POST / HTTP/1.1\r\nHost: default\r\nTrailer:foo, bar\r\nTransfer-Encoding: chunked\r\n\r\n9\r\nabcdefghi\r\n0\r\nfoo: fooVal\r\n bar: barVal\r\n\r\n"; String response1 = "HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nContent-Length: 26\r\n\r\nfoo: fooVal\r\nbar: barVal\r\n"; //header order is not guaranteed, we really should be parsing this properly String response2 = "HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nContent-Length: 26\r\n\r\nfoo: fooVal\r\nbar: barVal\r\n"; //TODO: parse the response properly, or better yet ues a client that supports trailers Socket s = new Socket(DefaultServer.getDefaultServerAddress().getAddress(), DefaultServer.getDefaultServerAddress().getPort()); try { s.getOutputStream().write(request.getBytes()); StringBuilder sb = new StringBuilder(); int read = 0; byte[] buf = new byte[300]; while (read < response1.length()) { int r = s.getInputStream().read(buf); if (r <= 0) break; if (r > 0) { read += r; sb.append(new String(buf, 0, r)); } } String actual = sb.toString(); actual = actual.replaceAll("\r\nDate:.*",""); actual = actual.replaceAll("content-length","Content-Length"); try { //this is pretty yuck Assert.assertEquals(response1, actual); } catch (AssertionError e) { Assert.assertEquals(response2, actual); } s.getOutputStream().write(request.getBytes()); sb = new StringBuilder(); read = 0; while (read < response1.length()) { int r = s.getInputStream().read(buf); if (r <= 0) break; if (r > 0) { read += r; sb.append(new String(buf, 0, r)); } } actual = sb.toString(); actual = actual.replaceAll("\r\nDate:.*",""); actual = actual.replaceAll("content-length","Content-Length"); try { Assert.assertEquals(response1, actual); } catch (AssertionError e) { Assert.assertEquals(response2, actual); } } finally { s.close(); } } } ChunkedRequestTransferCodingTestCase.java000066400000000000000000000163001420065311100355000ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.UndertowOptions; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.ServerConnection; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpHeaders; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.OptionMap; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Random; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ChunkedRequestTransferCodingTestCase { private static final String MESSAGE = "My HTTP Request!"; private static volatile String message; private static volatile ServerConnection connection; @BeforeClass public static void setup() { final BlockingHandler blockingHandler = new BlockingHandler(); DefaultServer.setRootHandler(blockingHandler); blockingHandler.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) { try { if (connection == null) { connection = exchange.getConnection(); } else if (!DefaultServer.isAjp() && !DefaultServer.isProxy() && connection != exchange.getConnection()) { exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); final OutputStream outputStream = exchange.getOutputStream(); outputStream.write("Connection not persistent".getBytes()); outputStream.close(); return; } final OutputStream outputStream = exchange.getOutputStream(); final InputStream inputStream = exchange.getInputStream(); String m = HttpClientUtils.readResponse(inputStream); Assert.assertEquals(message.length(), m.length()); Assert.assertEquals(message, m); inputStream.close(); outputStream.close(); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e); } } }); } @Test public void testChunkedRequest() throws IOException { connection = null; HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); TestHttpClient client = new TestHttpClient(); try { generateMessage(1); post.setEntity(new StringEntity(message) { @Override public long getContentLength() { return -1; } }); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); final Random random = new Random(); final int seed = random.nextInt(); System.out.print("Using Seed " + seed); random.setSeed(seed); for (int i = 0; i < 10; ++i) { generateMessage(100 * i); post.setEntity(new StringEntity(message) { @Override public long getContentLength() { return -1; } @Override public boolean isChunked() { return true; } @Override public void writeTo(OutputStream outstream) throws IOException { int l = 0; int i = 0; while (i <= message.length()) { i += random.nextInt(1000); i = Math.min(i, message.length()); outstream.write(message.getBytes(), l, i - l); l = i; ++i; } } }); result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } } finally { client.getConnectionManager().shutdown(); } } @Test @Ignore("sometimes the client attempts to re-use the same connection after the failure, but the server has already closed it") public void testMaxRequestSizeChunkedRequest() throws IOException { connection = null; OptionMap existing = DefaultServer.getUndertowOptions(); HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); post.setHeader(HttpHeaders.CONNECTION, "close"); TestHttpClient client = new TestHttpClient(); try { generateMessage(1); post.setEntity(new StringEntity(message) { @Override public long getContentLength() { return -1; } }); DefaultServer.setUndertowOptions(OptionMap.create(UndertowOptions.MAX_ENTITY_SIZE, 3L)); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.INTERNAL_SERVER_ERROR, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); connection = null; DefaultServer.setUndertowOptions(OptionMap.create(UndertowOptions.MAX_ENTITY_SIZE, (long) message.length())); result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { DefaultServer.setUndertowOptions(existing); client.getConnectionManager().shutdown(); } } private static void generateMessage(int repetitions) { final StringBuilder builder = new StringBuilder(repetitions * MESSAGE.length()); for (int i = 0; i < repetitions; ++i) { builder.append(MESSAGE); } message = builder.toString(); } } ChunkedResponseTrailersTestCase.java000066400000000000000000000145761420065311100345400ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.ServerConnection; import io.undertow.server.protocol.http.HttpAttachments; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.HeaderMap; import io.undertow.util.HttpString; import io.undertow.util.StatusCodes; import io.undertow.util.StringWriteChannelListener; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpResponseInterceptor; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.io.ChunkedInputStream; import org.apache.http.protocol.HttpContext; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.concurrent.atomic.AtomicReference; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) @HttpOneOnly public class ChunkedResponseTrailersTestCase { private static final String MESSAGE = "My HTTP Request!"; private static volatile String message; private static volatile ServerConnection connection; @Before public void reset() { connection = null; } @BeforeClass public static void setup() { final BlockingHandler blockingHandler = new BlockingHandler(); DefaultServer.setRootHandler(blockingHandler); blockingHandler.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) { try { if (connection == null) { connection = exchange.getConnection(); } else if (!DefaultServer.isAjp() && !DefaultServer.isProxy() && connection != exchange.getConnection()) { final OutputStream outputStream = exchange.getOutputStream(); outputStream.write("Connection not persistent".getBytes()); outputStream.close(); return; } HeaderMap trailers = new HeaderMap(); exchange.putAttachment(HttpAttachments.RESPONSE_TRAILERS, trailers); trailers.put(HttpString.tryFromString("foo"), "fooVal"); trailers.put(HttpString.tryFromString("bar"), "barVal"); new StringWriteChannelListener(message).setup(exchange.getResponseChannel()); } catch (IOException e) { throw new RuntimeException(e); } } }); } @Test public void sendHttpRequest() throws Exception { Assume.assumeFalse(DefaultServer.isH2()); //this test will still run under h2-upgrade, but will fail HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); TestHttpClient client = new TestHttpClient(); final AtomicReference stream = new AtomicReference<>(); client.addResponseInterceptor(new HttpResponseInterceptor() { public void process(final HttpResponse response, final HttpContext context) throws IOException { HttpEntity entity = response.getEntity(); if (entity != null) { InputStream instream = entity.getContent(); if (instream instanceof ChunkedInputStream) { stream.set(((ChunkedInputStream) instream)); } } } }); try { generateMessage(1); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(message, HttpClientUtils.readResponse(result)); Header[] footers = stream.get().getFooters(); Assert.assertEquals(2, footers.length); for (final Header header : footers) { if (header.getName().equals("foo")) { Assert.assertEquals("fooVal", header.getValue()); } else if (header.getName().equals("bar")) { Assert.assertEquals("barVal", header.getValue()); } else { Assert.fail("Unknown header" + header); } } generateMessage(1000); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(message, HttpClientUtils.readResponse(result)); footers = stream.get().getFooters(); Assert.assertEquals(2, footers.length); for (final Header header : footers) { if (header.getName().equals("foo")) { Assert.assertEquals("fooVal", header.getValue()); } else if (header.getName().equals("bar")) { Assert.assertEquals("barVal", header.getValue()); } else { Assert.fail("Unknown header" + header); } } } finally { client.getConnectionManager().shutdown(); } } private static void generateMessage(int repetitions) { final StringBuilder builder = new StringBuilder(repetitions * MESSAGE.length()); for (int i = 0; i < repetitions; ++i) { builder.append(MESSAGE); } message = builder.toString(); } } ChunkedResponseTransferCodingTestCase.java000066400000000000000000000101261420065311100356460ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.ServerConnection; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import io.undertow.util.StringWriteChannelListener; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.io.OutputStream; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ChunkedResponseTransferCodingTestCase { private static final String MESSAGE = "My HTTP Request!"; private static volatile String message; private static volatile ServerConnection connection; @Before public void reset() { connection = null; } @BeforeClass public static void setup() { final BlockingHandler blockingHandler = new BlockingHandler(); DefaultServer.setRootHandler(blockingHandler); blockingHandler.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) { try { if(connection == null) { connection = exchange.getConnection(); } else if(!DefaultServer.isAjp() && !DefaultServer.isProxy() && connection != exchange.getConnection()){ final OutputStream outputStream = exchange.getOutputStream(); outputStream.write("Connection not persistent".getBytes()); outputStream.close(); return; } new StringWriteChannelListener(message).setup(exchange.getResponseChannel()); } catch (IOException e) { throw new RuntimeException(e); } } }); } @Test public void sendHttpRequest() throws IOException { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); TestHttpClient client = new TestHttpClient(); try { generateMessage(0); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(message, HttpClientUtils.readResponse(result)); generateMessage(1); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(message, HttpClientUtils.readResponse(result)); generateMessage(1000); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(message, HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } private static void generateMessage(int repetitions) { final StringBuilder builder = new StringBuilder(repetitions * MESSAGE.length()); for (int i = 0; i < repetitions; ++i) { builder.append(MESSAGE); } message = builder.toString(); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/DateHandlerTestCase.java000066400000000000000000000054151420065311100321540ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.IOException; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.DateUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class DateHandlerTestCase { @BeforeClass public static void setup() { DefaultServer.setRootHandler(new DateHandler(ResponseCodeHandler.HANDLE_200)); } @Test public void testDateHandler() throws IOException, InterruptedException { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); TestHttpClient client = new TestHttpClient(); try { HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header date = result.getHeaders("Date")[0]; final long firstDate = DateUtils.parseDate(date.getValue()).getTime(); Assert.assertTrue((firstDate + 3000) > System.currentTimeMillis()); Assert.assertTrue(System.currentTimeMillis() >= firstDate); HttpClientUtils.readResponse(result); Thread.sleep(1500); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); date = result.getHeaders("Date")[0]; final long secondDate = DateUtils.parseDate(date.getValue()).getTime(); Assert.assertTrue((secondDate + 2000) > System.currentTimeMillis()); Assert.assertTrue(System.currentTimeMillis() >= secondDate); Assert.assertTrue(secondDate > firstDate); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/ExceptionHandlerTestCase.java000066400000000000000000000177401420065311100332410ustar00rootroot00000000000000package io.undertow.server.handlers; import io.undertow.Handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import java.io.IOException; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(DefaultServer.class) public class ExceptionHandlerTestCase { @Test public void testExceptionMappers() throws IOException { HttpHandler pathHandler = Handlers.path() .addExactPath("/", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("expected"); } }) .addExactPath("/exceptionParent", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { throw new ParentException(); } }) .addExactPath("/exceptionChild", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { throw new ChildException(); } }) .addExactPath("/exceptionAnotherChild", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { throw new AnotherChildException(); } }) .addExactPath("/illegalArgumentException", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { throw new IllegalArgumentException(); } }); HttpHandler exceptionHandler = Handlers.exceptionHandler(pathHandler) .addExceptionHandler(ChildException.class, new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("child exception handled"); } }) .addExceptionHandler(ParentException.class, new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("parent exception handled"); } }) .addExceptionHandler(Throwable.class, new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("catch all throwables"); } }); DefaultServer.setRootHandler(exceptionHandler); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("expected", HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/exceptionParent"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("parent exception handled", HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/exceptionChild"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("child exception handled", HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/exceptionAnotherChild"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("parent exception handled", HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/illegalArgumentException"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("catch all throwables", HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } @Test public void testReThrowUnmatchedException() throws IOException { HttpHandler pathHandler = Handlers.path() .addExactPath("/", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { throw new IllegalArgumentException(); } }); // intentionally not adding any exception handlers final HttpHandler exceptionHandler = Handlers.exceptionHandler(pathHandler); DefaultServer.setRootHandler(exceptionHandler); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.INTERNAL_SERVER_ERROR, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } @Test public void testAttachException() throws IOException { HttpHandler pathHandler = Handlers.path() .addExactPath("/", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { throw new IllegalArgumentException(); } }); final HttpHandler exceptionHandler = Handlers.exceptionHandler(pathHandler) .addExceptionHandler(IllegalArgumentException.class, new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("exception handled"); } }); DefaultServer.setRootHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { Throwable throwable = exchange.getAttachment(ExceptionHandler.THROWABLE); Assert.assertNull(throwable); exceptionHandler.handleRequest(exchange); throwable = exchange.getAttachment(ExceptionHandler.THROWABLE); Assert.assertTrue(throwable instanceof IllegalArgumentException); } }); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("exception handled", HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } private static class ParentException extends Exception {} private static class ChildException extends ParentException {} private static class AnotherChildException extends ParentException {} } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/FixedLengthRequestTestCase.java000066400000000000000000000136121420065311100335510ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.UndertowOptions; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.ServerConnection; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import org.apache.http.HttpHeaders; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.OptionMap; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class FixedLengthRequestTestCase { private static final String MESSAGE = "My HTTP Request!"; private static volatile String message; private static volatile ServerConnection connection; @BeforeClass public static void setup() { final BlockingHandler blockingHandler = new BlockingHandler(); DefaultServer.setRootHandler(blockingHandler); blockingHandler.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) { try { if (connection == null) { connection = exchange.getConnection(); } else if (!DefaultServer.isAjp() && !DefaultServer.isProxy() && connection != exchange.getConnection()) { exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); final OutputStream outputStream = exchange.getOutputStream(); outputStream.write("Connection not persistent".getBytes()); outputStream.close(); return; } final OutputStream outputStream = exchange.getOutputStream(); final InputStream inputStream = exchange.getInputStream(); String m = HttpClientUtils.readResponse(inputStream); Assert.assertEquals(message, m); inputStream.close(); outputStream.close(); } catch (IOException e) { exchange.getResponseHeaders().put(Headers.CONNECTION, "close"); exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); throw new RuntimeException(e); } } }); } @Test public void testFixedLengthRequest() throws IOException { connection = null; HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); TestHttpClient client = new TestHttpClient(); try { generateMessage(1); post.setEntity(new StringEntity(message)); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); generateMessage(1000); post.setEntity(new StringEntity(message)); result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } @Test @Ignore("sometimes the client attempts to re-use the same connection after the failure, but the server has already closed it") public void testMaxRequestSizeFixedLengthRequest() throws IOException { connection = null; OptionMap existing = DefaultServer.getUndertowOptions(); HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); post.setHeader(HttpHeaders.CONNECTION, "close"); TestHttpClient client = new TestHttpClient(); try { generateMessage(1); post.setEntity(new StringEntity(message)); DefaultServer.setUndertowOptions(OptionMap.create(UndertowOptions.MAX_ENTITY_SIZE, 3L)); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.INTERNAL_SERVER_ERROR, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); connection = null; DefaultServer.setUndertowOptions(OptionMap.create(UndertowOptions.MAX_ENTITY_SIZE, (long) message.length())); result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { DefaultServer.setUndertowOptions(existing); client.getConnectionManager().shutdown(); } } private static void generateMessage(int repetitions) { final StringBuilder builder = new StringBuilder(repetitions * MESSAGE.length()); for (int i = 0; i < repetitions; ++i) { builder.append(MESSAGE); } message = builder.toString(); } } FixedLengthResponseTestCase.java000066400000000000000000000072341420065311100336430ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.io.Sender; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.ServerConnection; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; /** * Tests that persistent connections work with fixed length responses * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class FixedLengthResponseTestCase { private static final String MESSAGE = "My HTTP Request!"; private static volatile String message; private static volatile ServerConnection connection; @Before public void reset() { connection = null; } @BeforeClass public static void setup() { DefaultServer.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { if (connection == null) { connection = exchange.getConnection(); } else if (!DefaultServer.isAjp() && !DefaultServer.isProxy() && connection != exchange.getConnection()) { Sender sender = exchange.getResponseSender(); sender.send("Connection not persistent"); return; } exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, message.length() + ""); final Sender sender = exchange.getResponseSender(); sender.send(message); } }); } @Test public void sendHttpRequest() throws IOException { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); TestHttpClient client = new TestHttpClient(); try { generateMessage(1); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(message, HttpClientUtils.readResponse(result)); generateMessage(1000); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(message, HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } private static void generateMessage(int repetitions) { final StringBuilder builder = new StringBuilder(repetitions * MESSAGE.length()); for (int i = 0; i < repetitions; ++i) { builder.append(MESSAGE); } message = builder.toString(); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/ForwardedHandlerTestCase.java000066400000000000000000000201411420065311100332050ustar00rootroot00000000000000package io.undertow.server.handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.ProxyIgnore; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; import java.util.Objects; import static io.undertow.server.handlers.ForwardedHandler.parseAddress; import static io.undertow.server.handlers.ForwardedHandler.parseHeader; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) @ProxyIgnore public class ForwardedHandlerTestCase { @BeforeClass public static void setup() { final boolean DEFAULT_CHANGE_LOCAL_ADDR_PORT = Boolean.TRUE; DefaultServer.setRootHandler(new ForwardedHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send( exchange.getRequestScheme() + "|" + exchange.getHostAndPort() + "|" + toJreNormalizedString(exchange.getDestinationAddress()) + "|" + toJreNormalizedString(exchange.getSourceAddress())); } }, DEFAULT_CHANGE_LOCAL_ADDR_PORT)); } private static String toJreNormalizedString(InetSocketAddress address) { // https://mail.openjdk.java.net/pipermail/net-dev/2019-June/012741.html // https://bugs.openjdk.java.net/browse/JDK-8225499 // Java 14 introduced a new component to the toString value to disambiguate ipv6 values return Objects.toString(address) .replace("/", "") .replace("[", "") .replace("]", ""); } @Test public void testHeaderParsing() { Map results = new HashMap<>(); parseHeader("For=\"[2001:db8:cafe::17]:4711\"", results); Assert.assertEquals("[2001:db8:cafe::17]:4711", results.get(ForwardedHandler.Token.FOR)); results.clear(); parseHeader("for=192.0.2.60;proto=http;by=203.0.113.43", results); Assert.assertEquals("192.0.2.60", results.get(ForwardedHandler.Token.FOR)); Assert.assertEquals("http", results.get(ForwardedHandler.Token.PROTO)); Assert.assertEquals("203.0.113.43", results.get(ForwardedHandler.Token.BY)); results.clear(); parseHeader("for=192.0.2.43, for=198.51.100.17", results); Assert.assertEquals("192.0.2.43", results.get(ForwardedHandler.Token.FOR)); results.clear(); parseHeader("for=192.0.2.43, for=198.51.100.17;by=\"foo\"", results); Assert.assertEquals("192.0.2.43", results.get(ForwardedHandler.Token.FOR)); Assert.assertEquals("foo", results.get(ForwardedHandler.Token.BY)); results.clear(); } @Test public void testAddressParsing() throws UnknownHostException { Assert.assertEquals(null, parseAddress("unknown")); Assert.assertEquals(null, parseAddress("_foo")); Assert.assertEquals(new InetSocketAddress(InetAddress.getByAddress(new byte[]{(byte) 192, (byte) 168, 1, 1}), 0), parseAddress("192.168.1.1")); Assert.assertEquals(new InetSocketAddress(InetAddress.getByAddress(new byte[]{(byte) 192, (byte) 168, 1, 1}), 8080), parseAddress("192.168.1.1:8080")); Assert.assertEquals(new InetSocketAddress(InetAddress.getByAddress(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}), 0), parseAddress("[::1]")); Assert.assertEquals(new InetSocketAddress(InetAddress.getByAddress(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}), 8080), parseAddress("[::1]:8080")); } @Test public void testForwardedHandler() throws IOException { String[] res = run(); Assert.assertEquals("http", res[0]); Assert.assertEquals( DefaultServer.getHostAddress() + ":" + DefaultServer.getHostPort(), res[1]); Assert.assertEquals( "/" + InetAddress.getByName(DefaultServer.getHostAddress()).getHostAddress() + ":" + DefaultServer.getHostPort(), res[2]); res = run("host=google.com"); Assert.assertEquals("http", res[0]); Assert.assertEquals( "google.com", res[1]); Assert.assertEquals( "google.com:80", res[2]); res = run("host=google.com, proto=https"); Assert.assertEquals("https", res[0]); Assert.assertEquals( "google.com", res[1]); Assert.assertEquals( "google.com:80", res[2]); res = run("for=8.8.8.8:3545"); Assert.assertEquals("http", res[0]); Assert.assertEquals( DefaultServer.getHostAddress() + ":" + DefaultServer.getHostPort(), res[1]); Assert.assertEquals( "/" + InetAddress.getByName(DefaultServer.getHostAddress()).getHostAddress() + ":" + DefaultServer.getHostPort(), res[2]); Assert.assertEquals( "/8.8.8.8:3545", res[3]); res = run("for=8.8.8.8:3545, for=9.9.9.9:2343"); Assert.assertEquals("http", res[0]); Assert.assertEquals( DefaultServer.getHostAddress() + ":" + DefaultServer.getHostPort(), res[1]); Assert.assertEquals( "/" + InetAddress.getByName(DefaultServer.getHostAddress()).getHostAddress() + ":" + DefaultServer.getHostPort(), res[2]); Assert.assertEquals( "/8.8.8.8:3545", res[3]); res = run("for=[::1]:3545, for=9.9.9.9:2343"); Assert.assertEquals("http", res[0]); Assert.assertEquals( DefaultServer.getHostAddress() + ":" + DefaultServer.getHostPort(), res[1]); Assert.assertEquals( "/" + InetAddress.getByName(DefaultServer.getHostAddress()).getHostAddress() + ":" + DefaultServer.getHostPort(), res[2]); Assert.assertEquals( "/0:0:0:0:0:0:0:1:3545", res[3]); res = run("for=[::1]:_foo, for=9.9.9.9:2343"); Assert.assertEquals("http", res[0]); Assert.assertEquals( DefaultServer.getHostAddress() + ":" + DefaultServer.getHostPort(), res[1]); Assert.assertEquals( "/" + InetAddress.getByName(DefaultServer.getHostAddress()).getHostAddress() + ":" + DefaultServer.getHostPort(), res[2]); Assert.assertEquals( "/0:0:0:0:0:0:0:1:0", res[3]); res = run("for=[::1], for=9.9.9.9:2343"); Assert.assertEquals("http", res[0]); Assert.assertEquals( DefaultServer.getHostAddress() + ":" + DefaultServer.getHostPort(), res[1]); Assert.assertEquals( "/" + InetAddress.getByName(DefaultServer.getHostAddress()).getHostAddress() + ":" + DefaultServer.getHostPort(), res[2]); Assert.assertEquals( "/0:0:0:0:0:0:0:1:0", res[3]); res = run("by=[::1]; for=9.9.9.9:2343"); Assert.assertEquals("http", res[0]); Assert.assertEquals( DefaultServer.getHostAddress() + ":" + DefaultServer.getHostPort(), res[1]); Assert.assertEquals( "/0:0:0:0:0:0:0:1:0", res[2]); Assert.assertEquals( "/9.9.9.9:2343", res[3]); res = run("by=[::1]; for=9.9.9.9:2343; host=foo.com"); Assert.assertEquals("http", res[0]); Assert.assertEquals( "foo.com", res[1]); Assert.assertEquals( "foo.com:80", res[2]); Assert.assertEquals( "/9.9.9.9:2343", res[3]); } private static String[] run(String ... headers) throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); for(String i : headers) { get.addHeader(Headers.FORWARDED_STRING, i); } HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); return HttpClientUtils.readResponse(result).split("\\|"); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/GracefulShutdownTestCase.java000066400000000000000000000150141420065311100332610ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.Handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpGet; import org.junit.After; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class GracefulShutdownTestCase { static final AtomicReference latch1 = new AtomicReference<>(); static final AtomicReference latch2 = new AtomicReference<>(); private static GracefulShutdownHandler shutdown; @BeforeClass public static void setup() { shutdown = Handlers.gracefulShutdown(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { final CountDownLatch countDownLatch = latch2.get(); final CountDownLatch latch = latch1.get(); if (latch != null) { latch.countDown(); } if (countDownLatch != null) { countDownLatch.await(); } } }); DefaultServer.setRootHandler(shutdown); } @After public void after() { latch1.set(null); latch2.set(null); shutdown.start(); } @Test public void simpleGracefulShutdownTestCase() throws IOException, InterruptedException { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); TestHttpClient client = new TestHttpClient(); try { HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); shutdown.shutdown(); result = client.execute(get); Assert.assertEquals(StatusCodes.SERVICE_UNAVAILABLE, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); shutdown.start(); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); CountDownLatch latch = new CountDownLatch(1); latch2.set(latch); latch1.set(new CountDownLatch(1)); Thread t = new Thread(new RequestTask()); t.start(); latch1.get().await(); shutdown.shutdown(); Assert.assertFalse(shutdown.awaitShutdown(10)); latch.countDown(); Assert.assertTrue(shutdown.awaitShutdown(10000)); } finally { client.getConnectionManager().shutdown(); } } @Test public void gracefulShutdownListenerTestCase() throws IOException, InterruptedException { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); TestHttpClient client = new TestHttpClient(); try { HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); shutdown.shutdown(); result = client.execute(get); Assert.assertEquals(StatusCodes.SERVICE_UNAVAILABLE, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); shutdown.start(); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); CountDownLatch latch = new CountDownLatch(1); latch2.set(latch); latch1.set(new CountDownLatch(1)); Thread t = new Thread(new RequestTask()); t.start(); latch1.get().await(); ShutdownListener listener = new ShutdownListener(); shutdown.shutdown(); shutdown.addShutdownListener(listener); Assert.assertFalse(listener.invoked); latch.countDown(); long end = System.currentTimeMillis() + 5000; while (!listener.invoked && System.currentTimeMillis() < end) { Thread.sleep(10); } Assert.assertTrue(listener.invoked); } finally { client.getConnectionManager().shutdown(); } } private class ShutdownListener implements GracefulShutdownHandler.ShutdownListener { private volatile boolean invoked = false; @Override public synchronized void shutdown(boolean successful) { invoked = true; } } private final class RequestTask implements Runnable { @Override public void run() { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); TestHttpClient client = new TestHttpClient(); try { HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { client.getConnectionManager().shutdown(); } } } } HeadBlockingExchangeTestCase.java000066400000000000000000000043241420065311100336750ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2022 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpHead; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; /** * Test that {@code HEAD} requests can be used with a blocking exchange, setting response * content-length without unnecessarily writing bytes. * * @author Carter Kozak */ @RunWith(DefaultServer.class) public class HeadBlockingExchangeTestCase { @BeforeClass public static void setup() { DefaultServer.setRootHandler(new BlockingHandler( exchange -> exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, "" + 100))); } @Test public void sendHttpHead() throws IOException { HttpHead head = new HttpHead(DefaultServer.getDefaultServerURL()); TestHttpClient client = new TestHttpClient(); try { HttpResponse result = client.execute(head); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("", HttpClientUtils.readResponse(result)); Assert.assertEquals("100", result.getFirstHeader("Content-Length").getValue()); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/HeadTestCase.java000066400000000000000000000102721420065311100306370ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.io.Sender; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.ServerConnection; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; /** * Tests that head requests will never send a response body, even if a handler author has not * considered HEAD methods when implementing the handler. * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class HeadTestCase { private static final String MESSAGE = "My HTTP Request!"; private static volatile String message; private static volatile ServerConnection connection; @BeforeClass public static void setup() { DefaultServer.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { if (connection == null) { connection = exchange.getConnection(); } else if (!DefaultServer.isAjp() && !DefaultServer.isProxy() && connection != exchange.getConnection()) { Sender sender = exchange.getResponseSender(); sender.send("Connection not persistent"); return; } exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, message.length() + ""); final Sender sender = exchange.getResponseSender(); sender.send(message); } }); } @Test public void sendHttpHead() throws IOException { connection = null; HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); HttpHead head = new HttpHead(DefaultServer.getDefaultServerURL() + "/path"); TestHttpClient client = new TestHttpClient(); try { generateMessage(1); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(message, HttpClientUtils.readResponse(result)); result = client.execute(head); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("", HttpClientUtils.readResponse(result)); generateMessage(1000); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(message, HttpClientUtils.readResponse(result)); result = client.execute(head); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("", HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } private static void generateMessage(int repetitions) { final StringBuilder builder = new StringBuilder(repetitions * MESSAGE.length()); for (int i = 0; i < repetitions; ++i) { builder.append(MESSAGE); } message = builder.toString(); } } HttpContinueAcceptingHandlerTestCase.java000066400000000000000000000115131420065311100354560ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import io.undertow.predicate.Predicate; import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpParams; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class HttpContinueAcceptingHandlerTestCase { private static volatile boolean accept = false; @BeforeClass public static void setup() { final BlockingHandler blockingHandler = new BlockingHandler(); final HttpContinueAcceptingHandler handler = new HttpContinueAcceptingHandler(blockingHandler, new Predicate() { @Override public boolean resolve(HttpServerExchange value) { return accept; } }); DefaultServer.setRootHandler(handler); blockingHandler.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) { try { byte[] buffer = new byte[1024]; final ByteArrayOutputStream b = new ByteArrayOutputStream(); int r = 0; final OutputStream outputStream = exchange.getOutputStream(); final InputStream inputStream = exchange.getInputStream(); while ((r = inputStream.read(buffer)) > 0) { b.write(buffer, 0, r); } outputStream.write(b.toByteArray()); outputStream.close(); } catch (IOException e) { throw new RuntimeException(e); } } }); } @Before public void before() { Assume.assumeFalse(DefaultServer.isAjp()); Assume.assumeFalse(DefaultServer.isH2upgrade()); } @Test public void testHttpContinueRejected() throws IOException { accept = false; String message = "My HTTP Request!"; HttpParams httpParams = new BasicHttpParams(); httpParams.setParameter("http.protocol.wait-for-continue", Integer.MAX_VALUE); TestHttpClient client = new TestHttpClient(); client.setParams(httpParams); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); post.addHeader("Expect", "100-continue"); post.setEntity(new StringEntity(message)); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.EXPECTATION_FAILED, result.getStatusLine().getStatusCode()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testHttpContinueAccepted() throws IOException { accept = true; String message = "My HTTP Request!"; HttpParams httpParams = new BasicHttpParams(); httpParams.setParameter("http.protocol.wait-for-continue", Integer.MAX_VALUE); TestHttpClient client = new TestHttpClient(); client.setParams(httpParams); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); post.addHeader("Expect", "100-continue"); post.setEntity(new StringEntity(message)); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(message, HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } } HttpContinueConduitWrappingHandlerBufferLeakTestCase.java000066400000000000000000000114151420065311100406260ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.IOException; import java.net.Socket; import java.nio.charset.StandardCharsets; import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.util.StatusCodes; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class HttpContinueConduitWrappingHandlerBufferLeakTestCase { static Socket persistentSocket; @BeforeClass public static void setup() { final BlockingHandler blockingHandler = new BlockingHandler(); final HttpContinueReadHandler handler = new HttpContinueReadHandler(blockingHandler); DefaultServer.setRootHandler(handler); blockingHandler.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) { try { if (exchange.getQueryParameters().containsKey("reject")) { exchange.getRequestChannel(); exchange.setStatusCode(StatusCodes.EXPECTATION_FAILED); exchange.getOutputStream().close(); } } catch (IOException e) { throw new RuntimeException(e); } } }); } @Before public void before() { Assume.assumeFalse(DefaultServer.isAjp()); } @Test public void testHttpContinueRejectedBodySentAnywayNoBufferLeak() throws IOException { persistentSocket = new Socket(DefaultServer.getHostAddress(), DefaultServer.getHostPort()); String message = "POST /path?reject=true HTTP/1.1\r\n" + "Expect: 100-continue\r\n" + "Content-Length: 16\r\n" + "Content-Type: text/plain; charset=ISO-8859-1\r\n" + "Host: localhost:7777\r\n" + "Connection: Keep-Alive\r\n\r\nMy HTTP Request!"; persistentSocket.getOutputStream().write(message.getBytes(StandardCharsets.UTF_8)); persistentSocket.getOutputStream().flush(); persistentSocket.getInputStream().read(); } @Test public void testHttpContinueBodySentAnywayNoLeak() throws IOException { persistentSocket = new Socket(DefaultServer.getHostAddress(), DefaultServer.getHostPort()); String message = "POST /path HTTP/1.1\r\n" + "Expect: 100-continue\r\n" + "Content-Length: 16\r\n" + "Content-Type: text/plain; charset=ISO-8859-1\r\n" + "Host: localhost:7777\r\n" + "Connection: Keep-Alive\r\n\r\nMy HTTP Request!"; persistentSocket.getOutputStream().write(message.getBytes(StandardCharsets.UTF_8)); persistentSocket.getOutputStream().flush(); persistentSocket.getInputStream().read(); } @Test public void testEmptySSLHttpContinueNoLeak() throws IOException { DefaultServer.startSSLServer(); try { final Socket sslSocket = DefaultServer.getClientSSLContext().getSocketFactory().createSocket( new Socket(DefaultServer.getHostAddress("default"), DefaultServer.getHostSSLPort("default")), DefaultServer.getHostAddress("default"), DefaultServer.getHostSSLPort("default"), true); String header = DefaultServer.isH2()?"POST /path HTTP/2.0":"POST /path HTTP/1.1"; String message = header + "Expect: 100-continue\r\n" + "Content-Type: text/plain; charset=ISO-8859-1\r\n" + "Host: localhost:7778\r\n" + "Connection: Keep-Alive\r\n\r\n"; sslSocket.getOutputStream().write(message.getBytes(StandardCharsets.UTF_8)); sslSocket.getOutputStream().flush(); sslSocket.getInputStream().read(); } finally { DefaultServer.stopSSLServer(); } } } HttpContinueConduitWrappingHandlerTestCase.java000066400000000000000000000135161420065311100367030ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import io.undertow.server.protocol.http.HttpContinue; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpParams; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class HttpContinueConduitWrappingHandlerTestCase { private static volatile boolean accept = false; @BeforeClass public static void setup() { final BlockingHandler blockingHandler = new BlockingHandler(); final HttpContinueReadHandler handler = new HttpContinueReadHandler(blockingHandler); DefaultServer.setRootHandler(handler); blockingHandler.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) { try { if(!accept) { HttpContinue.rejectExchange(exchange); return; } byte[] buffer = new byte[1024]; final ByteArrayOutputStream b = new ByteArrayOutputStream(); int r = 0; final OutputStream outputStream = exchange.getOutputStream(); final InputStream inputStream = exchange.getInputStream(); while ((r = inputStream.read(buffer)) > 0) { b.write(buffer, 0, r); } outputStream.write(b.toByteArray()); outputStream.close(); } catch (IOException e) { throw new RuntimeException(e); } } }); } @Before public void before() { Assume.assumeFalse(DefaultServer.isAjp()); } @Test public void testHttpContinueRejected() throws IOException { accept = false; String message = "My HTTP Request!"; HttpParams httpParams = new BasicHttpParams(); httpParams.setParameter("http.protocol.wait-for-continue", Integer.MAX_VALUE); TestHttpClient client = new TestHttpClient(); client.setParams(httpParams); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); post.addHeader("Expect", "100-continue"); post.setEntity(new StringEntity(message)); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.EXPECTATION_FAILED, result.getStatusLine().getStatusCode()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testHttpContinueAccepted() throws IOException { accept = true; String message = "My HTTP Request!"; HttpParams httpParams = new BasicHttpParams(); httpParams.setParameter("http.protocol.wait-for-continue", Integer.MAX_VALUE); TestHttpClient client = new TestHttpClient(); client.setParams(httpParams); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); post.addHeader("Expect", "100-continue"); post.setEntity(new StringEntity(message)); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(message, HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } //UNDERTOW-162 @Test public void testHttpContinueAcceptedWithChunkedRequest() throws IOException { accept = true; String message = "My HTTP Request!"; HttpParams httpParams = new BasicHttpParams(); httpParams.setParameter("http.protocol.wait-for-continue", Integer.MAX_VALUE); TestHttpClient client = new TestHttpClient(); client.setParams(httpParams); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); post.addHeader("Expect", "100-continue"); post.setEntity(new StringEntity(message){ @Override public long getContentLength() { return -1; } }); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(message, HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } } HttpTunnelingViaConnectTestCase.java000066400000000000000000000077101420065311100344770ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.Undertow; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.ProxyIgnore; import org.apache.http.HttpHost; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.impl.client.ProxyClient; import org.apache.http.protocol.HTTP; import org.junit.Assert; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Writer; import java.net.Socket; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) @ProxyIgnore public class HttpTunnelingViaConnectTestCase { private static Undertow server; @BeforeClass public static void setup() { DefaultServer.setRootHandler(new SetHeaderHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("hi all"); } }, "MyHeader", "MyValue")); server = Undertow.builder().addHttpListener(DefaultServer.getHostPort("default") + 1, DefaultServer.getHostAddress("default")) .setHandler(new ConnectHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.setStatusCode(500); } })) .build(); server.start(); } @AfterClass public static void stop() { server.stop(); server = null; // sleep 1 s to prevent BindException (Address already in use) when running the CI try { Thread.sleep(1000); } catch (InterruptedException ignore) {} } @Test public void testConnectViaProxy() throws Exception { final HttpHost proxy = new HttpHost(DefaultServer.getHostAddress("default"), DefaultServer.getHostPort("default") + 1, "http"); final HttpHost target = new HttpHost(DefaultServer.getHostAddress("default"), DefaultServer.getHostPort("default"), "http"); ProxyClient proxyClient = new ProxyClient(); Socket socket = proxyClient.tunnel(proxy, target, new UsernamePasswordCredentials("a", "b")); try { Writer out = new OutputStreamWriter(socket.getOutputStream(), HTTP.DEF_CONTENT_CHARSET); out.write("GET / HTTP/1.1\r\n"); out.write("Host: " + target.toHostString() + "\r\n"); out.write("Connection: close\r\n"); out.write("\r\n"); out.flush(); BufferedReader in = new BufferedReader( new InputStreamReader(socket.getInputStream(), HTTP.DEF_CONTENT_CHARSET)); String line = null; boolean found = false; while ((line = in.readLine()) != null) { System.out.println(line); if(line.equals("MyHeader: MyValue")) { found = true; } } Assert.assertTrue(found); } finally { socket.close(); } } } IPAddressAccessControlHandlerUnitTestCase.java000066400000000000000000000152601420065311100363600ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.testutils.category.UnitTest; import io.undertow.server.handlers.builder.HandlerParser; import io.undertow.util.StatusCodes; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import java.net.InetAddress; import java.net.UnknownHostException; /** * Unit tests for peer security handler * * @author Stuart Douglas */ @Category(UnitTest.class) public class IPAddressAccessControlHandlerUnitTestCase { @Test public void testIPv4ExactMatch() throws UnknownHostException { IPAddressAccessControlHandler handler = new IPAddressAccessControlHandler() .setDefaultAllow(false) .addAllow("127.0.0.1"); Assert.assertTrue(handler.isAllowed(InetAddress.getByName("127.0.0.1"))); Assert.assertFalse(handler.isAllowed(InetAddress.getByName("127.0.0.2"))); } @Test public void testIPv6ExactMatch() throws UnknownHostException { IPAddressAccessControlHandler handler = new IPAddressAccessControlHandler() .setDefaultAllow(false) .addAllow("FE45::AAA:FFFF:45"); Assert.assertTrue(handler.isAllowed(InetAddress.getByName("FE45:0:0:0:0:AAA:FFFF:45"))); Assert.assertFalse(handler.isAllowed(InetAddress.getByName("127.0.0.2"))); Assert.assertFalse(handler.isAllowed(InetAddress.getByName("FE45:0:0:0:0:AAA:FFFF:46"))); } @Test public void testIPv4WildcardMatch() throws UnknownHostException { IPAddressAccessControlHandler handler = new IPAddressAccessControlHandler() .setDefaultAllow(true) .addAllow("127.0.0.1") .addDeny("127.0.*.*"); Assert.assertTrue(handler.isAllowed(InetAddress.getByName("127.0.0.1"))); Assert.assertFalse(handler.isAllowed(InetAddress.getByName("127.0.0.2"))); Assert.assertTrue(handler.isAllowed(InetAddress.getByName("127.1.0.2"))); } @Test public void testIPv6PrefixMatch() throws UnknownHostException { IPAddressAccessControlHandler handler = new IPAddressAccessControlHandler() .setDefaultAllow(true) .addAllow("FE45::AAA:FFFF:45") .addDeny("FE45:0:0:0:0:AAA:FFFF:*"); Assert.assertTrue(handler.isAllowed(InetAddress.getByName("FE45:0:0:0:0:AAA:FFFF:45"))); Assert.assertTrue(handler.isAllowed(InetAddress.getByName("127.0.0.2"))); Assert.assertFalse(handler.isAllowed(InetAddress.getByName("FE45:0:0:0:0:AAA:FFFF:46"))); Assert.assertTrue(handler.isAllowed(InetAddress.getByName("FE45:0:0:0:0:AAA:FFFb:46"))); } @Test public void testIPv4SlashMatch() throws UnknownHostException { IPAddressAccessControlHandler handler = new IPAddressAccessControlHandler() .setDefaultAllow(true) .addAllow("127.0.0.1") .addAllow("127.0.0.48/30") .addDeny("127.0.0.0/16"); Assert.assertTrue(handler.isAllowed(InetAddress.getByName("127.0.0.1"))); Assert.assertFalse(handler.isAllowed(InetAddress.getByName("127.0.0.2"))); Assert.assertFalse(handler.isAllowed(InetAddress.getByName("127.0.1.1"))); Assert.assertTrue(handler.isAllowed(InetAddress.getByName("127.1.0.2"))); Assert.assertFalse(handler.isAllowed(InetAddress.getByName("127.0.0.47"))); Assert.assertTrue(handler.isAllowed(InetAddress.getByName("127.0.0.48"))); Assert.assertTrue(handler.isAllowed(InetAddress.getByName("127.0.0.49"))); Assert.assertTrue(handler.isAllowed(InetAddress.getByName("127.0.0.50"))); Assert.assertTrue(handler.isAllowed(InetAddress.getByName("127.0.0.51"))); Assert.assertFalse(handler.isAllowed(InetAddress.getByName("127.0.0.52"))); } @Test public void testIPv6SlashMatch() throws UnknownHostException { IPAddressAccessControlHandler handler = new IPAddressAccessControlHandler() .setDefaultAllow(true) .addAllow("FE45::AAA:FFFF:45") .addAllow("FE45:0:0:0:0:AAA:FFFF:01F4/127") .addDeny("FE45:0:0:0:0:AAA:FFFF:0/112"); runIpv6SlashMAtchTest(handler); } @Test public void testParsedHandler() throws UnknownHostException { IPAddressAccessControlHandler handler = (IPAddressAccessControlHandler) HandlerParser.parse("ip-access-control[default-allow=true, acl={'FE45:0:0:0:0:AAA:FFFF:45 allow', 'FE45:0:0:0:0:AAA:FFFF:01F4/127 allow', 'FE45:00:00:000:0:AAA:FFFF:0/112 deny'}]", getClass().getClassLoader()).wrap(ResponseCodeHandler.HANDLE_404); runIpv6SlashMAtchTest(handler); } private void runIpv6SlashMAtchTest(IPAddressAccessControlHandler handler) throws UnknownHostException { Assert.assertTrue(handler.isAllowed(InetAddress.getByName("FE45:0:0:0:0:AAA:FFFF:45"))); Assert.assertTrue(handler.isAllowed(InetAddress.getByName("127.0.0.2"))); Assert.assertFalse(handler.isAllowed(InetAddress.getByName("FE45:0:0:0:0:AAA:FFFF:46"))); Assert.assertTrue(handler.isAllowed(InetAddress.getByName("FE45:0:0:0:0:AAA:FFFb:46"))); Assert.assertFalse(handler.isAllowed(InetAddress.getByName("fe45:0000:0000:0000:0000:0aaa:ffff:01f3"))); Assert.assertTrue(handler.isAllowed(InetAddress.getByName("fe45:0000:0000:0000:0000:0aaa:ffff:01f4"))); Assert.assertTrue(handler.isAllowed(InetAddress.getByName("fe45:0000:0000:0000:0000:0aaa:ffff:01f5"))); Assert.assertFalse(handler.isAllowed(InetAddress.getByName("fe45:0000:0000:0000:0000:0aaa:ffff:01f6"))); } @Test public void testDefaultDenyResponseCode() { IPAddressAccessControlHandler handler = new IPAddressAccessControlHandler(); Assert.assertEquals(StatusCodes.FORBIDDEN, handler.getDenyResponseCode()); } @Test public void testDenyResponseCode() { IPAddressAccessControlHandler handler = new IPAddressAccessControlHandler(null, StatusCodes.NOT_FOUND); Assert.assertEquals(StatusCodes.NOT_FOUND, handler.getDenyResponseCode()); } } IPAddressAccessControlHandlerWithProxyPeerAddressHandlerTestCase.java000066400000000000000000000156621420065311100430440ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.IOException; import io.undertow.Handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.testutils.IPv6Ignore; import io.undertow.testutils.IPv6Only; @RunWith(DefaultServer.class) public class IPAddressAccessControlHandlerWithProxyPeerAddressHandlerTestCase { @BeforeClass public static void setup() { HttpHandler rootHandler = new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain"); exchange.getResponseSender().send(exchange.getSourceAddress().getHostString()); // System.out.println("X-Forwarded-For header = " + exchange.getRequestHeaders().getFirst(Headers.X_FORWARDED_FOR)); // System.out.println("source address = " + exchange.getSourceAddress()); } }; if (DefaultServer.isIpv6()) { rootHandler = Handlers.ipAccessControl(rootHandler, false).addAllow("::1"); } else { rootHandler = Handlers.ipAccessControl(rootHandler, false).addAllow("127.0.0.0/8"); } DefaultServer.setRootHandler(Handlers.proxyPeerAddress(rootHandler)); } @Test @IPv6Ignore public void testWithoutXForwardedFor() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.contains("127.0.0.1")); } finally { client.getConnectionManager().shutdown(); } } @Test @IPv6Only public void testWithoutXForwardedForIPv6() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.contains("0:0:0:0:0:0:0:1")); } finally { client.getConnectionManager().shutdown(); } } @Test @IPv6Ignore public void testWithXForwardedFor1() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); get.addHeader(Headers.X_FORWARDED_FOR_STRING, "127.0.0.2"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.contains("127.0.0.2")); } finally { client.getConnectionManager().shutdown(); } } @Test @IPv6Ignore public void testWithXForwardedFor2() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); get.addHeader(Headers.X_FORWARDED_FOR_STRING, "127.0.0.1, 192.168.0.10"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.contains("127.0.0.1")); } finally { client.getConnectionManager().shutdown(); } } @Test @IPv6Ignore public void testWithXForwardedFor3() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); get.addHeader(Headers.X_FORWARDED_FOR_STRING, "127.0.0.1, 127.0.0.2, 192.168.0.10"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.contains("127.0.0.1")); } finally { client.getConnectionManager().shutdown(); } } @Test @IPv6Ignore public void testForbiddenWithXForwardedFor1() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); get.addHeader(Headers.X_FORWARDED_FOR_STRING, "192.168.0.10"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.FORBIDDEN, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.isEmpty()); } finally { client.getConnectionManager().shutdown(); } } @Test @IPv6Ignore public void testForbiddenWithXForwardedFor2() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); get.addHeader(Headers.X_FORWARDED_FOR_STRING, "192.168.0.10, 192.168.0.20"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.FORBIDDEN, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.isEmpty()); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/JDBCLogDatabaseTestCase.java000066400000000000000000000204201420065311100325630ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.CompletionLatchHandler; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.h2.jdbcx.JdbcConnectionPool; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; /** * Tests writing the database (in memory) * * @author Filipe Ferraz */ @RunWith(DefaultServer.class) public class JDBCLogDatabaseTestCase { private static final int NUM_THREADS = 10; private static final int NUM_REQUESTS = 12; private static final HttpHandler HELLO_HANDLER = new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("Hello"); } }; private JdbcConnectionPool ds; @Before public void setup() throws SQLException { ds = JdbcConnectionPool.create("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", "user", "password"); Connection conn = null; Statement statement = null; try { conn = ds.getConnection(); conn.setAutoCommit(true); statement = conn.createStatement(); statement.executeUpdate("CREATE TABLE PUBLIC.ACCESS (" + " id SERIAL NOT NULL," + " remoteHost CHAR(15) NOT NULL," + " userName CHAR(15)," + " timestamp TIMESTAMP NOT NULL," + " virtualHost VARCHAR(64)," + " method VARCHAR(8)," + " query VARCHAR(255) NOT NULL," + " status SMALLINT UNSIGNED NOT NULL," + " bytes INT UNSIGNED NOT NULL," + " referer VARCHAR(128)," + " userAgent VARCHAR(128)," + " PRIMARY KEY (id)" + " );"); } finally { if (statement != null) { statement.close(); } if (conn != null) { conn.close(); } } } @After public void teardown() throws SQLException { Connection conn = null; Statement statement = null; try { conn = ds.getConnection(); conn.setAutoCommit(true); statement = conn.createStatement(); statement.executeUpdate("DROP TABLE PUBLIC.ACCESS;"); } finally { if (statement != null) { statement.close(); } if (conn != null) { conn.close(); } } ds.dispose(); ds = null; } @Test public void testSingleLogMessageToDatabase() throws IOException, InterruptedException, SQLException { JDBCLogHandler logHandler = new JDBCLogHandler(HELLO_HANDLER, DefaultServer.getWorker(), "common", ds); CompletionLatchHandler latchHandler; DefaultServer.setRootHandler(latchHandler = new CompletionLatchHandler(logHandler)); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); HttpResponse result = client.execute(get); latchHandler.await(); logHandler.awaitWrittenForTest(); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("Hello", HttpClientUtils.readResponse(result)); } finally { Connection conn = null; Statement statement = null; try { conn = ds.getConnection(); statement = conn.createStatement(); ResultSet resultDatabase = statement.executeQuery("SELECT * FROM PUBLIC.ACCESS;"); resultDatabase.next(); Assert.assertEquals(DefaultServer.getDefaultServerAddress().getAddress().getHostAddress(), resultDatabase.getString(logHandler.getRemoteHostField())); Assert.assertEquals("5", resultDatabase.getString(logHandler.getBytesField())); Assert.assertEquals("200", resultDatabase.getString(logHandler.getStatusField())); client.getConnectionManager().shutdown(); } finally { if (statement != null) { statement.close(); } if (conn != null) { conn.close(); } } } } @Test public void testLogLotsOfThreadsToDatabase() throws IOException, InterruptedException, ExecutionException, SQLException { JDBCLogHandler logHandler = new JDBCLogHandler(HELLO_HANDLER, DefaultServer.getWorker(), "combined", ds); CompletionLatchHandler latchHandler; DefaultServer.setRootHandler(latchHandler = new CompletionLatchHandler(NUM_REQUESTS * NUM_THREADS, logHandler)); ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS); try { final List> futures = new ArrayList<>(); for (int i = 0; i < NUM_THREADS; ++i) { final int threadNo = i; futures.add(executor.submit(new Runnable() { @Override public void run() { TestHttpClient client = new TestHttpClient(); try { for (int i = 0; i < NUM_REQUESTS; ++i) { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("Hello", response); } } catch (IOException e) { throw new RuntimeException(e); } finally { client.getConnectionManager().shutdown(); } } })); } for (Future future : futures) { future.get(); } } finally { executor.shutdown(); } latchHandler.await(); logHandler.awaitWrittenForTest(); Connection conn = null; Statement statement = null; try { conn = ds.getConnection(); statement = conn.createStatement(); ResultSet resultDatabase = conn.createStatement().executeQuery("SELECT COUNT(*) FROM PUBLIC.ACCESS;"); resultDatabase.next(); Assert.assertEquals(resultDatabase.getInt(1), NUM_REQUESTS * NUM_THREADS); } finally { if (statement != null) { statement.close(); } if (conn != null) { conn.close(); } } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/LongURLTestCase.java000066400000000000000000000047251420065311100312660ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.IOException; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.AjpIgnore; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) @AjpIgnore public class LongURLTestCase { private static final String MESSAGE = "HelloUrl"; private static final int COUNT = 10000; @BeforeClass public static void setup() { final BlockingHandler blockingHandler = new BlockingHandler(); DefaultServer.setRootHandler(blockingHandler); blockingHandler.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) { exchange.getResponseSender().send(exchange.getRelativePath()); } }); } @Test public void testLargeURL() throws IOException { StringBuilder sb = new StringBuilder(); for (int i = 0; i < COUNT; ++i) { sb.append(MESSAGE); } String message = sb.toString(); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/" + message); HttpResponse result = client.execute(get); Assert.assertEquals("/" + message, HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } } LotsOfHeadersRequestTestCase.java000066400000000000000000000156001420065311100337720ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.IOException; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.OptionMap; import io.undertow.UndertowOptions; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.AjpIgnore; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.TestHttpClient; import io.undertow.util.HeaderMap; import io.undertow.util.HeaderValues; import io.undertow.util.HttpString; import io.undertow.util.StatusCodes; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) @AjpIgnore(apacheOnly = true) public class LotsOfHeadersRequestTestCase { private static final String HEADER = "HEADER"; private static final String MESSAGE = "Hello Header"; private static final int DEFAULT_MAX_HEADERS = 200; private static final int TEST_MAX_HEADERS = 20; // Why -3? Because HttpClient adds the following 3 request headers by default: // - Host // - User-Agent // - Connection: Keep-Alive // - The proxy handler also adds 5 X-forwarded-* headers private static int getDefaultMaxHeaders() { int res = DEFAULT_MAX_HEADERS - 3; if (DefaultServer.isProxy()) { res -= 5; } if(DefaultServer.isH2()) { res -= 3; } return res; } private static int getTestMaxHeaders() { int res = TEST_MAX_HEADERS - 3; if (DefaultServer.isProxy()) { res -= 5; } if(DefaultServer.isH2()) { res -= 3; } return res; } @BeforeClass public static void setup() { Assume.assumeFalse(DefaultServer.isH2upgrade()); final BlockingHandler blockingHandler = new BlockingHandler(); DefaultServer.setRootHandler(blockingHandler); blockingHandler.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) { HeaderMap headers = exchange.getRequestHeaders(); for (HeaderValues header : headers) { for (String val : header) { exchange.getResponseHeaders().put(HttpString.tryFromString(header.getHeaderName().toString()), val); } } } }); } @Test @AjpIgnore public void testLotsOfHeadersInRequest_Default_Ok() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); for (int i = 0; i < getDefaultMaxHeaders(); ++i) { get.addHeader(HEADER + i, MESSAGE + i); } HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); for (int i = 0; i < getDefaultMaxHeaders(); ++i) { Header[] header = result.getHeaders(HEADER + i); Assert.assertEquals(MESSAGE + i, header[0].getValue()); } } finally { client.getConnectionManager().shutdown(); } } @Test public void testLotsOfHeadersInRequest_Default_BadRequest() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); // add request headers more than MAX_HEADERS for (int i = 0; i < (getDefaultMaxHeaders() + 1); ++i) { get.addHeader(HEADER + i, MESSAGE + i); } HttpResponse result = client.execute(get); Assert.assertEquals(DefaultServer.isH2() ? StatusCodes.SERVICE_UNAVAILABLE : StatusCodes.BAD_REQUEST, result.getStatusLine().getStatusCode()); //this is not great, but the HTTP/2 impl sends a stream error which is translated to a 503. Should not be a big deal in practice } finally { client.getConnectionManager().shutdown(); } } @Test @AjpIgnore public void testLotsOfHeadersInRequest_MaxHeaders_Ok() throws IOException { OptionMap existing = DefaultServer.getUndertowOptions(); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); for (int i = 0; i < getTestMaxHeaders(); ++i) { get.addHeader(HEADER + i, MESSAGE + i); } DefaultServer.setUndertowOptions(OptionMap.create(UndertowOptions.MAX_HEADERS, TEST_MAX_HEADERS)); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); for (int i = 0; i < getTestMaxHeaders(); ++i) { Header[] header = result.getHeaders(HEADER + i); Assert.assertEquals(MESSAGE + i, header[0].getValue()); } } finally { DefaultServer.setUndertowOptions(existing); client.getConnectionManager().shutdown(); } } @Test public void testLotsOfHeadersInRequest_MaxHeaders_BadRequest() throws IOException { OptionMap existing = DefaultServer.getUndertowOptions(); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); // add request headers more than MAX_HEADERS for (int i = 0; i < (getTestMaxHeaders() + 1); ++i) { get.addHeader(HEADER + i, MESSAGE + i); } DefaultServer.setUndertowOptions(OptionMap.create(UndertowOptions.MAX_HEADERS, TEST_MAX_HEADERS)); HttpResponse result = client.execute(get); Assert.assertEquals(DefaultServer.isH2() ? StatusCodes.SERVICE_UNAVAILABLE : StatusCodes.BAD_REQUEST, result.getStatusLine().getStatusCode()); } finally { DefaultServer.setUndertowOptions(existing); client.getConnectionManager().shutdown(); } } } LotsOfHeadersResponseTestCase.java000066400000000000000000000053511420065311100341420ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.IOException; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.AjpIgnore; import io.undertow.testutils.DefaultServer; import io.undertow.util.HttpString; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) @AjpIgnore(apacheOnly = true) public class LotsOfHeadersResponseTestCase { private static final String HEADER = "HEADER"; private static final String MESSAGE = "Hello Header"; private static final int COUNT = 10000; @BeforeClass public static void setup() { final BlockingHandler blockingHandler = new BlockingHandler(); DefaultServer.setRootHandler(blockingHandler); blockingHandler.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) { for (int i = 0; i < COUNT; ++i) { exchange.getResponseHeaders().put(HttpString.tryFromString(HEADER + i), MESSAGE + i); } } }); } @Test public void testLotsOfHeadersInResponse() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); for (int i = 0; i < COUNT; ++i) { Header[] header = result.getHeaders(HEADER + i); Assert.assertEquals(MESSAGE + i, header[0].getValue()); } } finally { client.getConnectionManager().shutdown(); } } } LotsOfQueryParametersTestCase.java000066400000000000000000000154771420065311100342130ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.AjpIgnore; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.TestHttpClient; import io.undertow.util.HttpString; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.net.URLEncoder; import java.util.Deque; import java.util.Map; import io.undertow.UndertowOptions; import org.xnio.OptionMap; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) @AjpIgnore(apacheOnly = true) public class LotsOfQueryParametersTestCase { private static final String QUERY = "QUERY"; private static final String MESSAGE = "Hello Query"; private static final int DEFAULT_MAX_PARAMETERS = 1000; private static final int TEST_MAX_PARAMETERS = 10; @BeforeClass public static void setup() { final BlockingHandler blockingHandler = new BlockingHandler(); DefaultServer.setRootHandler(blockingHandler); blockingHandler.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) { for (Map.Entry> entry : exchange.getQueryParameters().entrySet()) { exchange.getResponseHeaders().put(HttpString.tryFromString(entry.getKey()), entry.getValue().getFirst()); } } }); } @Test @AjpIgnore public void testLotsOfQueryParameters_Default_Ok() throws IOException { TestHttpClient client = new TestHttpClient(); try { StringBuilder qs = new StringBuilder(); for (int i = 0; i < DEFAULT_MAX_PARAMETERS; ++i) { qs.append(QUERY + i); qs.append("="); qs.append(URLEncoder.encode(MESSAGE + i, "UTF-8")); qs.append("&"); } qs.deleteCharAt(qs.length()-1); // delete last useless '&' HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path?" + qs.toString()); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); for (int i = 0; i < DEFAULT_MAX_PARAMETERS; ++i) { Header[] header = result.getHeaders(QUERY + i); Assert.assertEquals(MESSAGE + i, header[0].getValue()); } } finally { client.getConnectionManager().shutdown(); } } @Test public void testLotsOfQueryParameters_Default_BadRequest() throws IOException { TestHttpClient client = new TestHttpClient(); try { StringBuilder qs = new StringBuilder(); // add query parameters more than MAX_PARAMETERS for (int i = 0; i < (DEFAULT_MAX_PARAMETERS + 1); ++i) { qs.append(QUERY + i); qs.append("="); qs.append(URLEncoder.encode(MESSAGE + i, "UTF-8")); qs.append("&"); } qs.deleteCharAt(qs.length()-1); // delete last useless '&' HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path?" + qs.toString()); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.BAD_REQUEST, result.getStatusLine().getStatusCode()); } finally { client.getConnectionManager().shutdown(); } } @Test @AjpIgnore public void testLotsOfQueryParameters_MaxParameters_Ok() throws IOException { OptionMap existing = DefaultServer.getUndertowOptions(); TestHttpClient client = new TestHttpClient(); try { StringBuilder qs = new StringBuilder(); for (int i = 0; i < TEST_MAX_PARAMETERS; ++i) { qs.append(QUERY + i); qs.append("="); qs.append(URLEncoder.encode(MESSAGE + i, "UTF-8")); qs.append("&"); } qs.deleteCharAt(qs.length()-1); // delete last useless '&' HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path?" + qs.toString()); DefaultServer.setUndertowOptions(OptionMap.create(UndertowOptions.MAX_PARAMETERS, TEST_MAX_PARAMETERS)); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); for (int i = 0; i < TEST_MAX_PARAMETERS; ++i) { Header[] header = result.getHeaders(QUERY + i); Assert.assertEquals(MESSAGE + i, header[0].getValue()); } } finally { DefaultServer.setUndertowOptions(existing); client.getConnectionManager().shutdown(); } } @Test @AjpIgnore public void testLotsOfQueryParameters_MaxParameters_BadRequest() throws IOException { OptionMap existing = DefaultServer.getUndertowOptions(); TestHttpClient client = new TestHttpClient(); try { StringBuilder qs = new StringBuilder(); // add query parameters more than specified MAX_PARAMETERS for (int i = 0; i < (TEST_MAX_PARAMETERS + 1); ++i) { qs.append(QUERY + i); qs.append("="); qs.append(URLEncoder.encode(MESSAGE + i, "UTF-8")); qs.append("&"); } qs.deleteCharAt(qs.length()-1); // delete last useless '&' HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path?" + qs.toString()); DefaultServer.setUndertowOptions(OptionMap.create(UndertowOptions.MAX_PARAMETERS, TEST_MAX_PARAMETERS)); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.BAD_REQUEST, result.getStatusLine().getStatusCode()); } finally { DefaultServer.setUndertowOptions(existing); client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/MetricsHandlerTestCase.java000066400000000000000000000075241420065311100327100ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.CompletionLatchHandler; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class MetricsHandlerTestCase { @Test public void testMetrics() throws IOException, InterruptedException { MetricsHandler metricsHandler; CompletionLatchHandler latchHandler; DefaultServer.setRootHandler(latchHandler = new CompletionLatchHandler(metricsHandler = new MetricsHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { Thread.sleep(100); if(exchange.getQueryString().contains("error")) { throw new RuntimeException(); } exchange.getResponseSender().send("Hello"); } }))); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); TestHttpClient client = new TestHttpClient(); try { HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("Hello", HttpClientUtils.readResponse(result)); latchHandler.await(); latchHandler.reset(); MetricsHandler.MetricResult metrics = metricsHandler.getMetrics(); Assert.assertEquals(1, metrics.getTotalRequests()); Assert.assertTrue(metrics.getMaxRequestTime() > 0); Assert.assertEquals(metrics.getMinRequestTime(), metrics.getMaxRequestTime()); Assert.assertEquals(metrics.getMaxRequestTime(), metrics.getTotalRequestTime()); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("Hello", HttpClientUtils.readResponse(result)); latchHandler.await(); latchHandler.reset(); metrics = metricsHandler.getMetrics(); Assert.assertEquals(2, metrics.getTotalRequests()); Assert.assertEquals(0, metrics.getTotalErrors()); result = client.execute(new HttpGet(DefaultServer.getDefaultServerURL() + "/path?error=true")); Assert.assertEquals(StatusCodes.INTERNAL_SERVER_ERROR, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); latchHandler.await(); latchHandler.reset(); metrics = metricsHandler.getMetrics(); Assert.assertEquals(3, metrics.getTotalRequests()); Assert.assertEquals(1, metrics.getTotalErrors()); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/OriginTestCase.java000066400000000000000000000075201420065311100312270ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.IOException; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import io.undertow.testutils.TestHttpClient; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; /** * Tests that the Origin header is correctly interpreted * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class OriginTestCase { private static final String HEADER = "selected"; private static final String MESSAGE = "My HTTP Request!"; /** * Tests the Origin header is respected when the strictest options are selected * */ @Test public void testStrictOrigin() throws IOException { TestHttpClient client = new TestHttpClient(); try { final OriginHandler handler = new OriginHandler(); handler.addAllowedOrigins("http://www.mysite.com:80", "http://mysite.com:80"); DefaultServer.setRootHandler(handler); handler.setNext(ResponseCodeHandler.HANDLE_200); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); HttpResponse result = client.execute(get); //no origin header, we dny by default Assert.assertEquals(StatusCodes.FORBIDDEN, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ORIGIN_STRING, "http://www.mysite.com:80"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ORIGIN_STRING, "http://www.mysite.com:80"); get.setHeader(Headers.ORIGIN_STRING, "http://mysite.com:80"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ORIGIN_STRING, "http://www.mysite.com:80"); get.setHeader(Headers.ORIGIN_STRING, "bogus"); result = client.execute(get); Assert.assertEquals(StatusCodes.FORBIDDEN, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ORIGIN_STRING, "http://www.mysite.com:80"); get.setHeader(Headers.ORIGIN_STRING, "bogus"); result = client.execute(get); Assert.assertEquals(StatusCodes.FORBIDDEN, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } } PathTemplateHandlerTestCase.java000066400000000000000000000105271420065311100336100ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.Handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class PathTemplateHandlerTestCase { @BeforeClass public static void setup() { HttpHandler handler = Handlers.pathTemplate() .add("/", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("root"); } }) .add("/foo", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("foo"); } }).add("/foo/", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("foo/"); } }) .add("/foo/{bar}", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("foo-path" + exchange.getQueryParameters().get("bar")); } }); DefaultServer.setRootHandler(Handlers.path(handler).addPrefixPath("/prefix", handler)); } @Test public void testPathTemplateHandler() throws IOException { runPathTemplateHandlerTest(""); } @Test public void testPathTemplateHandlerWithPrefix() throws IOException { runPathTemplateHandlerTest("/prefix"); } public void runPathTemplateHandlerTest(String prefix) throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + prefix +"/foo"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("foo", HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + prefix +"/foo/"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("foo/", HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + prefix +"/foo/a"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("foo-path[a]", HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + prefix); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("root", HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } } PreChunkedResponseTransferCodingTestCase.java000066400000000000000000000116661420065311100363270ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.ServerConnection; import io.undertow.server.protocol.http.HttpAttachments; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import io.undertow.util.StringWriteChannelListener; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.io.OutputStream; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) @HttpOneOnly public class PreChunkedResponseTransferCodingTestCase { private static final String MESSAGE = "My HTTP Request!"; private static volatile String message; private static volatile String chunkedMessage; private static volatile ServerConnection connection; @BeforeClass public static void setup() { final BlockingHandler blockingHandler = new BlockingHandler(); DefaultServer.setRootHandler(blockingHandler); blockingHandler.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) { try { if(connection == null) { connection = exchange.getConnection(); } else if(!DefaultServer.isAjp() && !DefaultServer.isProxy() && connection != exchange.getConnection()){ final OutputStream outputStream = exchange.getOutputStream(); outputStream.write("Connection not persistent".getBytes()); outputStream.close(); return; } exchange.getResponseHeaders().put(Headers.TRANSFER_ENCODING, Headers.CHUNKED.toString()); exchange.putAttachment(HttpAttachments.PRE_CHUNKED_RESPONSE, true); new StringWriteChannelListener(chunkedMessage).setup(exchange.getResponseChannel()); } catch (IOException e) { throw new RuntimeException(e); } } }); } @Test public void sendHttpRequest() throws IOException { Assume.assumeFalse(DefaultServer.isH2()); //this test will still run under h2-upgrade, but will fail connection = null; HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); TestHttpClient client = new TestHttpClient(); try { generateMessage(0); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(message, HttpClientUtils.readResponse(result)); generateMessage(1); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(message, HttpClientUtils.readResponse(result)); generateMessage(1000); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(message, HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } private static void generateMessage(int repetitions) { final StringBuilder builder = new StringBuilder(repetitions * MESSAGE.length()); final StringBuilder chunkedBuilder = new StringBuilder(repetitions * MESSAGE.length()); for (int i = 0; i < repetitions; ++i) { builder.append(MESSAGE); chunkedBuilder.append(Integer.toHexString(MESSAGE.length())); chunkedBuilder.append("\r\n"); chunkedBuilder.append(MESSAGE); chunkedBuilder.append("\r\n"); } chunkedBuilder.append("0\r\n\r\n"); message = builder.toString(); chunkedMessage = chunkedBuilder.toString(); } } PredicatedHandlersProxyTestCase.java000066400000000000000000000102411420065311100345020ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.Handlers; import io.undertow.Undertow; import io.undertow.server.handlers.builder.PredicatedHandlersParser; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.NetworkUtils; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.net.URISyntaxException; import static io.undertow.testutils.DefaultServer.getHostAddress; import static io.undertow.testutils.DefaultServer.getHostPort; /** * @author Luis Mineiro */ @RunWith(DefaultServer.class) public class PredicatedHandlersProxyTestCase { private static Undertow server1; private static Undertow server2; @BeforeClass public static void setup() throws URISyntaxException { int port = getHostPort("default") + 1; final NameVirtualHostHandler handler = new NameVirtualHostHandler() .addHost("original-host", new SetHeaderHandler(ResponseCodeHandler.HANDLE_200, "myHost", "original-host")) .setDefaultHandler(new SetHeaderHandler(ResponseCodeHandler.HANDLE_200, "myHost", "upstream-host")); server1 = Undertow.builder() .addHttpListener(port, getHostAddress("default")) .setHandler(handler) .build(); server1.start(); } @Test public void testProxy() throws Exception { TestHttpClient client = new TestHttpClient(); int port = getHostPort("default"); String upstreamUrl = "http://" + NetworkUtils.formatPossibleIpv6Address(getHostAddress("default")) + ":" + (port + 1); DefaultServer.setRootHandler( Handlers.predicates( PredicatedHandlersParser.parse( String.format( "path-suffix['.html'] -> reverse-proxy[hosts={'%1$s'}, rewrite-host-header=true]\n" + "path-suffix['.jsp'] -> reverse-proxy[hosts={'%1$s'}]", upstreamUrl ), getClass().getClassLoader()), ResponseCodeHandler.HANDLE_404)); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/foo.html"); get.addHeader("Host", "original-host"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] header = result.getHeaders("myHost"); Assert.assertEquals("upstream-host", header[0].getValue()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/foo.jsp"); get.addHeader("Host", "original-host"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); header = result.getHeaders("myHost"); Assert.assertEquals("original-host", header[0].getValue()); HttpClientUtils.readResponse(result); } @AfterClass public static void teardown() { server1.stop(); // sleep 1 s to prevent BindException (Address already in use) when running the CI try { Thread.sleep(1000); } catch (InterruptedException ignore) {} } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/PredicatedHandlersTestCase.java000066400000000000000000000150261420065311100335250ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.Handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.PredicatedHandlersParser; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class PredicatedHandlersTestCase { @Test public void testRewrite() throws IOException { DefaultServer.setRootHandler( Handlers.predicates( PredicatedHandlersParser.parse( "path(/skipallrules) and true -> done\n" + "method(GET) -> set(attribute='%{o,type}', value=get) \r\n" + "regex('(.*).css') -> {rewrite['${1}.xcss'];set(attribute='%{o,chained}', value=true)} \n" + "regex('(.*).redirect$') -> redirect['${1}.redirected']\n\n\n\n\n" + "set[attribute='%{o,someHeader}', value=always]\n" + "path-template('/foo/{bar}/{f}') -> set[attribute='%{o,template}', value='${bar}']\r\n" + "path-template('/bar->foo') -> redirect(/);" + "regex('(.*).css') -> set[attribute='%{o,css}', value='true'] else set[attribute='%{o,css}', value='false']; " + "path(/restart) -> {rewrite(/foo/a/b); restart; }\r\n" + "regex('^/path/([^/]+)/(.*)/?$') -> rewrite('/newpath'); set(attribute='%{o,result}', value='param1=$1¶m2=$2'); done()", getClass().getClassLoader()), new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send(exchange.getRelativePath()); } })); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/foo/a/b"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("get", result.getHeaders("type")[0].getValue()); Assert.assertEquals("always", result.getHeaders("someHeader")[0].getValue()); Assert.assertEquals("a", result.getHeaders("template")[0].getValue()); Assert.assertEquals("false", result.getHeaders("css")[0].getValue()); Assert.assertEquals("/foo/a/b", response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path/a/b"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("param1=a¶m2=b", result.getHeaders("result")[0].getValue()); Assert.assertEquals("/newpath", response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/foo/a/b.css"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("get", result.getHeaders("type")[0].getValue()); Assert.assertEquals("true", result.getHeaders("chained")[0].getValue()); Assert.assertEquals("/foo/a/b.xcss", response); Assert.assertEquals("always", result.getHeaders("someHeader")[0].getValue()); Assert.assertEquals("true", result.getHeaders("css")[0].getValue()); Assert.assertEquals("a", result.getHeaders("template")[0].getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/foo/a/b.redirect"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("get", result.getHeaders("type")[0].getValue()); Assert.assertEquals("always", result.getHeaders("someHeader")[0].getValue()); Assert.assertEquals("a", result.getHeaders("template")[0].getValue()); Assert.assertEquals("/foo/a/b.redirected", response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/skipallrules"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals(0, result.getHeaders("someHeader").length); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/restart"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("get", result.getHeaders("type")[0].getValue()); Assert.assertEquals("always", result.getHeaders("someHeader")[0].getValue()); Assert.assertEquals("a", result.getHeaders("template")[0].getValue()); Assert.assertEquals("/foo/a/b", response); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/QueryParametersTestCase.java000066400000000000000000000130551420065311100331310ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.IOException; import java.util.Deque; import java.util.Iterator; import java.util.Map; import io.undertow.UndertowOptions; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.ProxyIgnore; import io.undertow.testutils.TestHttpClient; import io.undertow.util.ParameterLimitException; import io.undertow.util.URLUtils; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.OptionMap; /** * Tests that query parameters are handled correctly. * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class QueryParametersTestCase { @BeforeClass public static void setup (){ DefaultServer.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { StringBuilder sb = new StringBuilder(); sb.append("{"); Iterator>> iterator = exchange.getQueryParameters().entrySet().iterator(); while (iterator.hasNext()) { Map.Entry> qp = iterator.next(); sb.append(qp.getKey()); sb.append("=>"); if(qp.getValue().size() == 1) { sb.append(qp.getValue().getFirst()); } else { sb.append("["); for(Iterator i = qp.getValue().iterator(); i.hasNext(); ) { String val = i.next(); sb.append(val); if(i.hasNext()) { sb.append(","); } } sb.append("]"); } if(iterator.hasNext()) { sb.append(","); } } sb.append("}"); exchange.getResponseSender().send(sb.toString()); } }); } @Test public void testQueryParameters() throws IOException { TestHttpClient client = new TestHttpClient(); try { runTest(client, "{unicode=>Iñtërnâtiônàližætiøn}", "/path?unicode=Iñtërnâtiônàližætiøn"); runTest(client, "{a=>b,value=>bb bb}", "/path?a=b&value=bb%20bb"); runTest(client, "{a=>b,value=>[bb,cc]}", "/path?a=b&value=bb&value=cc"); runTest(client, "{a=>b,value=>[bb,cc]}", "/path?&a=b&value=bb&&value=cc"); // Specifing some query parameters with empty by intentional for the test purpose. These should be ignored. runTest(client, "{a=>b,s =>,t =>,value=>[bb,cc]}", "/path?a=b&value=bb&value=cc&s%20&t%20"); runTest(client, "{a=>b,s =>,t =>,value=>[bb,cc]}", "/path?a=b&value=bb&value=cc&s%20&t%20&"); runTest(client, "{a=>b,s =>,t =>,u=>,value=>[bb,cc]}", "/path?a=b&value=bb&value=cc&s%20&t%20&u"); } finally { client.getConnectionManager().shutdown(); } } @Test @ProxyIgnore public void testQueryParametersShiftJIS() throws IOException { OptionMap old = DefaultServer.getUndertowOptions(); try { DefaultServer.setUndertowOptions(OptionMap.create(UndertowOptions.URL_CHARSET, "Shift_JIS")); TestHttpClient client = new TestHttpClient(); try { runTest(client, "{unicode=>テスト}", "/path?unicode=%83e%83X%83g"); } finally { client.getConnectionManager().shutdown(); } } finally { DefaultServer.setUndertowOptions(old); } } @Test @ProxyIgnore public void testQueryParameterParsingIncorrectlyEncodedURI() throws IOException, ParameterLimitException { StringBuilder out = new StringBuilder(); out.append((char)0xc7); out.append((char)0xd1); out.append((char)0x25); out.append((char)0x32); out.append((char)0x30); out.append((char)0xb1); out.append((char)0xdb); String s = "p=" + out.toString(); HttpServerExchange exchange = new HttpServerExchange(null); URLUtils.parseQueryString(s, exchange, "MS949", true, 1000); Assert.assertEquals("한 글", exchange.getQueryParameters().get("p").getFirst()); } private void runTest(final TestHttpClient client, final String expected, final String queryString) throws IOException { Assert.assertEquals(expected, HttpClientUtils.readResponse(client.execute(new HttpGet(DefaultServer.getDefaultServerURL() + queryString)))); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/RangeRequestTestCase.java000066400000000000000000000351351420065311100324100ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.Handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.cache.DirectBufferCache; import io.undertow.server.handlers.resource.CachingResourceManager; import io.undertow.server.handlers.resource.PathResourceManager; import io.undertow.server.handlers.resource.ResourceHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.TestHttpClient; import io.undertow.util.DateUtils; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.util.EntityUtils; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Date; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class RangeRequestTestCase { @BeforeClass public static void setup() throws URISyntaxException { Path rootPath = Paths.get(RangeRequestTestCase.class.getResource("range.txt").toURI()).getParent(); PathHandler path = Handlers.path(); path.addPrefixPath("/path", new ByteRangeHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseHeaders().put(Headers.LAST_MODIFIED, DateUtils.toDateString(new Date(10000))); exchange.getResponseHeaders().put(Headers.ETAG, "\"someetag\""); exchange.getResponseSender().send("0123456789"); } }, true)); path.addPrefixPath("/resource", new ResourceHandler( new PathResourceManager(rootPath, 10485760)) .setDirectoryListingEnabled(true)); path.addPrefixPath("/cachedresource", new ResourceHandler(new CachingResourceManager(1000, 1000000, new DirectBufferCache(1000, 10, 10000), new PathResourceManager(rootPath, 10485760), -1)) .setDirectoryListingEnabled(true)); DefaultServer.setRootHandler(path); } @Test public void testGenericRangeHandler() throws IOException, InterruptedException { runTest("/path", true); } @Test public void testResourceHandler() throws IOException, InterruptedException { runTest("/resource/range.txt", false); } @Test public void testCachedResourceHandler() throws IOException, InterruptedException { for(int i = 0; i < 10; ++i) { runTest("/cachedresource/range.txt", false); } } @Test public void testLargeCachedResourceHandler() throws IOException, InterruptedException { String path = "/cachedresource/largerange.txt"; TestHttpClient client = new TestHttpClient(); try { for(int i = 0; i < 3; ++i) { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + path); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); client.getConnectionManager().shutdown(); client = new TestHttpClient(); } for(int i = 0; i < 10; ++i) { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + path); get.addHeader(Headers.RANGE_STRING, "bytes=10-20"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); String response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("89#2:012345", response); Assert.assertEquals( "bytes 10-20/1034", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + path); get.addHeader(Headers.RANGE_STRING, "bytes=1000-1024"); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("3:0123456789#74:012345678", response); Assert.assertEquals( "bytes 1000-1024/1034", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + path); get.addHeader(Headers.RANGE_STRING, "bytes=1001-1024"); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals(":0123456789#74:012345678", response); Assert.assertEquals( "bytes 1001-1024/1034", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + path); get.addHeader(Headers.RANGE_STRING, "bytes=1025-1030"); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("9abcde", response); Assert.assertEquals( "bytes 1025-1030/1034", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); } } finally { client.getConnectionManager().shutdown(); } } public void runTest(String path, boolean etag) throws IOException, InterruptedException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + path); get.addHeader(Headers.RANGE_STRING, "bytes=2-3"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); String response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("23", response); Assert.assertEquals( "bytes 2-3/10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + path); get.addHeader(Headers.RANGE_STRING, "bytes=3-1000"); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("3456789", response); Assert.assertEquals( "bytes 3-9/10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + path); get.addHeader(Headers.RANGE_STRING, "bytes=3-9"); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("3456789", response); Assert.assertEquals( "bytes 3-9/10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + path); get.addHeader(Headers.RANGE_STRING, "bytes=0-0"); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("0", response); Assert.assertEquals( "bytes 0-0/10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + path); get.addHeader(Headers.RANGE_STRING, "bytes=1-"); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("123456789", response); Assert.assertEquals( "bytes 1-9/10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + path); get.addHeader(Headers.RANGE_STRING, "bytes=0-"); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("0123456789", response); Assert.assertEquals("bytes 0-9/10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + path); get.addHeader(Headers.RANGE_STRING, "bytes=9-"); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("9", response); Assert.assertEquals("bytes 9-9/10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + path); get.addHeader(Headers.RANGE_STRING, "bytes=-1"); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("9", response); Assert.assertEquals("bytes 9-9/10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + path); get.addHeader(Headers.RANGE_STRING, "bytes=99-100"); result = client.execute(get); Assert.assertEquals(StatusCodes.REQUEST_RANGE_NOT_SATISFIABLE, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("", response); Assert.assertEquals("bytes */10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + path); get = new HttpGet(DefaultServer.getDefaultServerURL() + path); get.addHeader(Headers.RANGE_STRING, "bytes=2-1"); result = client.execute(get); Assert.assertEquals(StatusCodes.REQUEST_RANGE_NOT_SATISFIABLE, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("", response); Assert.assertEquals("bytes */10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + path); get.addHeader(Headers.RANGE_STRING, "bytes=10-"); result = client.execute(get); Assert.assertEquals(StatusCodes.REQUEST_RANGE_NOT_SATISFIABLE, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("", response); Assert.assertEquals("bytes */10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + path); get.addHeader(Headers.RANGE_STRING, "bytes=2-3"); get.addHeader(Headers.IF_RANGE_STRING, DateUtils.toDateString(new Date(System.currentTimeMillis() + 1000))); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("23", response); Assert.assertEquals( "bytes 2-3/10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + path); get.addHeader(Headers.RANGE_STRING, "bytes=2-3"); get.addHeader(Headers.IF_RANGE_STRING, DateUtils.toDateString(new Date(0))); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("0123456789", response); Assert.assertNull(result.getFirstHeader(Headers.CONTENT_RANGE_STRING)); if(etag) { get = new HttpGet(DefaultServer.getDefaultServerURL() + path); get.addHeader(Headers.RANGE_STRING, "bytes=2-3"); get.addHeader(Headers.IF_RANGE_STRING, "\"someetag\""); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("23", response); Assert.assertEquals( "bytes 2-3/10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + path); get.addHeader(Headers.RANGE_STRING, "bytes=2-3"); get.addHeader(Headers.IF_RANGE_STRING, "\"otheretag\""); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("0123456789", response); Assert.assertNull(result.getFirstHeader(Headers.CONTENT_RANGE_STRING)); } } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/ReceiverTestCase.java000066400000000000000000000224101420065311100315370ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.io.IoCallback; import io.undertow.io.Receiver; import io.undertow.io.Sender; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.ProxyIgnore; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.nio.ByteBuffer; import java.util.Deque; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ReceiverTestCase { public static final String HELLO_WORLD = "Hello World"; private static final LinkedBlockingDeque EXCEPTIONS = new LinkedBlockingDeque<>(); public static final Receiver.ErrorCallback ERROR_CALLBACK = new Receiver.ErrorCallback() { @Override public void error(HttpServerExchange exchange, IOException e) { EXCEPTIONS.add(e); exchange.endExchange(); } }; @BeforeClass public static void setup() { HttpHandler testFullString = new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getRequestReceiver().receiveFullString(new Receiver.FullStringCallback() { @Override public void handle(HttpServerExchange exchange, String message) { exchange.getResponseSender().send(message); } }, ERROR_CALLBACK); } }; HttpHandler testPartialString = new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final StringBuilder sb = new StringBuilder(); exchange.getRequestReceiver().receivePartialString(new Receiver.PartialStringCallback() { @Override public void handle(HttpServerExchange exchange, String message, boolean last) { sb.append(message); if(last) { exchange.getResponseSender().send(sb.toString()); } } }, ERROR_CALLBACK); } }; HttpHandler testFullBytes = new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getRequestReceiver().receiveFullBytes(new Receiver.FullBytesCallback() { @Override public void handle(HttpServerExchange exchange, byte[] message) { exchange.getResponseSender().send(ByteBuffer.wrap(message)); } }, ERROR_CALLBACK); } }; HttpHandler testPartialBytes = new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { class CB implements Receiver.PartialBytesCallback, IoCallback { final Receiver receiver; final Sender sender; CB(Receiver receiver, Sender sender) { this.receiver = receiver; this.sender = sender; } @Override public void onComplete(HttpServerExchange exchange, Sender sender) { receiver.resume(); } @Override public void onException(HttpServerExchange exchange, Sender sender, IOException exception) { exception.printStackTrace(); exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); exchange.endExchange(); } @Override public void handle(HttpServerExchange exchange, byte[] message, boolean last) { receiver.pause(); sender.send(ByteBuffer.wrap(message), last ? IoCallback.END_EXCHANGE : this); } } CB callback = new CB(exchange.getRequestReceiver(), exchange.getResponseSender()); exchange.getRequestReceiver().receivePartialBytes(callback); } }; final PathHandler handler = new PathHandler().addPrefixPath("/fullstring", testFullString) .addPrefixPath("/partialstring", testPartialString) .addPrefixPath("/fullbytes", testFullBytes) .addPrefixPath("/partialbytes", testPartialBytes); DefaultServer.setRootHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { Deque block = exchange.getQueryParameters().get("blocking"); if(block != null) { exchange.startBlocking(); exchange.dispatch(handler); return; } handler.handleRequest(exchange); } }); } @Test public void testAsyncReceiveWholeString() { doTest("/fullstring"); } @Test public void testAsyncReceivePartialString() { doTest("/partialstring"); } @Test public void testAsyncReceiveWholeBytes() { doTest("/fullbytes"); } @Test @ProxyIgnore // FIXME UNDERTOW-1942 assertion of not null IOException fails sporadically (last line of this method) public void testAsyncReceiveWholeBytesFailed() throws Exception { EXCEPTIONS.clear(); Socket socket = new Socket(); socket.connect(DefaultServer.getDefaultServerAddress()); StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10000; ++i) { sb.append("hello world\r\n"); } //send a large request that is too small, then kill the socket String request = "POST /fullbytes HTTP/1.1\r\nHost:localhost\r\nContent-Length:" + sb.length() + 100 + "\r\n\r\n" + sb.toString(); OutputStream outputStream = socket.getOutputStream(); outputStream.write(request.getBytes("US-ASCII")); socket.getInputStream().close(); outputStream.close(); IOException e = EXCEPTIONS.poll(2, TimeUnit.SECONDS); Assert.assertNotNull(e); } @Test public void testAsyncReceivePartialBytes() { doTest("/partialbytes"); } @Test public void testBlockingReceiveWholeString() { doTest("/fullstring?blocking"); } @Test public void testBlockingReceivePartialString() { doTest("/partialstring?blocking"); } @Test public void testBlockingReceiveWholeBytes() { doTest("/fullbytes?blocking"); } @Test public void testBlockingReceivePartialBytes() { doTest("/partialbytes?blocking"); } public void doTest(String path) { StringBuilder builder = new StringBuilder(1000 * HELLO_WORLD.length()); for (int i = 0; i < 10; ++i) { try { for (int j = 0; j < 1000; ++j) { builder.append(HELLO_WORLD); } String message = builder.toString(); runTest(message, path); } catch (Throwable e) { throw new RuntimeException("test failed with i equal to " + i, e); } } } public void runTest(final String message, String url) throws IOException { TestHttpClient client = new TestHttpClient(); try { String uri = DefaultServer.getDefaultServerURL() + url; HttpPost post = new HttpPost(uri); post.setEntity(new StringEntity(message)); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(message.length(), response.length()); Assert.assertEquals(message, response); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/RedirectTestCase.java000066400000000000000000000066331420065311100315450ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.Handlers; import io.undertow.predicate.Predicates; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import static io.undertow.Handlers.predicate; import static io.undertow.Handlers.predicateContext; /** * Tests the redirect handler * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class RedirectTestCase { private static volatile String message; @BeforeClass public static void setup() { DefaultServer.setRootHandler(new PathHandler() .addPrefixPath("/target", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { message = exchange.getRequestURI(); } }) .addPrefixPath("/", predicateContext(predicate(Predicates.regex("%{REQUEST_URL}", "/(aa.*?)c", RedirectTestCase.class.getClassLoader(), false), Handlers.redirect("/target/matched/${1}"), Handlers.redirect("/target%U")))) ); } @Test public void testRedirectHandler() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path/a"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); Assert.assertEquals("/target/path/a", message ); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/aabc"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); Assert.assertEquals("/target/matched/aab", message ); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/somePath/aabc"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); Assert.assertEquals("/target/matched/aab", message ); } finally { client.getConnectionManager().shutdown(); } } } RequestLimitingHandlerTestCase.java000066400000000000000000000160011420065311100343360ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.Handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class RequestLimitingHandlerTestCase { public static final int N_THREADS = 10; private static volatile CountDownLatch latch = new CountDownLatch(1); static final AtomicInteger count = new AtomicInteger(); @BeforeClass public static void setup() { DefaultServer.setRootHandler(new BlockingHandler(Handlers.requestLimitingHandler(2, N_THREADS, new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { int res = count.incrementAndGet(); try { if (!latch.await(20, TimeUnit.SECONDS)) { exchange.setStatusCode(500); } else { exchange.getOutputStream().write(("" + res).getBytes("US-ASCII")); } } finally { count.decrementAndGet(); } } }))); } @Test public void testRateLimitingHandler() throws ExecutionException, InterruptedException { latch.countDown(); latch = new CountDownLatch(1); ExecutorService executor = Executors.newFixedThreadPool(N_THREADS); try { final List> futures = new ArrayList<>(); for (int i = 0; i < N_THREADS; ++i) { futures.add(executor.submit(new Callable() { @Override public String call() { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); return HttpClientUtils.readResponse(result); } catch (IOException e) { throw new RuntimeException(e); } finally { client.getConnectionManager().shutdown(); } } })); } Thread.sleep(300); latch.countDown(); for (Future future : futures) { String res = (String) future.get(); Assert.assertTrue(res, res.equals("1") || res.equals("2")); } } finally { executor.shutdown(); } } @Test public void testRateLimitingHandlerQueueFull() throws ExecutionException, InterruptedException { latch.countDown(); latch = new CountDownLatch(1); ExecutorService executor = Executors.newFixedThreadPool(N_THREADS * 2); try { final List> futures = new ArrayList<>(); for (int i = 0; i < N_THREADS * 2; ++i) { futures.add(executor.submit(new Callable() { @Override public String call() { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); if(result.getStatusLine().getStatusCode() == 503) { return "503"; } Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); return HttpClientUtils.readResponse(result); } catch (IOException e) { throw new RuntimeException(e); } finally { client.getConnectionManager().shutdown(); } } })); } Thread.sleep(300); latch.countDown(); for (Future future : futures) { String res = (String) future.get(); Assert.assertTrue(res, res.equals("1") || res.equals("2") || res.equals("503")); } futures.clear(); for (int i = 0; i < 2; ++i) { futures.add(executor.submit(new Callable() { @Override public String call() { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); return HttpClientUtils.readResponse(result); } catch (IOException e) { throw new RuntimeException(e); } finally { client.getConnectionManager().shutdown(); } } })); } for (Future future : futures) { String res = (String) future.get(); Assert.assertTrue(res, res.equals("1") || res.equals("2")); } } finally { executor.shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/ResumeWritesTestCase.java000066400000000000000000000167761420065311100324530ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import io.undertow.server.ConduitWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.ConduitFactory; import io.undertow.util.Headers; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.HttpVersion; import org.apache.http.client.methods.HttpGet; import org.apache.http.params.CoreProtocolPNames; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.AbstractStreamSinkConduit; import org.xnio.conduits.StreamSinkConduit; /** * Test that uses a fake channel that returns 0 a lot, to make sure that resume writes works correclty * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ResumeWritesTestCase { public static final String HELLO_WORLD = "Hello World"; @Test public void testResumeWritesFixedLength() throws IOException { DefaultServer.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.addResponseWrapper(new ReturnZeroWrapper()); exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, HELLO_WORLD.length()); exchange.getResponseSender().send(HELLO_WORLD); } }); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(HELLO_WORLD, HttpClientUtils.readResponse(result)); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(HELLO_WORLD, HttpClientUtils.readResponse(result)); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(HELLO_WORLD, HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } @Test public void testResumeWritesChunked() throws IOException { DefaultServer.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.addResponseWrapper(new ReturnZeroWrapper()); exchange.getResponseSender().send(HELLO_WORLD); } }); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(HELLO_WORLD, HttpClientUtils.readResponse(result)); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(HELLO_WORLD, HttpClientUtils.readResponse(result)); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(HELLO_WORLD, HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } @Test public void testResumeWritesHttp10() throws IOException { DefaultServer.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.addResponseWrapper(new ReturnZeroWrapper()); exchange.getResponseSender().send(HELLO_WORLD); } }); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_0); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(HELLO_WORLD, HttpClientUtils.readResponse(result)); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(HELLO_WORLD, HttpClientUtils.readResponse(result)); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(HELLO_WORLD, HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } private static class ReturnZeroWrapper implements ConduitWrapper { @Override public StreamSinkConduit wrap(final ConduitFactory factory, final HttpServerExchange exchange) { return new AbstractStreamSinkConduit(factory.create()) { int c = 0; @Override public long transferFrom(final FileChannel src, final long position, final long count) throws IOException { if(c++ % 100 != 90) return 0; return super.transferFrom(src, position, count); } @Override public long transferFrom(final StreamSourceChannel source, final long count, final ByteBuffer throughBuffer) throws IOException { if(c++ % 100 != 90) return 0; return super.transferFrom(source, count, throughBuffer); } @Override public int write(final ByteBuffer src) throws IOException { if(c++ % 100 != 90) return 0; return super.write(src); } @Override public long write(final ByteBuffer[] srcs, final int offs, final int len) throws IOException { if(c++ % 100 != 90) return 0; return super.write(srcs, offs, len); } @Override public boolean flush() throws IOException { if(c++ % 100 != 90) return false; return super.flush(); } }; } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/RoutingHandlerTestCase.java000066400000000000000000000331351420065311100327260ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.Handlers; import io.undertow.predicate.Predicates; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.RoutingHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Methods; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class RoutingHandlerTestCase { @BeforeClass public static void setup() { RoutingHandler commonHandler = Handlers.routing() .add(Methods.GET, "/baz", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("baz"); } }) .add(Methods.GET, "/baz/{foo}", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("baz-path" + exchange.getQueryParameters().get("foo")); } }); RoutingHandler convienceHandler = Handlers.routing() .get("/bar", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("GET bar"); } }) .put("/bar", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("PUT bar"); } }) .post("/bar", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("POST bar"); } }) .delete("/bar", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("DELETE bar"); } }); HttpHandler handler = Handlers.routing() .add(Methods.GET, "/wild/{test}/*", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("wild:" + exchange.getQueryParameters().get("test") + ":" + exchange.getQueryParameters().get("*")); } }) .add(Methods.GET, "/wilder/*", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("wilder:" + exchange.getQueryParameters().get("*")); } }) .add(Methods.GET, "/wildest*", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("wildest:" + exchange.getQueryParameters().get("*")); } }) .add(Methods.GET, "/foo", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("foo"); } }) .add(Methods.GET, "/foo", Predicates.parse("contains[value=%{i,SomeHeader},search='special'] "), new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("special foo"); } }) .add(Methods.POST, "/foo", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("posted foo"); } }) .add(Methods.POST, "/foo/{baz}", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("foo-path" + exchange.getQueryParameters().get("bar")); } }) .add(Methods.GET, "/foo/{bar}", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("foo-path" + exchange.getQueryParameters().get("bar")); } }) .get("/", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("GET /"); } }).add(Methods.GET, "scoop/{scoop}", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("SCOOP GET"); } }) .add(Methods.POST, "scoop/{scoop}", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("SCOOP POST"); } }) .addAll(commonHandler) .addAll(convienceHandler); DefaultServer.setRootHandler(Handlers.path(handler).addPrefixPath("/prefix", handler)); } @Test public void testRoutingTemplateHandler() throws IOException { runRoutingTemplateHandlerTests(""); } @Test public void testRoutingTemplateHandlerWithPrefixPath() throws IOException { runRoutingTemplateHandlerTests("/prefix"); } public void runRoutingTemplateHandlerTests(String prefix) throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + prefix + "/foo"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("foo", HttpClientUtils.readResponse(result)); HttpDelete delete = new HttpDelete(DefaultServer.getDefaultServerURL() + prefix + "/foo"); result = client.execute(delete); Assert.assertEquals(StatusCodes.METHOD_NOT_ALLOWED, result.getStatusLine().getStatusCode()); Assert.assertEquals("", HttpClientUtils.readResponse(result)); HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + prefix + "/foo"); result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("posted foo", HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + prefix + "/foo"); get.addHeader("SomeHeader", "value"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("foo", HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + prefix + "/foo"); get.addHeader("SomeHeader", "special"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("special foo", HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + prefix + "/foo/a"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("foo-path[a]", HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + prefix + "/baz"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("baz", HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + prefix + "/baz/a"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("baz-path[a]", HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + prefix + "/bar"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("GET bar", HttpClientUtils.readResponse(result)); post = new HttpPost(DefaultServer.getDefaultServerURL() + prefix + "/bar"); result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("POST bar", HttpClientUtils.readResponse(result)); HttpPut put = new HttpPut(DefaultServer.getDefaultServerURL() + prefix + "/bar"); result = client.execute(put); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("PUT bar", HttpClientUtils.readResponse(result)); delete = new HttpDelete(DefaultServer.getDefaultServerURL() + prefix + "/bar"); result = client.execute(delete); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("DELETE bar", HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + prefix); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("GET /", HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + prefix + "/scoop/scoop"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("SCOOP GET", HttpClientUtils.readResponse(result)); post = new HttpPost(DefaultServer.getDefaultServerURL() + prefix + "/scoop/scoop"); result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("SCOOP POST", HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } @Test public void testWildCardRoutingTemplateHandler() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/wild/test/card"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("wild:[test]:[card]", HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/wilder/test/card"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("wilder:[test/card]", HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/wildestBeast"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("wildest:[Beast]", HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } } SameSiteCookieHandlerTestCase.java000066400000000000000000000461711420065311100340700ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.IOException; import io.undertow.util.Headers; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.params.CoreProtocolPNames; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.junit.Assert; import org.junit.Test; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.TestHttpClient; import io.undertow.util.FileUtils; import io.undertow.util.StatusCodes; import org.junit.runner.RunWith; @RunWith(DefaultServer.class) public class SameSiteCookieHandlerTestCase { @Test public void testStrict() throws IOException { DefaultServer.setRootHandler(new SameSiteCookieHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.setResponseCookie(new CookieImpl("foo", "bar")); } }, "Strict", "foo")); DefaultServer.startSSLServer(); TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress()); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header header = result.getFirstHeader("set-cookie"); Assert.assertEquals("foo=bar; SameSite=Strict", header.getValue()); FileUtils.readFile(result.getEntity().getContent()); } finally { client.getConnectionManager().shutdown(); DefaultServer.stopSSLServer(); } } @Test public void testLax() throws IOException { DefaultServer.setRootHandler(new SameSiteCookieHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.setResponseCookie(new CookieImpl("foo", "bar")); } }, "Lax", "foo")); DefaultServer.startSSLServer(); TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress()); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header header = result.getFirstHeader("set-cookie"); Assert.assertEquals("foo=bar; SameSite=Lax", header.getValue()); FileUtils.readFile(result.getEntity().getContent()); } finally { client.getConnectionManager().shutdown(); DefaultServer.stopSSLServer(); } } @Test public void testNone() throws IOException { DefaultServer.setRootHandler(new SameSiteCookieHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.setResponseCookie(new CookieImpl("foo", "bar")); } }, "None", "foo")); DefaultServer.startSSLServer(); TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress()); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header header = result.getFirstHeader("set-cookie"); Assert.assertEquals("foo=bar; secure; SameSite=None", header.getValue()); FileUtils.readFile(result.getEntity().getContent()); } finally { client.getConnectionManager().shutdown(); DefaultServer.stopSSLServer(); } } @Test public void testInvalidMode() throws IOException { DefaultServer.setRootHandler(new SameSiteCookieHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.setResponseCookie(new CookieImpl("foo", "bar")); } }, "invalidmode", "foo")); DefaultServer.startSSLServer(); TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress()); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header header = result.getFirstHeader("set-cookie"); Assert.assertEquals("foo=bar", header.getValue()); // invalid mode is ignored FileUtils.readFile(result.getEntity().getContent()); } finally { client.getConnectionManager().shutdown(); DefaultServer.stopSSLServer(); } } @Test public void testRegexPattern() throws IOException { DefaultServer.setRootHandler(new SameSiteCookieHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.setResponseCookie(new CookieImpl("foo", "bar")); } }, "Lax", "fo.*")); DefaultServer.startSSLServer(); TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress()); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header header = result.getFirstHeader("set-cookie"); Assert.assertEquals("foo=bar; SameSite=Lax", header.getValue()); FileUtils.readFile(result.getEntity().getContent()); } finally { client.getConnectionManager().shutdown(); DefaultServer.stopSSLServer(); } } @Test public void testCaseInsensitivePattern() throws IOException { DefaultServer.setRootHandler(new SameSiteCookieHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.setResponseCookie(new CookieImpl("foo", "bar")); } }, "Lax", "FOO", false)); DefaultServer.startSSLServer(); TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress()); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header header = result.getFirstHeader("set-cookie"); Assert.assertEquals("foo=bar; SameSite=Lax", header.getValue()); FileUtils.readFile(result.getEntity().getContent()); } finally { client.getConnectionManager().shutdown(); DefaultServer.stopSSLServer(); } } @Test public void testPatternUnmatched() throws IOException { DefaultServer.setRootHandler(new SameSiteCookieHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.setResponseCookie(new CookieImpl("foo", "bar")); } }, "Lax", "FO.*")); DefaultServer.startSSLServer(); TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress()); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header header = result.getFirstHeader("set-cookie"); Assert.assertEquals("foo=bar", header.getValue()); FileUtils.readFile(result.getEntity().getContent()); } finally { client.getConnectionManager().shutdown(); DefaultServer.stopSSLServer(); } } @Test public void testAllCookies() throws IOException { DefaultServer.setRootHandler(new SameSiteCookieHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.setResponseCookie(new CookieImpl("foo", "bar")); exchange.setResponseCookie(new CookieImpl("baz", "qux")); exchange.setResponseCookie(new CookieImpl("test", "test")); } }, "Strict")); DefaultServer.startSSLServer(); TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress()); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] headerArray = result.getHeaders("set-cookie"); for (Header h : headerArray) { if (h.getValue().contains("foo")) { Assert.assertEquals("foo=bar; SameSite=Strict", h.getValue()); } if (h.getValue().contains("baz")) { Assert.assertEquals("baz=qux; SameSite=Strict", h.getValue()); } if (h.getValue().contains("test")) { Assert.assertEquals("test=test; SameSite=Strict", h.getValue()); } } FileUtils.readFile(result.getEntity().getContent()); } finally { client.getConnectionManager().shutdown(); DefaultServer.stopSSLServer(); } } @Test public void testMultipleCookiesMatched() throws IOException { DefaultServer.setRootHandler(new SameSiteCookieHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.setResponseCookie(new CookieImpl("foo", "bar")); exchange.setResponseCookie(new CookieImpl("baz", "qux")); exchange.setResponseCookie(new CookieImpl("test", "test")); } }, "Lax", "foo|baz")); DefaultServer.startSSLServer(); TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress()); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] headerArray = result.getHeaders("set-cookie"); for (Header h : headerArray) { if (h.getValue().contains("foo")) { Assert.assertEquals("foo=bar; SameSite=Lax", h.getValue()); } if (h.getValue().contains("baz")) { Assert.assertEquals("baz=qux; SameSite=Lax", h.getValue()); } if (h.getValue().contains("test")) { Assert.assertEquals("test=test", h.getValue()); } } FileUtils.readFile(result.getEntity().getContent()); } finally { client.getConnectionManager().shutdown(); DefaultServer.stopSSLServer(); } } @Test public void testNoneIncompatibleUA() throws IOException { DefaultServer.setRootHandler(new SameSiteCookieHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.setResponseCookie(new CookieImpl("foo", "bar")); } }, "None", "foo")); DefaultServer.startSSLServer(); TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress()); // Chrome version whic is known to be incompatible with the `SameSite=None` attribute get.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header header = result.getFirstHeader("set-cookie"); Assert.assertEquals("foo=bar", header.getValue()); FileUtils.readFile(result.getEntity().getContent()); } finally { client.getConnectionManager().shutdown(); DefaultServer.stopSSLServer(); } } @Test public void testNoneUACheckerDisabled() throws IOException { DefaultServer.setRootHandler(new SameSiteCookieHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.setResponseCookie(new CookieImpl("foo", "bar")); } }, "None", "foo", true, false, true)); DefaultServer.startSSLServer(); TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress()); // Chrome version whic is known to be incompatible with the `SameSite=None` attribute get.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header header = result.getFirstHeader("set-cookie"); Assert.assertEquals("foo=bar; secure; SameSite=None", header.getValue()); FileUtils.readFile(result.getEntity().getContent()); } finally { client.getConnectionManager().shutdown(); DefaultServer.stopSSLServer(); } } @Test public void testNoneUACheckerEnabledAlthoughUAHeaderEmpty() throws IOException { DefaultServer.setRootHandler(new SameSiteCookieHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) { exchange.setResponseCookie(new CookieImpl("foo", "bar")); } }, "None", "foo")); DefaultServer.startSSLServer(); TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress()); // Use empty User-Agent header get.setHeader(Headers.USER_AGENT.toString(), ""); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header header = result.getFirstHeader("set-cookie"); Assert.assertEquals("foo=bar; secure; SameSite=None", header.getValue()); FileUtils.readFile(result.getEntity().getContent()); } finally { client.getConnectionManager().shutdown(); DefaultServer.stopSSLServer(); } } @Test public void testNoneUACheckerEnabledAlthoughUAHeaderNotSet() throws IOException { DefaultServer.setRootHandler(new SameSiteCookieHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) { exchange.setResponseCookie(new CookieImpl("foo", "bar")); } }, "None", "foo")); DefaultServer.startSSLServer(); TestHttpClient client = new TestHttpClient() { // Here we need to get client instance that does not set ANY User-Agent header by default. @Override protected HttpParams createHttpParams() { HttpParams params = super.createHttpParams(); params.removeParameter(CoreProtocolPNames.USER_AGENT); HttpConnectionParams.setSoTimeout(params, 30000); return params; } }; client.setSSLContext(DefaultServer.getClientSSLContext()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress()); // Don't set any User-Agent header HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header header = result.getFirstHeader("set-cookie"); Assert.assertEquals("foo=bar; secure; SameSite=None", header.getValue()); FileUtils.readFile(result.getEntity().getContent()); } finally { client.getConnectionManager().shutdown(); DefaultServer.stopSSLServer(); } } @Test public void testNoneWithoutSecure() throws IOException { DefaultServer.setRootHandler(new SameSiteCookieHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.setResponseCookie(new CookieImpl("foo", "bar")); } }, "None", "foo", true, true, false)); DefaultServer.startSSLServer(); TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress()); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header header = result.getFirstHeader("set-cookie"); Assert.assertEquals("foo=bar; SameSite=None", header.getValue()); FileUtils.readFile(result.getEntity().getContent()); } finally { client.getConnectionManager().shutdown(); DefaultServer.stopSSLServer(); } } } SecureCookieHandlerTestCase.java000066400000000000000000000054251420065311100336010ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.IOException; import java.security.GeneralSecurityException; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.TestHttpClient; import io.undertow.util.FileUtils; import io.undertow.util.StatusCodes; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class SecureCookieHandlerTestCase { @Test public void testSecureCookieHandler() throws IOException, GeneralSecurityException { DefaultServer.setRootHandler(new SecureCookieHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.setResponseCookie(new CookieImpl("foo", "bar")); } })); DefaultServer.startSSLServer(); TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress()); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header header = result.getFirstHeader("set-cookie"); Assert.assertEquals("foo=bar; secure", header.getValue()); FileUtils.readFile(result.getEntity().getContent()); get = new HttpGet(DefaultServer.getDefaultServerURL()); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); header = result.getFirstHeader("set-cookie"); Assert.assertEquals("foo=bar", header.getValue()); } finally { client.getConnectionManager().shutdown(); DefaultServer.stopSSLServer(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/SenderTestCase.java000066400000000000000000000252421420065311100312210ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.DataInputStream; import java.io.IOException; import java.net.URI; import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import io.undertow.io.IoCallback; import io.undertow.io.Sender; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.ProxyIgnore; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class SenderTestCase { public static final int SENDS = 10000; public static final int TXS = 1000; public static final String HELLO_WORLD = "Hello World"; @BeforeClass public static void setup() { HttpHandler lotsOfSendsHandler = new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { boolean blocking = exchange.getQueryParameters().get("blocking").getFirst().equals("true"); if (blocking) { if (exchange.isInIoThread()) { exchange.startBlocking(); exchange.dispatch(this); return; } } final Sender sender = exchange.getResponseSender(); class SendClass implements Runnable, IoCallback { int sent = 0; @Override public void run() { sent++; sender.send("a", this); } @Override public void onComplete(final HttpServerExchange exchange, final Sender sender) { if (sent++ == SENDS) { sender.close(); return; } sender.send("a", this); } @Override public void onException(final HttpServerExchange exchange, final Sender sender, final IOException exception) { exception.printStackTrace(); exchange.endExchange(); } } new SendClass().run(); } }; HttpHandler lotsOfTransferHandler = new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { boolean blocking = exchange.getQueryParameters().get("blocking").getFirst().equals("true"); if (blocking) { if (exchange.isInIoThread()) { exchange.startBlocking(); exchange.dispatch(this); return; } } URI uri = SenderTestCase.class.getResource(SenderTestCase.class.getSimpleName() + ".class").toURI(); Path file = Paths.get(uri); final FileChannel channel = FileChannel.open(file, StandardOpenOption.READ); exchange.setResponseContentLength(channel.size() * TXS); final Sender sender = exchange.getResponseSender(); class SendClass implements Runnable, IoCallback { int sent = 0; @Override public void run() { sent++; try { channel.position(0); } catch (IOException e) { } sender.transferFrom(channel, this); } @Override public void onComplete(final HttpServerExchange exchange, final Sender sender) { if (sent++ == TXS) { sender.close(); return; } try { channel.position(0); } catch (IOException e) { } sender.transferFrom(channel, this); } @Override public void onException(final HttpServerExchange exchange, final Sender sender, final IOException exception) { exception.printStackTrace(); exchange.endExchange(); } } new SendClass().run(); } }; final HttpHandler fixedLengthSender = new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send(HELLO_WORLD); } }; PathHandler handler = new PathHandler().addPrefixPath("/lots", lotsOfSendsHandler) .addPrefixPath("/fixed", fixedLengthSender) .addPrefixPath("/transfer", lotsOfTransferHandler); DefaultServer.setRootHandler(handler); } @Test public void testAsyncSender() throws IOException { StringBuilder sb = new StringBuilder(SENDS); for (int i = 0; i < SENDS; ++i) { sb.append("a"); } HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/lots?blocking=false"); TestHttpClient client = new TestHttpClient(); try { HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(sb.toString(), HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } @Test @ProxyIgnore("UNDERTOW-1926 fails with proxy http2 sporadically") // FIXME public void testAsyncTransfer() throws Exception { StringBuilder sb = new StringBuilder(TXS); for (int i = 0; i < TXS; ++i) { sb.append("a"); } HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/transfer?blocking=false"); TestHttpClient client = new TestHttpClient(); try { HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Path file = Paths.get(SenderTestCase.class.getResource(SenderTestCase.class.getSimpleName() + ".class").toURI()); long length = Files.size(file); byte[] data = new byte[(int) length * TXS]; for (int i = 0; i < TXS; i++) { try(DataInputStream is = new DataInputStream(Files.newInputStream(file))) { is.readFully(data, (int) (i * length), (int) length); } } Assert.assertArrayEquals(data, HttpClientUtils.readRawResponse(result)); } finally { client.getConnectionManager().shutdown(); } } @Test @ProxyIgnore("UNDERTOW-1926 fails with proxy http2 sporadically") // FIXME public void testSyncTransfer() throws Exception { StringBuilder sb = new StringBuilder(TXS); for (int i = 0; i < TXS; ++i) { sb.append("a"); } HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/transfer?blocking=true"); TestHttpClient client = new TestHttpClient(); try { HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Path file = Paths.get(SenderTestCase.class.getResource(SenderTestCase.class.getSimpleName() + ".class").toURI()); long length = Files.size(file); byte[] data = new byte[(int) length * TXS]; for (int i = 0; i < TXS; i++) { try(DataInputStream is = new DataInputStream(Files.newInputStream(file))) { is.readFully(data, (int) (i * length), (int) length); } } Assert.assertArrayEquals(data, HttpClientUtils.readRawResponse(result)); } finally { client.getConnectionManager().shutdown(); } } @Test public void testBlockingSender() throws IOException { StringBuilder sb = new StringBuilder(SENDS); for (int i = 0; i < SENDS; ++i) { sb.append("a"); } HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/lots?blocking=true"); TestHttpClient client = new TestHttpClient(); try { HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(sb.toString(), HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } @Test public void testSenderSetsContentLength() throws IOException { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/fixed"); TestHttpClient client = new TestHttpClient(); try { HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(HELLO_WORLD, HttpClientUtils.readResponse(result)); Header[] header = result.getHeaders(Headers.CONTENT_LENGTH_STRING); Assert.assertEquals(1, header.length); Assert.assertEquals("" + HELLO_WORLD.length(), header[0].getValue()); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/SetAttributeTestCase.java000066400000000000000000000125641420065311100324230ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.Handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.util.Deque; import java.util.Map; import static io.undertow.Handlers.path; import static io.undertow.Handlers.rewrite; /** * Tests the redirect handler * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class SetAttributeTestCase { @Test public void testSettingHeader() throws IOException { DefaultServer.setRootHandler(Handlers.setAttribute(ResponseCodeHandler.HANDLE_200, "%{o,Foo}", "%U-%{q,p1}", SetAttributeHandler.class.getClassLoader())); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path/a"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); Assert.assertEquals("/path/a-", result.getHeaders("foo")[0].getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path/a?p1=someQp"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); Assert.assertEquals("/path/a-someQp", result.getHeaders("foo")[0].getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path/a?p1=someQp&p1=value2"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); Assert.assertEquals("/path/a-[someQp, value2]", result.getHeaders("foo")[0].getValue()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testRewrite() throws IOException { DefaultServer.setRootHandler( rewrite("regex['/somePath/(.*)']", "/otherPath/$1", getClass().getClassLoader(), path() .addPrefixPath("/otherPath", new InfoHandler()) .addPrefixPath("/relative", rewrite("path-template['/foo/{bar}/{woz}']", "/foo?bar=${bar}&woz=${woz}", getClass().getClassLoader(), new InfoHandler())) )); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/relative/foo/a/b"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("URI: /relative/foo relative: /foo QS:bar=a&woz=b bar: a woz: b", response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/somePath/foo/a/b"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("URI: /otherPath/foo/a/b relative: /foo/a/b QS:", response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/somePath/foo?a=b"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("URI: /otherPath/foo relative: /foo QS:a=b a: b", response); } finally { client.getConnectionManager().shutdown(); } } private class InfoHandler implements HttpHandler { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { final StringBuilder sb = new StringBuilder("URI: " + exchange.getRequestURI() + " relative: " + exchange.getRelativePath() + " QS:" + exchange.getQueryString()); for (Map.Entry> param : exchange.getQueryParameters().entrySet()) { sb.append(" " + param.getKey() + ": " + param.getValue().getFirst()); } exchange.getResponseSender().send(sb.toString()); } } } SimpleNonBlockingServerTestCase.java000066400000000000000000000071721420065311100344700ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.IOException; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.HttpVersion; import org.apache.http.client.methods.HttpGet; import org.apache.http.params.CoreProtocolPNames; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class SimpleNonBlockingServerTestCase { @BeforeClass public static void setup() { DefaultServer.setRootHandler(new SetHeaderHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("hi all"); } }, "MyHeader", "MyValue")); } @Test public void sendHttpRequest() throws IOException, InterruptedException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] header = result.getHeaders("MyHeader"); Assert.assertEquals("MyValue", header[0].getValue()); } finally { client.getConnectionManager().shutdown(); } } @Test public void sendHttp11RequestWithClose() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.addHeader("Connection", "close"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] header = result.getHeaders("MyHeader"); Assert.assertEquals("MyValue", header[0].getValue()); } finally { client.getConnectionManager().shutdown(); } } @Test public void sendHttpOneZeroRequest() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_0); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] header = result.getHeaders("MyHeader"); Assert.assertEquals("MyValue", header[0].getValue()); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/URLDecodingHandlerTestCase.java000066400000000000000000000165131420065311100333770ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.Undertow; import io.undertow.UndertowOptions; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.RoutingHandler; import io.undertow.testutils.TestHttpClient; import io.undertow.util.PathTemplateMatch; import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Test; import java.io.IOException; /** * @author Carter Kozak */ public class URLDecodingHandlerTestCase { private static int PORT = 7890; @Test public void testDoesNotDecodeByDefault() throws Exception { // By default Undertow decodes upon accepting requests, see UndertowOptions.DECODE_URL. // If this is enabled, the URLDecodingHandler should no-op Undertow undertow = Undertow.builder() .addHttpListener(PORT, "0.0.0.0") .setHandler(new URLDecodingHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send(exchange.getRelativePath()); } }, "UTF-8")) .build(); undertow.start(); try { TestHttpClient client = new TestHttpClient(); // '%253E' decodes to '%3E', which would decode to '>' if decoded twice try (CloseableHttpResponse response = client.execute(new HttpGet("http://localhost:" + PORT + "/%253E"))) { Assert.assertEquals("/%3E", getResponseString(response)); } } finally { undertow.stop(); // sleep 1 s to prevent BindException (Address already in use) when restarting the server try { Thread.sleep(1000); } catch (InterruptedException ignore) {} } } @Test public void testDecodesWhenUrlDecodingIsDisabled() throws Exception { // When UndertowOptions.DECODE_URL is disabled, the URLDecodingHandler should decode values. Undertow undertow = Undertow.builder() .setServerOption(UndertowOptions.DECODE_URL, false) .addHttpListener(PORT, "0.0.0.0") .setHandler(new URLDecodingHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send(exchange.getRelativePath()); } }, "UTF-8")) .build(); undertow.start(); try { TestHttpClient client = new TestHttpClient(); // '%253E' decodes to '%3E', which would decode to '>' if decoded twice try (CloseableHttpResponse response = client.execute(new HttpGet("http://localhost:" + PORT + "/%253E"))) { Assert.assertEquals("/%3E", getResponseString(response)); } } finally { undertow.stop(); // sleep 1 s to prevent BindException (Address already in use) when restarting the server try { Thread.sleep(1000); } catch (InterruptedException ignore) {} } } @Test public void testDecodeCharactersInMatchedPaths() throws Exception { // When UndertowOptions.DECODE_URL is disabled, the URLDecodingHandler should decode values. Undertow undertow = Undertow.builder() .setServerOption(UndertowOptions.DECODE_URL, false) .addHttpListener(PORT, "0.0.0.0") .setHandler(new RoutingHandler().get("/api/{pathParam}/tail", new URLDecodingHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { String matched = exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY) .getParameters().get("pathParam"); exchange.getResponseSender().send(matched); } }, "UTF-8"))) .build(); undertow.start(); try { TestHttpClient client = new TestHttpClient(); // '%253E' decodes to '%3E', which would decode to '>' if decoded twice try (CloseableHttpResponse response = client.execute( new HttpGet("http://localhost:" + PORT + "/api/test%2Ftest+test%2Btest%20test/tail"))) { Assert.assertEquals("test/test+test+test test", getResponseString(response)); } } finally { undertow.stop(); // sleep 1 s to prevent BindException (Address already in use) when restarting the server try { Thread.sleep(1000); } catch (InterruptedException ignore) {} } } @Test public void testMultipleURLDecodingHandlers() throws Exception { // When multiple URLDecodingHandler are present, only the first handler to consume an exchange should decode Undertow undertow = Undertow.builder() .setServerOption(UndertowOptions.DECODE_URL, false) .addHttpListener(PORT, "0.0.0.0") .setHandler(new URLDecodingHandler(new URLDecodingHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send(exchange.getRelativePath()); } }, "UTF-8"), "UTF-8")) .build(); undertow.start(); try { TestHttpClient client = new TestHttpClient(); // '%253E' decodes to '%3E', which would decode to '>' if decoded twice try (CloseableHttpResponse response = client.execute(new HttpGet("http://localhost:" + PORT + "/%253E"))) { Assert.assertEquals("/%3E", getResponseString(response)); } } finally { undertow.stop(); // sleep 1 s to prevent BindException (Address already in use) when restarting the server try { Thread.sleep(1000); } catch (InterruptedException ignore) {} } } private static String getResponseString(CloseableHttpResponse response) throws IOException { Assert.assertEquals(200, response.getStatusLine().getStatusCode()); return IOUtils.toString(response.getEntity().getContent(), "UTF-8"); } } UserAgentAccessControlHandlerUnitTestCase.java000066400000000000000000000100541420065311100364330ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import io.undertow.testutils.category.UnitTest; import org.junit.Test; import org.junit.experimental.categories.Category; import java.net.UnknownHostException; import static io.undertow.attribute.ExchangeAttributes.requestHeader; import static io.undertow.util.Headers.USER_AGENT; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** * Unit tests for peer security handler * * @author Andre Dietisheim */ @Category(UnitTest.class) public class UserAgentAccessControlHandlerUnitTestCase { private static final String PATTERN_IE_ALL = "Mozilla.+\\(compatible; MSIE .+"; private static final String PATTERN_IE_ALL_ABOVE_6 = "Mozilla.+\\(compatible; MSIE ([7-9]|1[0-9]).+"; private static final String PATTERN_FF_ALL = "Mozilla.+\\(.+ Gecko.* Firefox.+"; private static final String IE_6 = "Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)"; private static final String IE_10 = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"; private static final String FF_25 = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0"; private static final String SAFARI = "Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25"; @Test(expected = IllegalArgumentException.class) public void testInvalidPattern() { new AccessControlListHandler(requestHeader(USER_AGENT)).addAllow("[bogus"); } @Test public void testFalseDefault() { assertFalse(new AccessControlListHandler(requestHeader(USER_AGENT)).setDefaultAllow(false).isAllowed("some useragent")); } @Test public void testTrueDefault() throws UnknownHostException { assertTrue(new AccessControlListHandler(requestHeader(USER_AGENT)).setDefaultAllow(true).isAllowed("some useragent")); } @Test public void testNullUserAgent() { assertTrue(new AccessControlListHandler(requestHeader(USER_AGENT)).setDefaultAllow(true).isAllowed(null)); } @Test public void testAllowAllButOne() throws UnknownHostException { AccessControlListHandler handler = new AccessControlListHandler(requestHeader(USER_AGENT)) .setDefaultAllow(true) .addDeny(PATTERN_IE_ALL); assertFalse(handler.isAllowed(IE_6)); assertTrue(handler.isAllowed(FF_25)); } @Test public void testDenyAllButOne() throws UnknownHostException { AccessControlListHandler handler = new AccessControlListHandler(requestHeader(USER_AGENT)) .setDefaultAllow(false) .addAllow(PATTERN_FF_ALL); assertTrue(handler.isAllowed(FF_25)); assertFalse(handler.isAllowed(IE_10)); } @Test public void testAllowIE6AndAboveAndAllOthers() throws UnknownHostException { AccessControlListHandler handler = new AccessControlListHandler(requestHeader(USER_AGENT)) .setDefaultAllow(true) .addAllow(PATTERN_IE_ALL_ABOVE_6) .addDeny(PATTERN_IE_ALL); assertFalse(handler.isAllowed(IE_6)); assertTrue(handler.isAllowed(IE_10)); assertTrue(handler.isAllowed(FF_25)); assertTrue(handler.isAllowed(SAFARI)); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/VirtualHostTestCase.java000066400000000000000000000056471420065311100322740ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers; import java.io.IOException; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.NetworkUtils; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class VirtualHostTestCase { /** * Tests the Origin header is respected when the strictest options are selected */ @Test public void testVirtualHost() throws IOException { TestHttpClient client = new TestHttpClient(); try { final NameVirtualHostHandler handler = new NameVirtualHostHandler() .addHost(NetworkUtils.formatPossibleIpv6Address(DefaultServer.getHostAddress("default")), new SetHeaderHandler(ResponseCodeHandler.HANDLE_200, "myHost", "localhost")) .setDefaultHandler(new SetHeaderHandler(ResponseCodeHandler.HANDLE_200, "myHost", "default")); DefaultServer.setRootHandler(handler); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); HttpResponse result = client.execute(get); //no origin header, we dny by default Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] header = result.getHeaders("myHost"); Assert.assertEquals("localhost", header[0].getValue()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.addHeader("Host", "otherHost"); result = client.execute(get); //no origin header, we dny by default Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); header = result.getHeaders("myHost"); Assert.assertEquals("default", header[0].getValue()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/accesslog/000077500000000000000000000000001420065311100274405ustar00rootroot00000000000000AccessLogFileTestCase.java000066400000000000000000000244051420065311100343300ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/accesslog/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.accesslog; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.CompletionLatchHandler; import io.undertow.util.FileUtils; /** * Tests writing the access log to a file * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class AccessLogFileTestCase { private static final Path logDirectory = Paths.get(System.getProperty("java.io.tmpdir"), "logs"); private static final int NUM_THREADS = 10; private static final int NUM_REQUESTS = 12; @Before public void before() throws IOException { Files.createDirectories(logDirectory); } @After public void after() throws IOException { FileUtils.deleteRecursive(logDirectory); } private static final HttpHandler HELLO_HANDLER = new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("Hello"); } }; @Test public void testSingleLogMessageToFile() throws IOException, InterruptedException { Path directory = logDirectory; Path logFileName = directory.resolve("server1.log"); DefaultAccessLogReceiver logReceiver = new DefaultAccessLogReceiver(DefaultServer.getWorker(), directory, "server1."); verifySingleLogMessageToFile(logFileName, logReceiver); } @Test public void testSingleLogMessageToFileWithSuffix() throws IOException, InterruptedException { Path directory = logDirectory; Path logFileName = directory.resolve("server1.logsuffix"); DefaultAccessLogReceiver logReceiver = new DefaultAccessLogReceiver(DefaultServer.getWorker(), directory, "server1.", "logsuffix"); verifySingleLogMessageToFile(logFileName, logReceiver); } private void verifySingleLogMessageToFile(Path logFileName, DefaultAccessLogReceiver logReceiver) throws IOException, InterruptedException { CompletionLatchHandler latchHandler; DefaultServer.setRootHandler(latchHandler = new CompletionLatchHandler(new AccessLogHandler(HELLO_HANDLER, logReceiver, "Remote address %a Code %s test-header %{i,test-header} %{i,non-existent} %{i,dup}", AccessLogFileTestCase.class.getClassLoader()))); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.addHeader("test-header", "single-val"); get.addHeader("dup", "d"); //we can't rely on ordering, so we just send the same thing twice to make the comparison easy get.addHeader("dup", "d"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("Hello", HttpClientUtils.readResponse(result)); latchHandler.await(); logReceiver.awaitWrittenForTest(); Assert.assertEquals("Remote address " + DefaultServer.getDefaultServerAddress().getAddress().getHostAddress() + " Code 200 test-header single-val - [d, d]" + System.lineSeparator(), new String(Files.readAllBytes(logFileName))); } finally { client.getConnectionManager().shutdown(); } } @Test public void testLogLotsOfThreads() throws IOException, InterruptedException, ExecutionException { Path directory = logDirectory; Path logFileName = directory.resolve("server2.log"); DefaultAccessLogReceiver logReceiver = new DefaultAccessLogReceiver(DefaultServer.getWorker(), directory, "server2."); CompletionLatchHandler latchHandler; DefaultServer.setRootHandler(latchHandler = new CompletionLatchHandler(NUM_REQUESTS * NUM_THREADS, new AccessLogHandler(HELLO_HANDLER, logReceiver, "REQ %{i,test-header}", AccessLogFileTestCase.class.getClassLoader()))); ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS); try { final List> futures = new ArrayList<>(); for (int i = 0; i < NUM_THREADS; ++i) { final int threadNo = i; futures.add(executor.submit(new Runnable() { @Override public void run() { TestHttpClient client = new TestHttpClient(); try { for (int i = 0; i < NUM_REQUESTS; ++i) { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.addHeader("test-header", "thread-" + threadNo + "-request-" + i); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("Hello", response); } } catch (IOException e) { throw new RuntimeException(e); } finally { client.getConnectionManager().shutdown(); } } })); } for (Future future : futures) { future.get(); } } finally { executor.shutdown(); } latchHandler.await(); logReceiver.awaitWrittenForTest(); String completeLog = new String(Files.readAllBytes(logFileName)); for (int i = 0; i < NUM_THREADS; ++i) { for (int j = 0; j < NUM_REQUESTS; ++j) { Assert.assertTrue(completeLog.contains("REQ thread-" + i + "-request-" + j)); } } } @Test public void testForcedLogRotation() throws IOException, InterruptedException { Path logFileName = logDirectory.resolve("server.log"); DefaultAccessLogReceiver logReceiver = new DefaultAccessLogReceiver(DefaultServer.getWorker(), logDirectory, "server."); CompletionLatchHandler latchHandler; DefaultServer.setRootHandler(latchHandler = new CompletionLatchHandler(new AccessLogHandler(HELLO_HANDLER, logReceiver, "Remote address %a Code %s test-header %{i,test-header}", AccessLogFileTestCase.class.getClassLoader()))); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.addHeader("test-header", "v1"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("Hello", HttpClientUtils.readResponse(result)); latchHandler.await(); latchHandler.reset(); logReceiver.awaitWrittenForTest(); Assert.assertEquals("Remote address " + DefaultServer.getDefaultServerAddress().getAddress().getHostAddress() + " Code 200 test-header v1" + System.lineSeparator(), new String(Files.readAllBytes(logFileName))); logReceiver.rotate(); logReceiver.awaitWrittenForTest(); Assert.assertFalse(Files.exists(logFileName)); Path firstLogRotate = logDirectory.resolve("server." + new SimpleDateFormat("yyyy-MM-dd").format(new Date()) + ".log"); Assert.assertEquals("Remote address " + DefaultServer.getDefaultServerAddress().getAddress().getHostAddress() + " Code 200 test-header v1" + System.lineSeparator(), new String(Files.readAllBytes(firstLogRotate))); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.addHeader("test-header", "v2"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("Hello", HttpClientUtils.readResponse(result)); latchHandler.await(); latchHandler.reset(); logReceiver.awaitWrittenForTest(); Assert.assertEquals("Remote address " + DefaultServer.getDefaultServerAddress().getAddress().getHostAddress() + " Code 200 test-header v2" + System.lineSeparator(), new String(Files.readAllBytes(logFileName))); logReceiver.rotate(); logReceiver.awaitWrittenForTest(); Assert.assertFalse(Files.exists(logFileName)); Path secondLogRotate = logDirectory.resolve("server." + new SimpleDateFormat("yyyy-MM-dd").format(new Date()) + "-1.log"); Assert.assertEquals("Remote address " + DefaultServer.getDefaultServerAddress().getAddress().getHostAddress() + " Code 200 test-header v2" + System.lineSeparator(), new String(Files.readAllBytes(secondLogRotate))); } finally { client.getConnectionManager().shutdown(); } } } AccessLogTestCase.java000066400000000000000000000063041420065311100335260ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/accesslog/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.accesslog; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.StoredResponseHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import java.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class AccessLogTestCase { private static volatile String message; private volatile CountDownLatch latch; private final AccessLogReceiver RECEIVER = new AccessLogReceiver() { @Override public void logMessage(final String msg) { message = msg; latch.countDown(); } }; private static final HttpHandler HELLO_HANDLER = new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain"); exchange.getResponseSender().send("HelloResponse"); } }; @Test public void testRemoteAddress() throws IOException, InterruptedException { latch = new CountDownLatch(1); DefaultServer.setRootHandler(new StoredResponseHandler(new AccessLogHandler(HELLO_HANDLER, RECEIVER, "Remote address %a Code %s test-header %{i,test-header} %{STORED_RESPONSE}", AccessLogFileTestCase.class.getClassLoader()))); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.addHeader("test-header", "test-value"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("HelloResponse", HttpClientUtils.readResponse(result)); latch.await(10, TimeUnit.SECONDS); Assert.assertEquals("Remote address " + DefaultServer.getDefaultServerAddress().getAddress().getHostAddress() + " Code 200 test-header test-value HelloResponse", message); } finally { client.getConnectionManager().shutdown(); } } } ExtendedAccessLogFileTestCase.java000066400000000000000000000111261420065311100360050ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/accesslog/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.accesslog; import io.undertow.Version; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.CompletionLatchHandler; import io.undertow.util.FileUtils; import io.undertow.util.HttpString; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; /** * Tests writing the access log to a file * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ExtendedAccessLogFileTestCase { private static final Path logDirectory = Paths.get(System.getProperty("java.io.tmpdir"), "logs"); public static final String PATTERN = "cs-uri cs(test-header) x-O(aa) x-H(secure)"; private DefaultAccessLogReceiver logReceiver; @Before public void before() throws IOException { Files.createDirectories(logDirectory); DefaultServer.startSSLServer(); logReceiver = DefaultAccessLogReceiver.builder().setLogWriteExecutor(DefaultServer.getWorker()) .setOutputDirectory(logDirectory) .setLogBaseName("extended.") .setLogFileHeaderGenerator(new ExtendedAccessLogParser.ExtendedAccessLogHeaderGenerator(PATTERN)).build(); } @After public void after() throws IOException { DefaultServer.stopSSLServer(); FileUtils.deleteRecursive(logDirectory); logReceiver.close(); } private static final HttpHandler HELLO_HANDLER = new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getResponseHeaders().put(new HttpString("aa"), "bb"); exchange.getResponseSender().send("Hello"); } }; @Test public void testSingleLogMessageToFile() throws IOException, InterruptedException { Path logFileName = logDirectory.resolve("extended.log"); verifySingleLogMessageToFile(logFileName, logReceiver); } private void verifySingleLogMessageToFile(Path logFileName, DefaultAccessLogReceiver logReceiver) throws IOException, InterruptedException { CompletionLatchHandler latchHandler; DefaultServer.setRootHandler(latchHandler = new CompletionLatchHandler(new AccessLogHandler(HELLO_HANDLER, logReceiver, PATTERN, new ExtendedAccessLogParser( ExtendedAccessLogFileTestCase.class.getClassLoader()).parse(PATTERN)))); TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress() + "/path"); get.addHeader("test-header", "single-val"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("Hello", HttpClientUtils.readResponse(result)); latchHandler.await(); logReceiver.awaitWrittenForTest(); String data = new String(Files.readAllBytes(logFileName)); String[] lines = data.split(System.lineSeparator()); Assert.assertEquals("#Fields: " + PATTERN, lines[0]); Assert.assertEquals("#Version: 2.0", lines[1]); Assert.assertEquals("#Software: " + Version.getFullVersionString(), lines[2]); Assert.assertEquals("", lines[3]); Assert.assertEquals("/path 'single-val' 'bb' true", lines[4]); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/blocking/000077500000000000000000000000001420065311100272655ustar00rootroot00000000000000BlockingServerStreamResetTestCase.java000066400000000000000000000051541420065311100366100ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/blocking/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.blocking; import io.undertow.io.UndertowOutputStream; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.BlockingHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; /** * @author Carter Kozak */ @RunWith(DefaultServer.class) public class BlockingServerStreamResetTestCase { @Test public void testResponseAfterStreamReset() throws IOException { final BlockingHandler blockingHandler = new BlockingHandler(); DefaultServer.setRootHandler(blockingHandler); blockingHandler.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { UndertowOutputStream stream = (UndertowOutputStream) exchange.getOutputStream(); stream.write(1); Assert.assertEquals(1, stream.getBytesWritten()); stream.resetBuffer(); Assert.assertEquals(0, stream.getBytesWritten()); exchange.getOutputStream().write("Hello, World".getBytes()); } }); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("Hello, World", HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } } SimpleBlockingServerTestCase.java000066400000000000000000000235471420065311100356110ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/blocking/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.blocking; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import io.undertow.io.IoCallback; import io.undertow.io.Sender; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.BlockingHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Headers; import io.undertow.util.Methods; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class SimpleBlockingServerTestCase { private static volatile String message; @BeforeClass public static void setup() { final BlockingHandler blockingHandler = new BlockingHandler(); DefaultServer.setRootHandler(blockingHandler); blockingHandler.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) { try { if (exchange.getRequestMethod().equals(Methods.POST)) { //for a post we just echo back what was sent //we need to fully buffer it, as otherwise the send buffer fills up, and the client will still be blocked //on writing and will never read byte[] buffer = new byte[1024]; final ByteArrayOutputStream b = new ByteArrayOutputStream(); int r = 0; final OutputStream outputStream = exchange.getOutputStream(); final InputStream inputStream = exchange.getInputStream(); while ((r = inputStream.read(buffer)) > 0) { b.write(buffer, 0, r); } outputStream.write(b.toByteArray()); outputStream.close(); } else { if (exchange.getQueryParameters().containsKey("useFragmentedSender")) { //we send it byte at a time exchange.getResponseSender().send("", new IoCallback() { int i = 0; @Override public void onComplete(final HttpServerExchange exchange, final Sender sender) { if (i == message.length()) { sender.close(); exchange.endExchange(); } else { sender.send("" + message.charAt(i++), this); } } @Override public void onException(final HttpServerExchange exchange, final Sender sender, final IOException exception) { exchange.endExchange(); } }); } else if (exchange.getQueryParameters().containsKey("useSender")) { exchange.getResponseSender().send(message, IoCallback.END_EXCHANGE); } else { final OutputStream outputStream = exchange.getOutputStream(); outputStream.write(message.getBytes()); outputStream.close(); } } } catch (IOException e) { throw new RuntimeException(e); } } }); } @Test public void sendHttpRequest() throws IOException { message = "My HTTP Request!"; TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(message, HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } @Test public void testHeadRequests() throws IOException { message = "My HTTP Request!"; TestHttpClient client = new TestHttpClient(); HttpHead head = new HttpHead(DefaultServer.getDefaultServerURL() + "/path"); try { for (int i = 0; i < 3; ++i) { //WFLY-1540 run a few requests to make sure persistent re HttpResponse result = client.execute(head); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("", HttpClientUtils.readResponse(result)); Assert.assertEquals(message.length() + "", result.getFirstHeader(Headers.CONTENT_LENGTH_STRING).getValue()); } } finally { client.getConnectionManager().shutdown(); } } @Test public void testDeleteRequests() throws IOException { message = "My HTTP Request!"; TestHttpClient client = new TestHttpClient(); HttpDelete delete = new HttpDelete(DefaultServer.getDefaultServerURL() + "/path"); try { for (int i = 0; i < 3; ++i) { //WFLY-1540 run a few requests to make sure persistent re HttpResponse result = client.execute(delete); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(message, HttpClientUtils.readResponse(result)); } } finally { client.getConnectionManager().shutdown(); } } @Test public void testLargeResponse() throws IOException { final StringBuilder messageBuilder = new StringBuilder(6919638); for (int i = 0; i < 6919638; ++i) { messageBuilder.append("*"); } message = messageBuilder.toString(); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String resultString = HttpClientUtils.readResponse(result); Assert.assertEquals(message.length(), resultString.length()); Assert.assertTrue(message.equals(resultString)); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path?useSender"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String resultBody = HttpClientUtils.readResponse(result); Assert.assertTrue(message.equals(resultBody)); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path?useFragmentedSender"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); resultBody = HttpClientUtils.readResponse(result); Assert.assertTrue(message.equals(resultBody)); } finally { client.getConnectionManager().shutdown(); } } @Test public void testSmallRequest() throws IOException { message = null; TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); post.setEntity(new StringEntity("a")); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertTrue("a".equals(HttpClientUtils.readResponse(result))); } finally { client.getConnectionManager().shutdown(); } } @Test public void testLargeRequest() throws IOException { message = null; final StringBuilder messageBuilder = new StringBuilder(6919638); for (int i = 0; i < 6919638; ++i) { messageBuilder.append("+"); } TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); post.setEntity(new StringEntity(messageBuilder.toString())); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertTrue(messageBuilder.toString().equals(HttpClientUtils.readResponse(result))); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/builder/000077500000000000000000000000001420065311100271235ustar00rootroot00000000000000PredicatedHandlersParserTestCase.java000066400000000000000000000243231420065311100362510ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/builder/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.builder; import io.undertow.testutils.category.UnitTest; import io.undertow.predicate.ContainsPredicate; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.AllowedMethodsHandler; import io.undertow.server.handlers.RequestDumpingHandler; import io.undertow.server.handlers.ResponseCodeHandler; import io.undertow.server.handlers.SetHeaderHandler; import io.undertow.server.handlers.builder.PredicatedHandlersParser.BlockNode; import io.undertow.server.handlers.builder.PredicatedHandlersParser.Node; import io.undertow.server.handlers.builder.PredicatedHandlersParser.PredicateOperatorNode; import io.undertow.util.Headers; import io.undertow.util.HttpString; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import java.util.Arrays; import java.util.HashSet; import java.util.List; /** * @author Stuart Douglas */ @Category(UnitTest.class) public class PredicatedHandlersParserTestCase { @Test public void testAstRepresentation1() { String value = "path(/foo) -> rewrite(/bar)"; Node node = PredicatedHandlersParser.parse(value, PredicatedHandlersParser.tokenize(value)); Assert.assertTrue(node instanceof PredicateOperatorNode); PredicateOperatorNode op = (PredicateOperatorNode) node; Assert.assertEquals("->", op.getToken().getToken()); Assert.assertEquals("path", op.getLeft().getToken().getToken()); Assert.assertEquals("/foo", ((PredicatedHandlersParser.ExpressionNode) op.getLeft()).getValues().get(null).toString()); } @Test public void testAstRepresentation2() { String value = "path(/foo) -> rewrite(/bar)\npath(/foo) -> rewrite(/bar)"; Node node = PredicatedHandlersParser.parse(value, PredicatedHandlersParser.tokenize(value)); Assert.assertTrue(node instanceof BlockNode); BlockNode block = (BlockNode) node; PredicateOperatorNode op = (PredicateOperatorNode) block.getBlock().get(1); Assert.assertEquals("->", op.getToken().getToken()); Assert.assertEquals("path", op.getLeft().getToken().getToken()); Assert.assertEquals("/foo", ((PredicatedHandlersParser.ExpressionNode) op.getLeft()).getValues().get(null).toString()); } @Test public void testAstRepresentation3() { String value = "path(/foo) -> { rewrite(/bar); path(/x) -> rewrite(/x)}"; Node node = PredicatedHandlersParser.parse(value, PredicatedHandlersParser.tokenize(value)); Assert.assertTrue(node instanceof PredicateOperatorNode); PredicateOperatorNode op = (PredicateOperatorNode) node; Assert.assertEquals("->", op.getToken().getToken()); Assert.assertEquals("path", op.getLeft().getToken().getToken()); Assert.assertEquals("/foo", ((PredicatedHandlersParser.ExpressionNode) op.getLeft()).getValues().get(null).toString()); BlockNode block = (BlockNode) op.getRight(); op = (PredicateOperatorNode) block.getBlock().get(1); Assert.assertEquals("->", op.getToken().getToken()); Assert.assertEquals("path", op.getLeft().getToken().getToken()); Assert.assertEquals("/x", ((PredicatedHandlersParser.ExpressionNode) op.getLeft()).getValues().get(null).toString()); } @Test public void testParsedHandler1() { String value = "dump-request"; List ret = PredicatedHandlersParser.parse(value, getClass().getClassLoader()); Assert.assertEquals(1, ret.size()); HttpHandler handler = ret.get(0).getHandler().wrap(ResponseCodeHandler.HANDLE_200); Assert.assertTrue(handler instanceof RequestDumpingHandler); } @Test public void testParsedHandler2() { String value = "header(header=a, value='a%%lb')"; List ret = PredicatedHandlersParser.parse(value, getClass().getClassLoader()); Assert.assertEquals(1, ret.size()); SetHeaderHandler handler = (SetHeaderHandler) ret.get(0).getHandler().wrap(ResponseCodeHandler.HANDLE_200); Assert.assertEquals("a", handler.getHeader().toString()); Assert.assertEquals("a%lb", handler.getValue().readAttribute(null)); } @Test public void testParsedHandler3() { String value = "allowed-methods(GET)"; List ret = PredicatedHandlersParser.parse(value, getClass().getClassLoader()); Assert.assertEquals(1, ret.size()); AllowedMethodsHandler handler = (AllowedMethodsHandler) ret.get(0).getHandler().wrap(ResponseCodeHandler.HANDLE_200); Assert.assertEquals(new HashSet<>(Arrays.asList(HttpString.tryFromString("GET"))), handler.getAllowedMethods()); value = "allowed-methods(methods=GET)"; ret = PredicatedHandlersParser.parse(value, getClass().getClassLoader()); Assert.assertEquals(1, ret.size()); handler = (AllowedMethodsHandler) ret.get(0).getHandler().wrap(ResponseCodeHandler.HANDLE_200); Assert.assertEquals(new HashSet<>(Arrays.asList(HttpString.tryFromString("GET"))), handler.getAllowedMethods()); value = "allowed-methods(methods={GET})"; ret = PredicatedHandlersParser.parse(value, getClass().getClassLoader()); Assert.assertEquals(1, ret.size()); handler = (AllowedMethodsHandler) ret.get(0).getHandler().wrap(ResponseCodeHandler.HANDLE_200); Assert.assertEquals(new HashSet<>(Arrays.asList(HttpString.tryFromString("GET"))), handler.getAllowedMethods()); value = "allowed-methods({GET})"; ret = PredicatedHandlersParser.parse(value, getClass().getClassLoader()); Assert.assertEquals(1, ret.size()); handler = (AllowedMethodsHandler) ret.get(0).getHandler().wrap(ResponseCodeHandler.HANDLE_200); Assert.assertEquals(new HashSet<>(Arrays.asList(HttpString.tryFromString("GET"))), handler.getAllowedMethods()); value = "allowed-methods({GET, POST})"; ret = PredicatedHandlersParser.parse(value, getClass().getClassLoader()); Assert.assertEquals(1, ret.size()); handler = (AllowedMethodsHandler) ret.get(0).getHandler().wrap(ResponseCodeHandler.HANDLE_200); Assert.assertEquals(new HashSet<>(Arrays.asList(HttpString.tryFromString("GET"), HttpString.tryFromString("POST"))), handler.getAllowedMethods()); value = "allowed-methods(methods={GET, POST})"; ret = PredicatedHandlersParser.parse(value, getClass().getClassLoader()); Assert.assertEquals(1, ret.size()); handler = (AllowedMethodsHandler) ret.get(0).getHandler().wrap(ResponseCodeHandler.HANDLE_200); Assert.assertEquals(new HashSet<>(Arrays.asList(HttpString.tryFromString("GET"), HttpString.tryFromString("POST"))), handler.getAllowedMethods()); value = "allowed-methods(GET, POST)"; ret = PredicatedHandlersParser.parse(value, getClass().getClassLoader()); Assert.assertEquals(1, ret.size()); handler = (AllowedMethodsHandler) ret.get(0).getHandler().wrap(ResponseCodeHandler.HANDLE_200); Assert.assertEquals(new HashSet<>(Arrays.asList(HttpString.tryFromString("GET"), HttpString.tryFromString("POST"))), handler.getAllowedMethods()); } @Test public void testParsedPredicatedHandler1() { String value = "contains(value='a', search=b) -> dump-request"; List ret = PredicatedHandlersParser.parse(value, getClass().getClassLoader()); Assert.assertEquals(1, ret.size()); HttpHandler handler = ret.get(0).getHandler().wrap(ResponseCodeHandler.HANDLE_200); Assert.assertTrue(handler instanceof RequestDumpingHandler); ContainsPredicate predicate = (ContainsPredicate) ret.get(0).getPredicate(); Assert.assertEquals("a", predicate.getAttribute().readAttribute(null)); Assert.assertArrayEquals(new String[]{"b"}, predicate.getValues()); value = "contains(value='a', search={b}) -> dump-request"; ret = PredicatedHandlersParser.parse(value, getClass().getClassLoader()); Assert.assertEquals(1, ret.size()); handler = ret.get(0).getHandler().wrap(ResponseCodeHandler.HANDLE_200); Assert.assertTrue(handler instanceof RequestDumpingHandler); predicate = (ContainsPredicate) ret.get(0).getPredicate(); Assert.assertEquals("a", predicate.getAttribute().readAttribute(null)); Assert.assertArrayEquals(new String[]{"b"}, predicate.getValues()); value = "contains[value='a', search={b, c}] -> dump-request"; ret = PredicatedHandlersParser.parse(value, getClass().getClassLoader()); Assert.assertEquals(1, ret.size()); handler = ret.get(0).getHandler().wrap(ResponseCodeHandler.HANDLE_200); Assert.assertTrue(handler instanceof RequestDumpingHandler); predicate = (ContainsPredicate) ret.get(0).getPredicate(); Assert.assertEquals("a", predicate.getAttribute().readAttribute(null)); Assert.assertArrayEquals(new String[]{"b", "c"}, predicate.getValues()); } @Test public void testClearHeader() throws Exception { String value = "set(attribute=%{i,User-Agent}, value=%{NULL})"; List ret = PredicatedHandlersParser.parse(value, getClass().getClassLoader()); Assert.assertEquals(1, ret.size()); HttpServerExchange exchange = new HttpServerExchange(null); exchange.getRequestHeaders().put(Headers.USER_AGENT, "firefox"); ret.get(0).getHandler().wrap(ResponseCodeHandler.HANDLE_200).handleRequest(exchange); Assert.assertNull(exchange.getRequestHeaders().get(Headers.USER_AGENT)); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/caching/000077500000000000000000000000001420065311100270715ustar00rootroot00000000000000CacheHandlerTestCase.java000066400000000000000000000100111420065311100336030ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/caching/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.caching; import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.cache.CacheHandler; import io.undertow.server.handlers.cache.DirectBufferCache; import io.undertow.server.handlers.cache.ResponseCache; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.Headers; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; /** * Tests out the caching handler * * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class CacheHandlerTestCase { @Test public void testBasicPathBasedCaching() throws IOException { final AtomicInteger responseCount = new AtomicInteger(); final HttpHandler messageHandler = new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final ResponseCache cache = exchange.getAttachment(ResponseCache.ATTACHMENT_KEY); if(!cache.tryServeResponse()) { final String data = "Response " + responseCount.incrementAndGet(); exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, data.length() + ""); exchange.getResponseSender().send(data); } } }; final CacheHandler cacheHandler = new CacheHandler(new DirectBufferCache(100, 10, 1000), messageHandler); DefaultServer.setRootHandler(cacheHandler); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); //it takes 5 hits to make an entry actually get cached for (int i = 1; i <= 5; ++i) { HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("Response " + i, HttpClientUtils.readResponse(result)); } HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("Response 5", HttpClientUtils.readResponse(result)); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("Response 5", HttpClientUtils.readResponse(result)); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("Response 5", HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path2"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("Response 6", HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/encoding/000077500000000000000000000000001420065311100272635ustar00rootroot00000000000000DeflateContentEncodingTestCase.java000066400000000000000000000122031420065311100360470ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/encoding/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.encoding; import io.undertow.io.IoCallback; import io.undertow.predicate.Predicates; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.entity.DecompressingEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.util.Random; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class DeflateContentEncodingTestCase { private static volatile String message; @BeforeClass public static void setup() { final EncodingHandler handler = new EncodingHandler(new ContentEncodingRepository() .addEncodingHandler("deflate", new DeflateEncodingProvider(), 50, Predicates.maxContentSize(5))) .setNext(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, message.length() + ""); exchange.getResponseSender().send(message, IoCallback.END_EXCHANGE); } }); DefaultServer.setRootHandler(handler); } /** * Tests the use of the deflate contentent encoding * * @throws IOException */ @Test public void testDeflateEncoding() throws IOException { runTest("Hello World"); } /** * This message should not be compressed as it is too small * * @throws IOException */ @Test public void testSmallMessagePredicateDoesNotCompress() throws IOException { try (CloseableHttpClient client = HttpClientBuilder.create().build()) { message = "Hi"; HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "deflate"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] header = result.getHeaders(Headers.CONTENT_ENCODING_STRING); Assert.assertEquals(0, header.length); final String body = HttpClientUtils.readResponse(result); Assert.assertEquals("Hi", body); } } @Test public void testDeflateEncodingBigResponse() throws IOException { final StringBuilder messageBuilder = new StringBuilder(691963); for (int i = 0; i < 691963; ++i) { messageBuilder.append("*"); } runTest(messageBuilder.toString()); } @Test public void testDeflateEncodingRandomSizeResponse() throws IOException { int seed = new Random().nextInt(); //System.out.println("Using seed " + seed); try { final Random random = new Random(seed); int size = random.nextInt(691963); final StringBuilder messageBuilder = new StringBuilder(size); for (int i = 0; i < size; ++i) { messageBuilder.append('*' + random.nextInt(10)); } runTest(messageBuilder.toString()); } catch (Exception e) { throw new RuntimeException("Test failed with seed " + seed, e); } } public void runTest(final String theMessage) throws IOException { try (CloseableHttpClient client = HttpClientBuilder.create().build()) {//by default it has gzip enabled message = theMessage; HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "deflate"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); assert result.getEntity() instanceof DecompressingEntity; //no other nice way to be sure we get back gzipped content final String body = HttpClientUtils.readResponse(result); Assert.assertEquals(theMessage, body); } } } EncodingSelectionTestCase.java000066400000000000000000000303021420065311100350750ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/encoding/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.encoding; import java.io.IOException; import io.undertow.predicate.Predicates; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; /** * Tests that the correct encoding is selected * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class EncodingSelectionTestCase { private static final String HEADER = Headers.CONTENT_ENCODING_STRING; /** * Tests encoding selection with no qvalue *

* Also tests a lot of non standard formats for Accept-Encoding to make sure that * we are liberal in what we accept * * @throws IOException */ @Test public void testBasicEncodingSelect() throws IOException { TestHttpClient client = new TestHttpClient(); try { final EncodingHandler handler = new EncodingHandler(new ContentEncodingRepository() .addEncodingHandler("compress", ContentEncodingProvider.IDENTITY, 50) .addEncodingHandler("bzip", ContentEncodingProvider.IDENTITY, 100)) .setNext(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("hi"); //we need some content to encode } }); DefaultServer.setRootHandler(handler); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] header = result.getHeaders(HEADER); Assert.assertEquals(0, header.length); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "bzip"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); header = result.getHeaders(HEADER); Assert.assertEquals("bzip", header[0].getValue()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "bzip compress identity someOtherEncoding"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); header = result.getHeaders(HEADER); Assert.assertEquals("bzip", header[0].getValue()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, " compress, identity, someOtherEncoding, bzip , "); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); header = result.getHeaders(HEADER); Assert.assertEquals("bzip", header[0].getValue()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "boo; compress, identity; someOtherEncoding, , "); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); header = result.getHeaders(HEADER); Assert.assertEquals("compress", header[0].getValue()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "boo; compress; identity; someOtherEncoding, , "); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); header = result.getHeaders(HEADER); Assert.assertEquals("compress", header[0].getValue()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } /** * Tests encoding selection with a qvalue * * @throws IOException */ @Test public void testEncodingSelectWithQValue() throws IOException { TestHttpClient client = new TestHttpClient(); try { final EncodingHandler handler = new EncodingHandler(new ContentEncodingRepository() .addEncodingHandler("compress", ContentEncodingProvider.IDENTITY, 100) .addEncodingHandler("bzip", ContentEncodingProvider.IDENTITY, 50)) .setNext(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("hi"); //we need some content to encode } }); DefaultServer.setRootHandler(handler); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "bzip, compress;q=0.6"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] header = result.getHeaders(HEADER); Assert.assertEquals("bzip", header[0].getValue()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "*;q=0.00"); result = client.execute(get); Assert.assertEquals(StatusCodes.NOT_ACCEPTABLE, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "*;q=0.00 bzip"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); header = result.getHeaders(HEADER); Assert.assertEquals("bzip", header[0].getValue()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "*;q=0.00 bzip;q=0.3"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); header = result.getHeaders(HEADER); Assert.assertEquals("bzip", header[0].getValue()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "compress;q=0.1 bzip;q=0.05"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); header = result.getHeaders(HEADER); Assert.assertEquals("compress", header[0].getValue()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "compress;q=0.1, bzip;q=1.000"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); header = result.getHeaders(HEADER); Assert.assertEquals("bzip", header[0].getValue()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } @Test public void testEncodingSelectionWithQValueAndPredicate() throws IOException { TestHttpClient client = new TestHttpClient(); try { final EncodingHandler handler = new EncodingHandler(new ContentEncodingRepository() .addEncodingHandler("compress", ContentEncodingProvider.IDENTITY, 100, Predicates.falsePredicate()) .addEncodingHandler("bzip", ContentEncodingProvider.IDENTITY, 50)) .setNext(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("hi"); //we need some content to encode } }); DefaultServer.setRootHandler(handler); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "bzip, compress;q=0.6"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] header = result.getHeaders(HEADER); Assert.assertEquals("bzip", header[0].getValue()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "*;q=0.00"); result = client.execute(get); Assert.assertEquals(StatusCodes.NOT_ACCEPTABLE, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "compress"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); header = result.getHeaders(HEADER); Assert.assertEquals(0, header.length); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "*;q=0.00 bzip;q=0.3"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); header = result.getHeaders(HEADER); Assert.assertEquals("bzip", header[0].getValue()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "compress;q=0.1 bzip;q=0.05"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); header = result.getHeaders(HEADER); Assert.assertEquals("bzip", header[0].getValue()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "compress;q=0.1, bzip;q=1.000"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); header = result.getHeaders(HEADER); Assert.assertEquals("bzip", header[0].getValue()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } } GzipContentEncodingSimpleObjectPoolTestCase.java000066400000000000000000000142521420065311100405550ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/encoding/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.encoding; import io.undertow.conduits.DeflatingStreamSinkConduit; import io.undertow.io.IoCallback; import io.undertow.predicate.Predicates; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.Headers; import io.undertow.util.ObjectPool; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.entity.DecompressingEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.util.Random; import java.util.zip.Deflater; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class GzipContentEncodingSimpleObjectPoolTestCase { private static volatile String message; @BeforeClass public static void setup() { final ObjectPool deflaterPool = DeflatingStreamSinkConduit.simpleDeflaterPool(50, Deflater.BEST_SPEED); final EncodingHandler handler = new EncodingHandler(new ContentEncodingRepository() .addEncodingHandler("gzip", new GzipEncodingProvider(deflaterPool), 50, Predicates.parse("max-content-size[5]"))) .setNext(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, message.length() + ""); exchange.getResponseSender().send(message, IoCallback.END_EXCHANGE); } }); DefaultServer.setRootHandler(handler); } /** * Tests the use of the deflate content encoding * * @throws IOException */ @Test public void testGzipEncoding() throws IOException { runTest("Hello World"); } /** * This message should not be compressed as it is too small * * @throws IOException */ @Test public void testSmallMessagePredicateDoesNotCompress() throws IOException { try (CloseableHttpClient client = HttpClientBuilder.create().build()){ message = "Hi"; HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "gzip"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] header = result.getHeaders(Headers.CONTENT_ENCODING_STRING); Assert.assertEquals(0, header.length); final String body = HttpClientUtils.readResponse(result); Assert.assertEquals("Hi", body); } } //UNDERTOW-331 @Test public void testAcceptIdentity() throws IOException { try (CloseableHttpClient client = HttpClientBuilder.create().build()){ message = "Hi"; HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "identity;q=1, *;q=0"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] header = result.getHeaders(Headers.CONTENT_ENCODING_STRING); Assert.assertEquals(1, header.length); Assert.assertEquals("identity", header[0].getValue()); final String body = HttpClientUtils.readResponse(result); Assert.assertEquals("Hi", body); } } @Test public void testGZipEncodingLargeResponse() throws IOException { final StringBuilder messageBuilder = new StringBuilder(691963); for (int i = 0; i < 691963; ++i) { messageBuilder.append("*"); } runTest(messageBuilder.toString()); } @Test public void testGzipEncodingRandomSizeResponse() throws IOException { int seed = new Random().nextInt(); //System.out.println("Using seed " + seed); try { final Random random = new Random(seed); int size = random.nextInt(691963); final StringBuilder messageBuilder = new StringBuilder(size); for (int i = 0; i < size; ++i) { messageBuilder.append('*' + random.nextInt(10)); } runTest(messageBuilder.toString()); } catch (Exception e) { throw new RuntimeException("Test failed with seed " + seed, e); } } public void runTest(final String theMessage) throws IOException { try (CloseableHttpClient client = HttpClientBuilder.create().build()){ message = theMessage; HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "gzip"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); assert result.getEntity() instanceof DecompressingEntity; //no other nice way to be sure we get back gzipped content final String body = HttpClientUtils.readResponse(result); Assert.assertEquals(theMessage, body); } } } GzipContentEncodingTestCase.java000066400000000000000000000136501420065311100354230ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/encoding/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.encoding; import io.undertow.io.IoCallback; import io.undertow.predicate.Predicates; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.entity.DecompressingEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.util.Random; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class GzipContentEncodingTestCase { private static volatile String message; @BeforeClass public static void setup() { final EncodingHandler handler = new EncodingHandler(new ContentEncodingRepository() .addEncodingHandler("gzip", new GzipEncodingProvider(), 50, Predicates.parse("max-content-size[5]"))) .setNext(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, message.length() + ""); exchange.getResponseSender().send(message, IoCallback.END_EXCHANGE); } }); DefaultServer.setRootHandler(handler); } /** * Tests the use of the deflate content encoding * * @throws java.io.IOException */ @Test public void testGzipEncoding() throws IOException { runTest("Hello World"); } /** * This message should not be compressed as it is too small * * @throws java.io.IOException */ @Test public void testSmallMessagePredicateDoesNotCompress() throws IOException { try (CloseableHttpClient client = HttpClientBuilder.create().build()){ message = "Hi"; HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "gzip"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] header = result.getHeaders(Headers.CONTENT_ENCODING_STRING); Assert.assertEquals(0, header.length); final String body = HttpClientUtils.readResponse(result); Assert.assertEquals("Hi", body); } } //UNDERTOW-331 @Test public void testAcceptIdentity() throws IOException { try (CloseableHttpClient client = HttpClientBuilder.create().build()){ message = "Hi"; HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "identity;q=1, *;q=0"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] header = result.getHeaders(Headers.CONTENT_ENCODING_STRING); Assert.assertEquals(1, header.length); Assert.assertEquals("identity", header[0].getValue()); final String body = HttpClientUtils.readResponse(result); Assert.assertEquals("Hi", body); } } @Test public void testGZipEncodingLargeResponse() throws IOException { final StringBuilder messageBuilder = new StringBuilder(691963); for (int i = 0; i < 691963; ++i) { messageBuilder.append("*"); } runTest(messageBuilder.toString()); } @Test public void testGzipEncodingRandomSizeResponse() throws IOException { int seed = new Random().nextInt(); //System.out.println("Using seed " + seed); try { final Random random = new Random(seed); int size = random.nextInt(691963); final StringBuilder messageBuilder = new StringBuilder(size); for (int i = 0; i < size; ++i) { messageBuilder.append('*' + random.nextInt(10)); } runTest(messageBuilder.toString()); } catch (Exception e) { throw new RuntimeException("Test failed with seed " + seed, e); } } public void runTest(final String theMessage) throws IOException { try (CloseableHttpClient client = HttpClientBuilder.create().build()){ message = theMessage; HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, "gzip"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); assert result.getEntity() instanceof DecompressingEntity; //no other nice way to be sure we get back gzipped content final String body = HttpClientUtils.readResponse(result); Assert.assertEquals(theMessage, body); } } } RequestContentEncodingTestCase.java000066400000000000000000000135211420065311100361370ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/encoding/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.encoding; import java.io.IOException; import java.nio.ByteBuffer; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.conduits.GzipStreamSourceConduit; import io.undertow.conduits.InflatingStreamSourceConduit; import io.undertow.io.IoCallback; import io.undertow.io.Receiver; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.PathHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; /** * This is not part of the HTTP spec * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class RequestContentEncodingTestCase { private static volatile String message; @BeforeClass public static void setup() { final ContentEncodingRepository contentEncodingRepository = new ContentEncodingRepository() .addEncodingHandler("deflate", new DeflateEncodingProvider(), 50) .addEncodingHandler("gzip", new GzipEncodingProvider(), 60); final EncodingHandler encode = new EncodingHandler(contentEncodingRepository) .setNext(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, message.length() + ""); exchange.getResponseSender().send(message, IoCallback.END_EXCHANGE); } }); final EncodingHandler wrappedEncode = new EncodingHandler(contentEncodingRepository).setNext(encode); final HttpHandler decode = new RequestEncodingHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getRequestReceiver().receiveFullBytes(new Receiver.FullBytesCallback() { @Override public void handle(HttpServerExchange exchange, byte[] message) { exchange.getResponseSender().send(ByteBuffer.wrap(message)); } }); } }).addEncoding("deflate", InflatingStreamSourceConduit.WRAPPER) .addEncoding("gzip", GzipStreamSourceConduit.WRAPPER); final HttpHandler wrappedDecode = new RequestEncodingHandler(decode) .addEncoding("deflate", InflatingStreamSourceConduit.WRAPPER) .addEncoding("gzip", GzipStreamSourceConduit.WRAPPER); PathHandler pathHandler = new PathHandler(); pathHandler.addPrefixPath("/encode", wrappedEncode); pathHandler.addPrefixPath("/decode", wrappedDecode); DefaultServer.setRootHandler(pathHandler); } /** * Tests the use of the deflate contentent encoding * * @throws IOException */ @Test public void testDeflateEncoding() throws IOException { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000; ++i) { sb.append("a message"); } runTest(sb.toString(), "deflate"); runTest("Hello World", "deflate"); } @Test public void testGzipEncoding() throws IOException { runTest("Hello World", "gzip"); StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000; ++i) { sb.append("a message"); } runTest(sb.toString(), "gzip"); } public void runTest(final String theMessage, String encoding) throws IOException { try (CloseableHttpClient client = HttpClientBuilder.create().disableContentCompression().build()){ message = theMessage; HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/encode"); get.setHeader(Headers.ACCEPT_ENCODING_STRING, encoding); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] header = result.getHeaders(Headers.CONTENT_ENCODING_STRING); Assert.assertEquals(encoding, header[0].getValue()); byte[] body = HttpClientUtils.readRawResponse(result); HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/decode"); post.setEntity(new ByteArrayEntity(body)); post.addHeader(Headers.CONTENT_ENCODING_STRING, encoding); result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String sb = HttpClientUtils.readResponse(result); Assert.assertEquals(theMessage.length(), sb.length()); Assert.assertEquals(theMessage, sb); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/error/000077500000000000000000000000001420065311100266265ustar00rootroot00000000000000FileErrorPageHandlerTestCase.java000066400000000000000000000043271420065311100350400ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/error/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.error; import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Paths; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class FileErrorPageHandlerTestCase { @Test public void testFileBasedErrorPageIsGenerated() throws IOException, URISyntaxException { TestHttpClient client = new TestHttpClient(); try { final FileErrorPageHandler handler = new FileErrorPageHandler(Paths.get(getClass().getResource("errorpage.html").toURI()), StatusCodes.NOT_FOUND); DefaultServer.setRootHandler(handler); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); HttpResponse result = client.execute(get); Assert.assertEquals("text/html", result.getHeaders("Content-Type")[0].getValue()); Assert.assertEquals(StatusCodes.NOT_FOUND, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response, response.contains("Custom Error Page")); } finally { client.getConnectionManager().shutdown(); } } } SimpleErrorPageHandlerTestCase.java000066400000000000000000000037351420065311100354140ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/error/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.error; import java.io.IOException; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import io.undertow.testutils.TestHttpClient; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class SimpleErrorPageHandlerTestCase { @Test public void testSimpleErrorPageIsGenerated() throws IOException { TestHttpClient client = new TestHttpClient(); try { final SimpleErrorPageHandler handler = new SimpleErrorPageHandler(); DefaultServer.setRootHandler(handler); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.NOT_FOUND, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response, response.contains(StatusCodes.NOT_FOUND_STRING)); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/error/errorpage.html000066400000000000000000000001361420065311100315020ustar00rootroot00000000000000 Error Page Custom Error Page undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/file/000077500000000000000000000000001420065311100264145ustar00rootroot00000000000000ContentEncodedResourceTestCase.java000066400000000000000000000106121420065311100352400ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/file/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.file; import io.undertow.server.handlers.encoding.ContentEncodedResourceManager; import io.undertow.server.handlers.encoding.ContentEncodingRepository; import io.undertow.server.handlers.encoding.DeflateEncodingProvider; import io.undertow.server.handlers.resource.CachingResourceManager; import io.undertow.server.handlers.resource.PathResourceManager; import io.undertow.server.handlers.resource.ResourceHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.FileUtils; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.entity.DecompressingEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ContentEncodedResourceTestCase { public static final String DIR_NAME = "contentEncodingTestCase"; static Path tmpDir; @BeforeClass public static void setup() throws IOException{ tmpDir = Files.createTempDirectory(Paths.get(System.getProperty("java.io.tmpdir")), DIR_NAME); final PathResourceManager resourceManager = new PathResourceManager(tmpDir, 10485760); DefaultServer.setRootHandler(new ResourceHandler(resourceManager) .setContentEncodedResourceManager( new ContentEncodedResourceManager(tmpDir, new CachingResourceManager(100, 10000, null, resourceManager, -1), new ContentEncodingRepository() .addEncodingHandler("deflate", new DeflateEncodingProvider(), 50, null), 0, 100000, null))); } @AfterClass public static void after() throws IOException { FileUtils.deleteRecursive(tmpDir); } @Test public void testFileIsCompressed() throws IOException, InterruptedException { String fileName = "hello.html"; Path f = tmpDir.resolve(fileName); Files.write(f, "hello world".getBytes()); try (CloseableHttpClient client = HttpClientBuilder.create().build()){ for (int i = 0; i < 3; ++i) { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/" + fileName); CloseableHttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("hello world", response); assert result.getEntity() instanceof DecompressingEntity; //no other nice way to be sure we get back gzipped content result.close(); } Files.write(f, "modified file".getBytes()); //if it is serving a cached compressed version what is being served will not change HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/" + fileName); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("hello world", response); assert result.getEntity() instanceof DecompressingEntity; //no other nice way to be sure we get back gzipped content } } } FileHandlerIndexTestCase.java000066400000000000000000000120341420065311100340010ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/file/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.file; import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import io.undertow.server.handlers.CanonicalPathHandler; import io.undertow.server.handlers.PathHandler; import io.undertow.server.handlers.resource.PathResourceManager; import io.undertow.server.handlers.resource.ResourceHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Tomaz Cerar */ @RunWith(DefaultServer.class) public class FileHandlerIndexTestCase { @Test public void testWelcomeFile() throws IOException, URISyntaxException { TestHttpClient client = new TestHttpClient(); Path rootPath = Paths.get(getClass().getResource("page.html").toURI()).getParent(); try { DefaultServer.setRootHandler(new CanonicalPathHandler() .setNext(new PathHandler() .addPrefixPath("/path", new ResourceHandler(new PathResourceManager(rootPath, 10485760)) .setDirectoryListingEnabled(true) .addWelcomeFiles("page.html")))); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Header[] headers = result.getHeaders("Content-Type"); Assert.assertEquals("text/html", headers[0].getValue()); Assert.assertTrue(response, response.contains("A web page")); } finally { client.getConnectionManager().shutdown(); } } @Test public void testDirectoryIndex() throws IOException, URISyntaxException { TestHttpClient client = new TestHttpClient(); Path rootPath = Paths.get(getClass().getResource("page.html").toURI()).getParent(); Path badSymlink = null; try { DefaultServer.setRootHandler(new PathHandler() .addPrefixPath("/path", new ResourceHandler(new PathResourceManager(rootPath, 10485760)) .setDirectoryListingEnabled(true))); badSymlink = rootPath.resolve("tmp2"); Path badSymlinkTarget = rootPath.resolve("/tmp2"); Files.createSymbolicLink(badSymlink, badSymlinkTarget); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Header[] headers = result.getHeaders("Content-Type"); Assert.assertEquals("text/html; charset=UTF-8", headers[0].getValue()); Assert.assertTrue(response, response.contains("page.html")); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path/."); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); headers = result.getHeaders("Content-Type"); Assert.assertEquals("text/html; charset=UTF-8", headers[0].getValue()); Assert.assertTrue(response, response.contains("page.html")); Assert.assertTrue(response, response.contains("tmp2")); // All invalid symlinks have their date set to epoch SimpleDateFormat format = new SimpleDateFormat("MMM dd, yyyy HH:mm:ss", Locale.US); Assert.assertTrue(response, response.contains(format.format((new Date(0L))))); } finally { client.getConnectionManager().shutdown(); if (badSymlink != null) { Files.deleteIfExists(badSymlink); } } } } FileHandlerStressTestCase.java000066400000000000000000000103251420065311100342160ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/file/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.file; import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import io.undertow.server.handlers.CanonicalPathHandler; import io.undertow.server.handlers.PathHandler; import io.undertow.server.handlers.cache.CacheHandler; import io.undertow.server.handlers.cache.DirectBufferCache; import io.undertow.server.handlers.resource.PathResourceManager; import io.undertow.server.handlers.resource.ResourceHandler; import io.undertow.testutils.AjpIgnore; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import io.undertow.testutils.TestHttpClient; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @AjpIgnore // it looks like apache actually has trouble with the number of requests @RunWith(DefaultServer.class) public class FileHandlerStressTestCase { public static final int NUM_THREADS = 10; public static final int NUM_REQUESTS = 100; @Test public void simpleFileStressTest() throws IOException, ExecutionException, InterruptedException, URISyntaxException { ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS); try { Path rootPath = Paths.get(getClass().getResource("page.html").toURI()).getParent(); final ResourceHandler handler = new ResourceHandler(new PathResourceManager(rootPath, 10485760)); final CacheHandler cacheHandler = new CacheHandler(new DirectBufferCache(1024, 10, 10480), handler); final PathHandler path = new PathHandler(); path.addPrefixPath("/path", cacheHandler); final CanonicalPathHandler root = new CanonicalPathHandler(); root.setNext(path); DefaultServer.setRootHandler(root); final List> futures = new ArrayList<>(); for (int i = 0; i < NUM_THREADS; ++i) { futures.add(executor.submit(new Runnable() { @Override public void run() { TestHttpClient client = new TestHttpClient(); try { for (int i = 0; i < NUM_REQUESTS; ++i) { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path/page.html"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response, response.contains("A web page")); } } catch (IOException e) { throw new RuntimeException(e); } finally { client.getConnectionManager().shutdown(); } } })); } for (Future future : futures) { future.get(); } } finally { executor.shutdown(); } } } FileHandlerSymlinksTestCase.java000066400000000000000000000457361420065311100345620ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/file/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.file; import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import io.undertow.server.handlers.CanonicalPathHandler; import io.undertow.server.handlers.PathHandler; import io.undertow.server.handlers.resource.PathResourceManager; import io.undertow.server.handlers.resource.ResourceHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.apache.http.params.SyncBasicHttpParams; import org.junit.After; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Lucas Ponce */ @RunWith(DefaultServer.class) public class FileHandlerSymlinksTestCase { @Before public void createSymlinksScenario() throws IOException, URISyntaxException { Assume.assumeFalse(System.getProperty("os.name").toLowerCase().contains("windows")); /** * Creating following structure for test: * * $ROOT_PATH/newDir * $ROOT_PATH/newDir/page.html * $ROOT_PATH/newDir/innerDir/ * $ROOT_PATH/newDir/innerDir/page.html * $ROOT_PATH/newSymlink -> $ROOT_PATH/newDir * $ROOT_PATH/newDir/innerSymlink -> $ROOT_PATH/newDir/innerDir/ * */ Path filePath = Paths.get(getClass().getResource("page.html").toURI()); Path rootPath = filePath.getParent(); Path newDir = rootPath.resolve("newDir"); Files.createDirectories(newDir); Path innerDir = newDir.resolve("innerDir"); Files.createDirectories(innerDir); Files.copy(filePath, newDir.resolve(filePath.getFileName())); Files.copy(filePath, innerDir.resolve(filePath.getFileName())); Path newSymlink = rootPath.resolve("newSymlink"); Files.createSymbolicLink(newSymlink, newDir); Path innerSymlink = newDir.resolve("innerSymlink"); Files.createSymbolicLink(innerSymlink, innerDir); } @After public void deleteSymlinksScenario() throws IOException, URISyntaxException { Path rootPath = Paths.get(getClass().getResource("page.html").toURI()).getParent(); Path newSymlink = rootPath.resolve("newSymlink"); Path newDir = rootPath.resolve("newDir"); Path page = newDir.resolve("page.html"); Path innerDir = newDir.resolve("innerDir"); Path innerSymlink = newDir.resolve("innerSymlink"); Path innerPage = innerDir.resolve("page.html"); Files.deleteIfExists(innerSymlink); Files.deleteIfExists(newSymlink); Files.deleteIfExists(innerPage); Files.deleteIfExists(page); Files.deleteIfExists(innerDir); Files.deleteIfExists(newDir); } @Test public void testCreateSymlinks() throws IOException, URISyntaxException { Path rootPath = Paths.get(getClass().getResource("page.html").toURI()).getParent(); Path newDir = rootPath.resolve("newDir"); Assert.assertFalse(Files.isSymbolicLink(newDir)); Path innerDir = newDir.resolve("innerDir"); Assert.assertFalse(Files.isSymbolicLink(innerDir)); Path newSymlink = rootPath.resolve("newSymlink"); Assert.assertTrue(Files.isSymbolicLink(newSymlink)); Path innerSymlink = newSymlink.resolve("innerSymlink"); Assert.assertTrue(Files.isSymbolicLink(innerSymlink)); Path f = innerSymlink.getRoot(); for (int i=0; i", response); } finally { client.getConnectionManager().shutdown(); } } /* Starts simple file server, it is useful for testing directory browsing */ public static void main(String[] args) throws URISyntaxException { Path rootPath = Paths.get(FileHandlerTestCase.class.getResource("page.html").toURI()).getParent().getParent(); HttpHandler root = new CanonicalPathHandler() .setNext(new PathHandler() .addPrefixPath("/path", new ResourceHandler(new PathResourceManager(rootPath, 1)) // 1 byte = force transfer .setDirectoryListingEnabled(true))); Undertow undertow = Undertow.builder() .addHttpListener(8888, "localhost") .setHandler(root) .build(); undertow.start(); } } PathResourceManagerTestCase.java000066400000000000000000000163241420065311100345410ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/filepackage io.undertow.server.handlers.file; import io.undertow.server.handlers.resource.Resource; import io.undertow.testutils.category.UnitTest; import io.undertow.server.handlers.resource.PathResourceManager; import io.undertow.server.handlers.resource.ResourceManager; import io.undertow.util.ETag; import org.junit.Assert; import org.junit.Assume; import org.junit.Test; import org.junit.experimental.categories.Category; import java.io.BufferedOutputStream; import java.io.File; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; /** * @author Tomaz Cerar (c) 2016 Red Hat Inc. */ @Category(UnitTest.class) public class PathResourceManagerTestCase { @Test public void testGetResource() throws Exception { final Path rootPath = Paths.get(getClass().getResource("page.html").toURI()).getParent(); final PathResourceManager resourceManager = new PathResourceManager(rootPath, 1024 * 1024); Assert.assertNotNull(resourceManager.getResource("page.html")); Assert.assertNotNull(resourceManager.getResource("./page.html")); Assert.assertNotNull(resourceManager.getResource("../file/page.html")); } @Test public void testListDir() throws Exception { final Path rootPath = Paths.get(getClass().getResource("page.html").toURI()).getParent(); final PathResourceManager resourceManager = new PathResourceManager(rootPath, 1024 * 1024); Resource subdir = resourceManager.getResource("subdir"); Resource found = subdir.list().get(0); Assert.assertEquals("subdir" + File.separatorChar+ "a.txt", found.getPath()); } @Test public void testCantEscapeRoot() throws Exception { final Path rootPath = Paths.get(getClass().getResource("page.html").toURI()).getParent().resolve("subdir"); final PathResourceManager resourceManager = new PathResourceManager(rootPath, 1024 * 1024); Assert.assertNotNull(resourceManager.getResource("a.txt")); Assert.assertNull(resourceManager.getResource("../page.html")); } @Test public void testBaseDirInSymlink() throws Exception { Assume.assumeFalse(System.getProperty("os.name").toLowerCase().contains("windows")); Path filePath = Paths.get(getClass().getResource("page.html").toURI()); Path rootPath = filePath.getParent(); Path newDir = rootPath.resolve("newDir"); Path innerPage = newDir.resolve("page.html"); Path newSymlink = rootPath.resolve("newSymlink"); try { Files.createDirectories(newDir); Files.copy(filePath, innerPage); Files.createSymbolicLink(newSymlink, newDir); Assert.assertTrue("Ensure that newSymlink is still a symlink as expected", Files.isSymbolicLink(newSymlink)); final PathResourceManager resourceManager = new PathResourceManager(newSymlink, 1024 * 1024); Assert.assertNotNull(resourceManager.getResource("page.html")); Assert.assertNull(resourceManager.getResource("Page.html")); Assert.assertNotNull(resourceManager.getResource("./page.html")); } finally { Files.deleteIfExists(newSymlink); Files.deleteIfExists(innerPage); Files.deleteIfExists(newDir); Files.deleteIfExists(newDir); } } @Test public void testETagFunction() throws Exception { final String fileName = "page.html"; final Path rootPath = Paths.get(getClass().getResource(fileName).toURI()).getParent(); final ResourceManager resourceManager = PathResourceManager.builder() .setBase(rootPath) .setETagFunction(new PathResourceManager.ETagFunction() { @Override public ETag generate(Path path) { return new ETag(true, path.getFileName().toString()); } }) .build(); ETag expected = new ETag(true, fileName); ETag actual = resourceManager.getResource("page.html").getETag(); Assert.assertEquals(expected, actual); } @Test public void testNonDefaultFileSystem() throws Exception { Path zipFile = Files.createTempFile("undertow", ".zip"); try { String expectedText = "Hello, world!"; byte[] expectedBytes = expectedText.getBytes(StandardCharsets.UTF_8); try (OutputStream os = Files.newOutputStream(zipFile); BufferedOutputStream bos = new BufferedOutputStream(os); ZipOutputStream zos = new ZipOutputStream(bos)) { zos.putNextEntry(new ZipEntry("dir/")); zos.closeEntry(); zos.putNextEntry(new ZipEntry("dir/resource.txt")); zos.write(expectedBytes); zos.closeEntry(); zos.putNextEntry(new ZipEntry("root_resource.txt")); zos.write(expectedBytes); zos.closeEntry(); } try (FileSystem zipFileSystem = FileSystems.newFileSystem(zipFile, getClass().getClassLoader())) { PathResourceManager resourceManager = new PathResourceManager(zipFileSystem.getPath("/dir")); Resource resource = resourceManager.getResource("resource.txt"); Assert.assertArrayEquals(expectedBytes, Files.readAllBytes(resource.getFilePath())); try { resourceManager.registerResourceChangeListener(changes -> {}); Assert.fail("registerResourceChangeListener should have failed"); } catch (IllegalStateException expected) {} try { resource.getFile(); Assert.fail("getFile should have failed"); } catch (UnsupportedOperationException expected) {} Resource dir = resourceManager.getResource("."); Assert.assertTrue(dir.isDirectory()); List list = dir.list(); Assert.assertEquals(1, list.size()); Assert.assertEquals(resource.getFilePath().normalize(), list.get(0).getFilePath().normalize()); Resource outside = resourceManager.getResource("../root_resource.txt"); Assert.assertNull(outside); Resource doesNotExist = resourceManager.getResource("does_not_exist.txt"); Assert.assertNull(doesNotExist); resourceManager.setBase(Paths.get(getClass().getResource("page.html").toURI()).getParent()); Assert.assertNotNull(resourceManager.getResource("page.html")); resourceManager.setBase(zipFileSystem.getPath("/")); Assert.assertNotNull(resourceManager.getResource("root_resource.txt")); resourceManager.setBase(new File(getClass().getResource("page.html").toURI()).getParentFile()); Assert.assertNotNull(resourceManager.getResource("page.html")); } } finally { Files.deleteIfExists(zipFile); } } } PreCompressedResourceTestCase.java000066400000000000000000000300611420065311100351170ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/file/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.file; import java.io.IOException; import java.io.FileInputStream; import java.io.FileOutputStream; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.charset.StandardCharsets; import java.util.zip.GZIPOutputStream; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.entity.DecompressingEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.predicate.Predicates; import io.undertow.server.handlers.CanonicalPathHandler; import io.undertow.server.handlers.PathHandler; import io.undertow.server.handlers.encoding.ContentEncodingRepository; import io.undertow.server.handlers.encoding.EncodingHandler; import io.undertow.server.handlers.encoding.GzipEncodingProvider; import io.undertow.server.handlers.resource.PathResourceManager; import io.undertow.server.handlers.resource.PreCompressedResourceSupplier; import io.undertow.server.handlers.resource.ResourceHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class PreCompressedResourceTestCase { @After public void clean() throws IOException, URISyntaxException { Path rootPath = Paths.get(getClass().getResource("page.html").toURI()).getParent(); if (Files.exists(rootPath.resolve("page.html.gz"))) { Files.delete(rootPath.resolve("page.html.gz")); } if (Files.exists(rootPath.resolve("page.html.gzip"))) { Files.delete(rootPath.resolve("page.html.gzip")); } if (Files.exists(rootPath.resolve("page.html.nonsense"))) { Files.delete(rootPath.resolve("page.html.nonsense")); } if (Files.exists(rootPath.resolve("page.html.gzip.nonsense"))) { Files.delete(rootPath.resolve("page.html.gzip.nonsense")); } } @Test public void testContentEncodedResource() throws IOException, URISyntaxException { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path/page.html"); TestHttpClient client = new TestHttpClient(); Path rootPath = Paths.get(getClass().getResource("page.html").toURI()).getParent(); try (CloseableHttpClient compClient = HttpClientBuilder.create().build()){ DefaultServer.setRootHandler(new CanonicalPathHandler() .setNext(new PathHandler() .addPrefixPath("/path", new ResourceHandler(new PreCompressedResourceSupplier(new PathResourceManager(rootPath, 10485760)).addEncoding("gzip", ".gz")) .setDirectoryListingEnabled(true)))); //assert response without compression final String plainResponse = assertResponse(client.execute(get), false); //assert compressed response, that doesn't exists, so returns plain assertResponse(compClient.execute(get), false, plainResponse); //generate compressed resource with extension .gz generatePreCompressedResource("gz"); //assert compressed response that was pre compressed assertResponse(compClient.execute(get), true, plainResponse, "gz", "text/html"); } finally { client.getConnectionManager().shutdown(); } } @Test public void testContentEncodedJsonResource() throws IOException, URISyntaxException { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path/data1.json"); TestHttpClient client = new TestHttpClient(); Path rootPath = Paths.get(getClass().getResource("data1.json").toURI()).getParent(); try (CloseableHttpClient compClient = HttpClientBuilder.create().build()){ DefaultServer.setRootHandler(new CanonicalPathHandler() .setNext(new PathHandler() .addPrefixPath("/path", new ResourceHandler(new PreCompressedResourceSupplier(new PathResourceManager(rootPath, 10485760)).addEncoding("gzip", ".gz")) .setDirectoryListingEnabled(true)))); //assert response without compression final String plainResponse = assertResponse(client.execute(get), false, null, "web", "application/json"); //assert compressed response, that doesn't exists, so returns plain assertResponse(compClient.execute(get), false, plainResponse, "web", "application/json"); //generate compressed resource with extension .gz Path json = rootPath.resolve("data1.json"); generateGZipFile(json, rootPath.resolve("data1.json.gz")); //assert compressed response that was pre compressed assertResponse(compClient.execute(get), true, plainResponse, "gz", "application/json"); } finally { client.getConnectionManager().shutdown(); } } @Test public void testContentEncodedJsonResourceWithoutUncompressed() throws IOException, URISyntaxException { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path/data3.json"); TestHttpClient client = new TestHttpClient(); Path rootPath = Paths.get(getClass().getResource("data2.json").toURI()).getParent(); try (CloseableHttpClient compClient = HttpClientBuilder.create().build()){ DefaultServer.setRootHandler(new CanonicalPathHandler() .setNext(new PathHandler() .addPrefixPath("/path", new ResourceHandler(new PreCompressedResourceSupplier(new PathResourceManager(rootPath, 10485760)).addEncoding("gzip", ".gz")) .setDirectoryListingEnabled(true)))); //generate compressed resource with extension .gz and delete the uncompressed Path json = rootPath.resolve("data2.json"); Path jsonFileToBeZippedAndDeleted = rootPath.resolve("data3.json"); Files.copy(json, jsonFileToBeZippedAndDeleted); // data3.json.gz has no corresponding data3.json in the filesystem (UNDERTOW-1950) generateGZipFile(jsonFileToBeZippedAndDeleted, rootPath.resolve("data3.json.gz")); Files.delete(jsonFileToBeZippedAndDeleted); //assert compressed response even with missing uncompressed assertResponse(compClient.execute(get), true, null, "gz", "application/json"); } finally { client.getConnectionManager().shutdown(); } } @Test public void testCorrectResourceSelected() throws IOException, URISyntaxException { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path/page.html"); TestHttpClient client = new TestHttpClient(); Path rootPath = Paths.get(getClass().getResource("page.html").toURI()).getParent(); try (CloseableHttpClient compClient = HttpClientBuilder.create().build()){ DefaultServer.setRootHandler(new CanonicalPathHandler() .setNext(new PathHandler() .addPrefixPath("/path", new EncodingHandler(new ContentEncodingRepository() .addEncodingHandler("gzip", new GzipEncodingProvider(), 50, Predicates.truePredicate())) .setNext(new ResourceHandler(new PreCompressedResourceSupplier(new PathResourceManager(rootPath, 10485760)).addEncoding("gzip", ".gzip")) .setDirectoryListingEnabled(true))) )); //assert response without compression final String plainResponse = assertResponse(client.execute(get), false); //assert compressed response generated by filter assertResponse(compClient.execute(get), true, plainResponse); //generate resources generatePreCompressedResource("gzip"); generatePreCompressedResource("nonsense"); generatePreCompressedResource("gzip.nonsense"); //assert compressed response that was pre compressed assertResponse(compClient.execute(get), true, plainResponse, "gzip", "text/html"); } finally { client.getConnectionManager().shutdown(); } } private void generateGZipFile(Path source, Path target) throws IOException { byte[] buffer = new byte[1024]; GZIPOutputStream gzos = new GZIPOutputStream(new FileOutputStream(target.toFile())); FileInputStream in = new FileInputStream(source.toFile()); int len; while ((len = in.read(buffer)) > 0) { gzos.write(buffer, 0, len); } in.close(); gzos.finish(); gzos.close(); } private void replaceStringInFile(Path file, String original, String replacement) throws IOException { String content = new String(Files.readAllBytes(file), StandardCharsets.UTF_8); content = content.replaceAll(original, replacement); Files.write(file, content.getBytes(StandardCharsets.UTF_8)); } private String assertResponse(HttpResponse response, boolean encoding) throws IOException { return assertResponse(response, encoding, null, null, "text/html"); } private String assertResponse(HttpResponse response, boolean encoding, String compareWith) throws IOException { return assertResponse(response, encoding, compareWith, "web", "text/html"); } /** * Series of assertions checking response code, headers and response content */ private String assertResponse(HttpResponse response, boolean encoding, String compareWith, String extension, String contentType) throws IOException { Assert.assertEquals(StatusCodes.OK, response.getStatusLine().getStatusCode()); String body = HttpClientUtils.readResponse(response); Header[] headers = response.getHeaders(Headers.CONTENT_TYPE_STRING); Assert.assertEquals(contentType, headers[0].getValue()); if (encoding) { assert response.getEntity() instanceof DecompressingEntity; //no other nice way to be sure we get back gzipped content } else { Assert.assertNull(response.getFirstHeader(Headers.CONTENT_ENCODING_STRING)); } if (compareWith != null) { Assert.assertEquals(compareWith.replace("\r", "").replace("web", extension), body.replace("\r", "")); //ignore line ending differences and change inside of html page } return body; } /** * Creates compressed resource made by compressing page.html which content is updated before with {@param extension} * and after compression returned to original content */ private void generatePreCompressedResource(String extension) throws IOException, URISyntaxException{ Path rootPath = Paths.get(getClass().getResource("page.html").toURI()).getParent(); Path html = rootPath.resolve("page.html"); replaceStringInFile(html, "web", extension); generateGZipFile(html, rootPath.resolve("page.html." + extension)); replaceStringInFile(html, extension, "web"); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/file/data1.json000066400000000000000000000006761420065311100303120ustar00rootroot00000000000000{ "undertow": { "title": "compressed resource", "subtype": { "method": "test json compressed resource", "listInfo": { "test": { "json": "js", "compressed": "c", "resource": "this file", "type": "data", "def": { "thisFile": "Test purposes", "seeAlso": ["data2", "json"] }, "See": "compression" } } } } }undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/file/data2.json000066400000000000000000000006761420065311100303130ustar00rootroot00000000000000{ "undertow": { "title": "compressed resource", "subtype": { "method": "test json compressed resource", "listInfo": { "test": { "json": "js", "compressed": "c", "resource": "this file", "type": "data", "def": { "thisFile": "Test purposes", "seeAlso": ["data1", "json"] }, "See": "compression" } } } } }undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/file/page.html000066400000000000000000000014461420065311100302230ustar00rootroot00000000000000 Page A web page undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/file/subdir/000077500000000000000000000000001420065311100277045ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/file/subdir/a.txt000066400000000000000000000000071420065311100306620ustar00rootroot00000000000000a file undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/form/000077500000000000000000000000001420065311100264405ustar00rootroot00000000000000FormDataParserTestCase.java000066400000000000000000000232111420065311100335310ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/form/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.form; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.BlockingHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.Headers; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import java.nio.charset.StandardCharsets; import java.util.Collections; import junit.textui.TestRunner; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.utils.URLEncodedUtils; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HTTP; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; /** * @author Stuart Douglas */ @RunWith(DefaultServer.Parameterized.class) public class FormDataParserTestCase { static class AggregateRunner extends TestRunner { } private final HttpHandler rootHandler; public FormDataParserTestCase(final HttpHandler rootHandler) { this.rootHandler = rootHandler; } @Parameterized.Parameters public static Collection handlerChains() { List ret = new ArrayList<>(); // create the encoded form parser using UTF-8 to test direct decoding final FormParserFactory parserFactory = FormParserFactory.builder().withDefaultCharset("UTF-8").build(); HttpHandler fd = new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final FormDataParser parser = parserFactory.createParser(exchange); parser.parse(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { FormData data = exchange.getAttachment(FormDataParser.FORM_DATA); StringBuilder response = new StringBuilder(); Iterator it = data.iterator(); while (it.hasNext()) { String fd = it.next(); for (FormData.FormValue val : data.get(fd)) { if (response.length() > 0) { response.append("\n"); } response.append(fd).append(":").append(val.getValue()); } } exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain; charset=UTF-8"); exchange.getResponseSender().send(response.toString(), StandardCharsets.UTF_8); } }); } }; ret.add(new Object[]{fd}); final BlockingHandler blocking = new BlockingHandler(); blocking.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final FormDataParser parser = parserFactory.createParser(exchange); try { FormData data = parser.parseBlocking(); StringBuilder response = new StringBuilder(); Iterator it = data.iterator(); while (it.hasNext()) { String fd = it.next(); for (FormData.FormValue val : data.get(fd)) { if (response.length() > 0) { response.append("\n"); } response.append(fd).append(":").append(val.getValue()); } } exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain; charset=UTF-8"); exchange.getResponseSender().send(response.toString(), StandardCharsets.UTF_8); } catch (IOException e) { exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); } } }); ret.add(new Object[]{blocking}); return ret; } @Test public void testFormDataParsing() throws Exception { runTestUrlEncoded(new BasicNameValuePair("name", "A Value")); runTestUrlEncoded(new BasicNameValuePair("name", "A Value"), new BasicNameValuePair("Single-value", null)); runTestUrlEncoded(new BasicNameValuePair("name", "A Value"), new BasicNameValuePair("A/name/with_special*chars", "A $ value&& with=SomeCharacters")); runTestUrlEncoded(new BasicNameValuePair("name", "A Value"), new BasicNameValuePair("Single-value", null) , new BasicNameValuePair("A/name/with_special*chars", "A $ value&& with=SomeCharacters")); } @Test public void testRawFormDataParsingIncorrectValue() throws Exception { testRawFormDataParsing(new BasicNameValuePair("name", "%")); testRawFormDataParsing(new BasicNameValuePair("Name%", "value")); } @Test public void testUTF8FormDataParsing() throws Exception { // test name with direct UTF-8 characters String name = "abc\u0041\u00A9\u00E9\u0301\u0941\uD835\uDD0A%20+123"; String value = "test"; NameValuePair test = new BasicNameValuePair(name.replaceAll("%20", " ").replaceAll("\\+", " "), value); runTest(Collections.singletonList(test), new ByteArrayEntity((name + "=" + value).getBytes(StandardCharsets.UTF_8), ContentType.APPLICATION_FORM_URLENCODED)); // test value with direct UTF-8 characters name = "test"; value = "abc\u0041\u00A9\u00E9\u0301\u0941\uD835\uDD0A%20+123"; test = new BasicNameValuePair(name, value.replaceAll("%20", " ").replaceAll("\\+", " ")); runTest(Collections.singletonList(test), new ByteArrayEntity((name + "=" + value).getBytes(StandardCharsets.UTF_8), ContentType.APPLICATION_FORM_URLENCODED)); } private void testRawFormDataParsing(NameValuePair wrongPair) throws Exception { NameValuePair correctPair = new BasicNameValuePair("correctName", "A Value"); NameValuePair correctPair2 = new BasicNameValuePair("correctName2", "A Value2"); StringBuilder stringBuilder = new StringBuilder(); stringBuilder .append(URLEncodedUtils.format(java.util.Collections.singleton(correctPair), HTTP.DEF_CONTENT_CHARSET)) .append("&") .append(wrongPair.getName()).append("=").append(wrongPair.getValue()) .append("&") .append(URLEncodedUtils.format(java.util.Collections.singleton(correctPair2), HTTP.DEF_CONTENT_CHARSET)); final List expectedData = new ArrayList<>(); expectedData.add(correctPair); expectedData.add(correctPair2); runTest(expectedData, new StringEntity(stringBuilder.toString(), ContentType.APPLICATION_FORM_URLENCODED)); } private void runTestUrlEncoded(final NameValuePair... pairs) throws Exception { final List data = new ArrayList<>(Arrays.asList(pairs)); runTest(data, new UrlEncodedFormEntity(data)); } private void runTest(List data, HttpEntity entity) throws Exception { DefaultServer.setRootHandler(rootHandler); TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); post.setHeader(Headers.CONTENT_TYPE_STRING, FormEncodedDataDefinition.APPLICATION_X_WWW_FORM_URLENCODED); post.setEntity(entity); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); checkResult(data, result); } finally { client.getConnectionManager().shutdown(); } } private void checkResult(final List data, final HttpResponse result) throws IOException { Map res = new HashMap<>(); String content = HttpClientUtils.readResponse(result); for(String value : content.split("\n")) { String[] split = value.split(":"); res.put(split[0], split.length == 1 ? "" : split[1]); } Assert.assertEquals(data.size(), res.size()); for (NameValuePair vp : data) { Assert.assertEquals(vp.getValue() == null ? "" : vp.getValue(), res.get(vp.getName())); } } } MultipartFormDataParserTestCase.java000066400000000000000000000377641420065311100354550ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/form/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.form; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.HashMap; import java.util.Map; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.Charsets; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.entity.mime.FormBodyPart; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntity; import org.apache.http.entity.mime.content.FileBody; import org.apache.http.entity.mime.content.StringBody; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.IoUtils; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.BlockingHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.HeaderValues; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class MultipartFormDataParserTestCase { private static HttpHandler createHandler() { return new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final FormDataParser parser = FormParserFactory.builder().build().createParser(exchange); try { FormData data = parser.parseBlocking(); exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); if (data.getFirst("formValue").getValue().equals("myValue")) { FormData.FormValue file = data.getFirst("file"); if (file.isFile()) { if (file.getPath() != null) { if (new String(Files.readAllBytes(file.getPath())).startsWith("file contents")) { exchange.setStatusCode(StatusCodes.OK); } } } } exchange.endExchange(); } catch (Throwable e) { e.printStackTrace(); exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); exchange.endExchange(); } finally { IoUtils.safeClose(parser); } } }; } @Test public void testFileUpload() throws Exception { DefaultServer.setRootHandler(new BlockingHandler(createHandler())); TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); //post.setHeader(Headers.CONTENT_TYPE, MultiPartHandler.MULTIPART_FORM_DATA); MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); entity.addPart("formValue", new StringBody("myValue", "text/plain", StandardCharsets.UTF_8)); entity.addPart("file", new FileBody(new File(MultipartFormDataParserTestCase.class.getResource("uploadfile.txt").getFile()))); post.setEntity(entity); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } @Test public void testQuotedBoundary() throws Exception { DefaultServer.setRootHandler(new BlockingHandler(createHandler())); TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); post.setHeader(Headers.CONTENT_TYPE_STRING, "multipart/form-data; boundary=\"s58IGsuzbg6GBG1yIgUO8;n4WkVf7clWMje\""); StringEntity entity = new StringEntity("--s58IGsuzbg6GBG1yIgUO8;n4WkVf7clWMje\r\n" + "Content-Disposition: form-data; name=\"formValue\"\r\n" + "\r\n" + "myValue\r\n" + "--s58IGsuzbg6GBG1yIgUO8;n4WkVf7clWMje\r\n" + "Content-Disposition: form-data; name=\"file\"; filename=\"uploadfile.txt\"\r\n" + "Content-Type: application/octet-stream\r\n" + "\r\n" + "file contents\r\n" + "\r\n" + "--s58IGsuzbg6GBG1yIgUO8;n4WkVf7clWMje--\r\n"); post.setEntity(entity); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } @Test public void testFileUploadWithEagerParsing() throws Exception { DefaultServer.setRootHandler(new EagerFormParsingHandler().setNext(createHandler())); TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); //post.setHeader(Headers.CONTENT_TYPE, MultiPartHandler.MULTIPART_FORM_DATA); MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); entity.addPart("formValue", new StringBody("myValue", "text/plain", StandardCharsets.UTF_8)); entity.addPart("file", new FileBody(new File(MultipartFormDataParserTestCase.class.getResource("uploadfile.txt").getFile()))); post.setEntity(entity); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } @Test public void testFileUploadWithEagerParsingAndNonASCIIFilename() throws Exception { DefaultServer.setRootHandler(new EagerFormParsingHandler().setNext(createHandler())); TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); MultipartEntity entity = new MultipartEntity(); entity.addPart("formValue", new StringBody("myValue", "text/plain", StandardCharsets.UTF_8)); File uploadfile = new File(MultipartFormDataParserTestCase.class.getResource("uploadfile.txt").getFile()); FormBodyPart filePart = new FormBodyPart("file", new FileBody(uploadfile, "τεστ", "application/octet-stream", Charsets.UTF_8.toString())); filePart.addField("Content-Disposition", "form-data; name=\"file\"; filename*=\"utf-8''%CF%84%CE%B5%CF%83%CF%84.txt\""); entity.addPart(filePart); post.setEntity(entity); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } private static HttpHandler createInMemoryReadingHandler(final long fileSizeThreshold) { return new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { MultiPartParserDefinition multiPartParserDefinition = new MultiPartParserDefinition(); multiPartParserDefinition.setFileSizeThreshold(fileSizeThreshold); final FormDataParser parser = FormParserFactory.builder(false) .addParsers(new FormEncodedDataDefinition(), multiPartParserDefinition) .build().createParser(exchange); try { FormData data = parser.parseBlocking(); exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); if (data.getFirst("formValue").getValue().equals("myValue")) { FormData.FormValue file = data.getFirst("file"); if (file.isFileItem()) { exchange.setStatusCode(StatusCodes.OK); logResult(exchange, file.getFileItem().isInMemory(), file.getFileName(), stream2String(file)); } } exchange.endExchange(); } catch (Throwable e) { e.printStackTrace(); exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); exchange.endExchange(); } finally { IoUtils.safeClose(parser); } } private String stream2String(FormData.FormValue file) throws IOException { try (InputStream is = file.getFileItem().getInputStream()) { StringWriter sw = new StringWriter(); IOUtils.copy(is, sw, "UTF-8"); return sw.toString(); } } private String getFileName(FormData.FormValue data) { HeaderValues cdHeaders = data.getHeaders().get("content-disposition"); for (String cdHeader : cdHeaders) { if (cdHeader.startsWith("form-data")) { return cdHeader.substring(cdHeader.indexOf("filename=") + "filename=".length()).replace("\"", ""); } } return null; } private void logResult(HttpServerExchange exchange, boolean inMemory, String fileName, String content) throws IOException { String res = String.format("in_memory:%s;file_name:%s;hash:%s", inMemory, fileName, DigestUtils.md5Hex(content)); final OutputStream outputStream = exchange.getOutputStream(); outputStream.write(res.getBytes()); outputStream.close(); } }; } @Test public void testFileUploadWithSmallFileSizeThreshold() throws Exception { DefaultServer.setRootHandler(new BlockingHandler(createInMemoryReadingHandler(10))); TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); entity.addPart("formValue", new StringBody("myValue", "text/plain", StandardCharsets.UTF_8)); entity.addPart("file", new FileBody(new File(MultipartFormDataParserTestCase.class.getResource("uploadfile.txt").getFile()))); post.setEntity(entity); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String resp = HttpClientUtils.readResponse(result); Map parsedResponse = parse(resp); Assert.assertEquals("false", parsedResponse.get("in_memory")); Assert.assertEquals("uploadfile.txt", parsedResponse.get("file_name")); Assert.assertEquals(DigestUtils.md5Hex(new FileInputStream(new File(MultipartFormDataParserTestCase.class.getResource("uploadfile.txt").getFile()))), parsedResponse.get("hash")); } finally { client.getConnectionManager().shutdown(); } } @Test public void testFileUploadWithLargeFileSizeThreshold() throws Exception { DefaultServer.setRootHandler(new BlockingHandler(createInMemoryReadingHandler(10_000))); TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); entity.addPart("formValue", new StringBody("myValue", "text/plain", StandardCharsets.UTF_8)); entity.addPart("file", new FileBody(new File(MultipartFormDataParserTestCase.class.getResource("uploadfile.txt").getFile()))); post.setEntity(entity); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String resp = HttpClientUtils.readResponse(result); Map parsedResponse = parse(resp); Assert.assertEquals("true", parsedResponse.get("in_memory")); Assert.assertEquals("uploadfile.txt", parsedResponse.get("file_name")); Assert.assertEquals(DigestUtils.md5Hex(new FileInputStream(new File(MultipartFormDataParserTestCase.class.getResource("uploadfile.txt").getFile()))), parsedResponse.get("hash")); } finally { client.getConnectionManager().shutdown(); } } @Test public void testFileUploadWithMediumFileSizeThresholdAndLargeFile() throws Exception { int fileSizeThreshold = 1000; DefaultServer.setRootHandler(new BlockingHandler(createInMemoryReadingHandler(fileSizeThreshold))); TestHttpClient client = new TestHttpClient(); File file = new File("tmp_upload_file.txt"); file.createNewFile(); try { writeLargeFileContent(file, fileSizeThreshold * 2); HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path"); MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); entity.addPart("formValue", new StringBody("myValue", "text/plain", StandardCharsets.UTF_8)); entity.addPart("file", new FileBody(file)); post.setEntity(entity); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String resp = HttpClientUtils.readResponse(result); Map parsedResponse = parse(resp); Assert.assertEquals("false", parsedResponse.get("in_memory")); Assert.assertEquals("tmp_upload_file.txt", parsedResponse.get("file_name")); Assert.assertEquals(DigestUtils.md5Hex(new FileInputStream(file)), parsedResponse.get("hash")); } finally { file.delete(); client.getConnectionManager().shutdown(); } } private void writeLargeFileContent(File file, int size) throws IOException { int textLength = "content".getBytes().length; FileOutputStream fos = new FileOutputStream(file); for (int i = 0; i < size / textLength; i++) { fos.write("content".getBytes()); } fos.flush(); fos.close(); } private Map parse(String resp) { Map parsed = new HashMap<>(); String[] split = resp.split(";"); for (String s : split) { String[] pair = s.split(":"); parsed.put(pair[0], pair[1]); } return parsed; } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/form/uploadfile.txt000066400000000000000000000000161420065311100313220ustar00rootroot00000000000000file contents undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/largerange.txt000066400000000000000000000020121420065311100303400ustar00rootroot000000000000001:0123456789#2:0123456789#3:0123456789#4:0123456789#5:0123456789#6:0123456789#7:0123456789#8:0123456789#9:0123456789#10:0123456789#11:0123456789#12:0123456789#13:0123456789#14:0123456789#15:0123456789#16:0123456789#17:0123456789#18:0123456789#19:0123456789#20:0123456789#21:0123456789#22:0123456789#23:0123456789#24:0123456789#25:0123456789#26:0123456789#27:0123456789#28:0123456789#29:0123456789#30:0123456789#31:0123456789#32:0123456789#33:0123456789#34:0123456789#35:0123456789#36:0123456789#37:0123456789#38:0123456789#39:0123456789#40:0123456789#41:0123456789#42:0123456789#43:0123456789#44:0123456789#45:0123456789#46:0123456789#47:0123456789#48:0123456789#49:0123456789#50:0123456789#51:0123456789#52:0123456789#53:0123456789#54:0123456789#55:0123456789#56:0123456789#57:0123456789#58:0123456789#59:0123456789#60:0123456789#61:0123456789#62:0123456789#63:0123456789#64:0123456789#65:0123456789#66:0123456789#67:0123456789#68:0123456789#69:0123456789#70:0123456789#71:0123456789#72:0123456789#73:0123456789#74:0123456789abcdefg#undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/path/000077500000000000000000000000001420065311100264315ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/path/PathTestCase.java000066400000000000000000000143441420065311100316320ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.path; import java.io.IOException; import java.util.Collections; import java.util.Deque; import java.util.Map; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.PathHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.HttpString; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import io.undertow.testutils.TestHttpClient; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; /** * Tests that the path handler works as expected * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class PathTestCase { public static final String MATCHED = "matched"; public static final String PATH = "path"; @Test public void testBasicPathHanding() throws IOException { TestHttpClient client = new TestHttpClient(); try { final PathHandler handler = new PathHandler(); handler.addPrefixPath("a", new RemainingPathHandler("/a")); handler.addPrefixPath("/aa", new RemainingPathHandler("/aa")); handler.addExactPath("/aa", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("Exact /aa match:" + exchange.getRelativePath() + ":" + exchange.getResolvedPath()); } }); handler.addPrefixPath("/aa/anotherSubPath", new RemainingPathHandler("/aa/anotherSubPath")); final PathHandler sub = new PathHandler(); handler.addPrefixPath("/path", sub); sub.addPrefixPath("/subpath", new RemainingPathHandler("/subpath")); sub.addPrefixPath("/", new RemainingPathHandler("/path")); DefaultServer.setRootHandler(handler); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/notamatchingpath"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.NOT_FOUND, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/"); result = client.execute(get); Assert.assertEquals(StatusCodes.NOT_FOUND, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); runPathTest(client, "/path", "/path", ""); runPathTest(client, "/path/a", "/path", "/a"); runPathTest(client, "/path/subpath", "/subpath", ""); runPathTest(client, "/path/subpath/", "/subpath", "/"); runPathTest(client, "/path/subpath/foo", "/subpath", "/foo"); runPathTest(client, "/a", "/a", ""); runPathTest(client, "/aa/anotherSubPath", "/aa/anotherSubPath", ""); runPathTest(client, "/aa/anotherSubPath/bob", "/aa/anotherSubPath", "/bob"); runPathTest(client, "/aa/b?a=b", "/aa", "/b", Collections.singletonMap("a", "b")); runPathTest(client, "/path/:bar/baz", "/path", "/:bar/baz"); //now test the exact path match get = new HttpGet(DefaultServer.getDefaultServerURL() + "/aa"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("Exact /aa match::/aa", HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } private void runPathTest(TestHttpClient client, String path, String expectedMatch, String expectedRemaining) throws IOException { runPathTest(client, path, expectedMatch, expectedRemaining, Collections.emptyMap()); } private void runPathTest(TestHttpClient client, String path, String expectedMatch, String expectedRemaining, Map queryParams) throws IOException { HttpResponse result;HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + path); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] header = result.getHeaders(MATCHED); Assert.assertEquals(expectedMatch, header[0].getValue()); header = result.getHeaders(PATH); Assert.assertEquals(expectedRemaining, header[0].getValue()); HttpClientUtils.readResponse(result); for(Map.Entry entry : queryParams.entrySet()) { header = result.getHeaders(entry.getKey()); Assert.assertEquals(entry.getValue(), header[0].getValue()); } } private static class RemainingPathHandler implements HttpHandler { private final String matched; private RemainingPathHandler(String matched) { this.matched = matched; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseHeaders().add(new HttpString(MATCHED), matched); exchange.getResponseHeaders().add(new HttpString(PATH), exchange.getRelativePath()); for(Map.Entry> param : exchange.getQueryParameters().entrySet()) { exchange.getResponseHeaders().put(new HttpString(param.getKey()), param.getValue().getFirst()); } exchange.endExchange(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/proxy/000077500000000000000000000000001420065311100266565ustar00rootroot00000000000000AbstractLoadBalancingProxyTestCase.java000066400000000000000000000424171420065311100363120ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/proxy/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.concurrent.CountDownLatch; import io.undertow.Undertow; import io.undertow.UndertowLogger; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.session.InMemorySessionManager; import io.undertow.server.session.Session; import io.undertow.server.session.SessionAttachmentHandler; import io.undertow.server.session.SessionCookieConfig; import io.undertow.server.session.SessionManager; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.HttpOneOnly; import io.undertow.testutils.TestHttpClient; import io.undertow.util.AttachmentKey; import io.undertow.util.Protocols; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DecompressingHttpClient; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.IoUtils; import org.xnio.XnioWorker; import static io.undertow.Handlers.jvmRoute; import static io.undertow.Handlers.path; /** * Tests the load balancing proxy * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public abstract class AbstractLoadBalancingProxyTestCase { private static final String COUNT = "count"; public static final String RESPONSE_BODY = "This is a response body"; protected static Undertow server1; protected static Undertow server2; private static volatile boolean firstFail = true; @BeforeClass public static void setupFailTest() { firstFail = true; } protected static final int IDLE_TIMEOUT = 1000; @AfterClass public static void teardown() { XnioWorker worker1 = null, worker2 = null; int countDown = 0; try { if (server1 != null) { final XnioWorker worker = server1.getWorker(); server1.stop(); // if stop did not shutdown the worker, we need to run the latch to prevent a Address already in use (UNDERTOW-1960) if (worker != null && !worker.isShutdown()) { countDown++; worker1 = worker; } } } finally { try { if (server2 != null) { final XnioWorker worker = server2.getWorker(); server2.stop(); // if stop did not shutdown the worker, we need to run the latch to prevent a Address already in use (UNDERTOW-1960) if (worker != null && !worker.isShutdown() && worker != worker1) { worker2 = worker; countDown ++; } } } finally { if (countDown != 0) { // TODO this is needed solely for ssl servers; replace this by the mechanism described in UNDERTOW-1648 once it is implemented final CountDownLatch latch = new CountDownLatch(countDown); if (worker1 != null) worker1.getIoThread().execute(latch::countDown); if (worker2 != null) worker2.getIoThread().execute(latch::countDown); try { latch.await(); //double protection, we need to guarantee that the servers have stopped, and some environments seem to need a small delay to re-bind the socket Thread.sleep(1000); } catch (InterruptedException e) { //ignore } } } } } @Test public void testLoadShared() throws IOException { final StringBuilder resultString = new StringBuilder(); for (int i = 0; i < 6; ++i) { DecompressingHttpClient client = new DecompressingHttpClient(new TestHttpClient()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/name"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); resultString.append(HttpClientUtils.readResponse(result)); resultString.append(' '); } finally { client.getConnectionManager().shutdown(); } } Assert.assertTrue(resultString.toString().contains("server1")); Assert.assertTrue(resultString.toString().contains("server2")); } @Test public void testAbruptClosed() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/close"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.SERVICE_UNAVAILABLE, result.getStatusLine().getStatusCode()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testUrlEncoding() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/url/foo=bar"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("/url/foo=bar", HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } @Test @HttpOneOnly public void testOldBackend() throws IOException { TestHttpClient client = new TestHttpClient(); try { for(int i = 0; i < 10; ++i) { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/old"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(RESPONSE_BODY, HttpClientUtils.readResponse(result)); } } finally { client.getConnectionManager().shutdown(); } } @Test public void testMaxRetries() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/fail"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("/fail:false", HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } @Test public void testLoadSharedWithServerShutdown() throws Exception { final StringBuilder resultString = new StringBuilder(); for (int i = 0; i < 6; ++i) { TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/name"); HttpResponse result = client.execute(get); Assert.assertEquals("Test failed with i=" + i, StatusCodes.OK, result.getStatusLine().getStatusCode()); resultString.append(HttpClientUtils.readResponse(result)); resultString.append(' '); server1.stop(); Thread.sleep(600); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/name"); result = client.execute(get); Assert.assertEquals("Test failed with i=" + i, StatusCodes.OK, result.getStatusLine().getStatusCode()); resultString.append(HttpClientUtils.readResponse(result)); resultString.append(' '); server1.start(); server2.stop(); Thread.sleep(600); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/name"); result = client.execute(get); Assert.assertEquals("Test failed with i=" + i, StatusCodes.OK, result.getStatusLine().getStatusCode()); resultString.append(HttpClientUtils.readResponse(result)); resultString.append(' '); server2.start(); } Assert.assertTrue(resultString.toString().contains("server1")); Assert.assertTrue(resultString.toString().contains("server2")); } @Test public void testStickySessions() throws IOException { int expected = 0; TestHttpClient client = new TestHttpClient(); try { for (int i = 0; i < 6; ++i) { try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/session"); get.addHeader("Connection", "close"); HttpResponse result = client.execute(get); Assert.assertEquals("Test failed with i=" + i, StatusCodes.OK, result.getStatusLine().getStatusCode()); int count = Integer.parseInt(HttpClientUtils.readResponse(result)); Assert.assertEquals(expected++, count); } catch (AssertionError e) { throw e; } catch (Exception e) { throw new AssertionError("Test failed with i=" + i, e); } } } finally { client.getConnectionManager().shutdown(); } } //see https://issues.jboss.org/browse/UNDERTOW-276 @Test public void testDuplicateHeaders() throws IOException { int expected = 0; TestHttpClient client = new TestHttpClient(); try { for (int i = 0; i < 6; ++i) { try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/session"); get.addHeader("a", "b"); get.addHeader("a", "b"); get.addHeader("a", "b"); get.addHeader("a", "b"); get.addHeader("a", "b"); get.addHeader("a", "b"); get.addHeader("a", "b"); get.addHeader("a", "b"); get.addHeader("Connection", "close"); HttpResponse result = client.execute(get); Assert.assertEquals("Test failed with i=" + i, StatusCodes.OK, result.getStatusLine().getStatusCode()); int count = Integer.parseInt(HttpClientUtils.readResponse(result)); Assert.assertEquals("Test failed with i=" + i, expected++, count); } catch (AssertionError e) { throw e; } catch (Exception e) { throw new AssertionError("Test failed with i=" + i, e); } } } finally { client.getConnectionManager().shutdown(); } } @Test public void testConnectionTimeout() throws Exception { TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/timeout"); get.addHeader("Connection", "close"); HttpResponse result = client.execute(get); boolean res = Boolean.parseBoolean(HttpClientUtils.readResponse(result)); Assert.assertEquals(false, res); try { for (int i = 0; i < 20; ++i) { //try and make sure that all IO threads have been used, this is not reliable however result = client.execute(get); HttpClientUtils.readResponse(result); } result = client.execute(get); res = Boolean.parseBoolean(HttpClientUtils.readResponse(result)); //Assert.assertEquals(true, res); //this will fail sometime, unless we make a huge number of requests to make sure all IO threads are utilised Thread.sleep(IDLE_TIMEOUT + 1000); UndertowLogger.ROOT_LOGGER.info("Sending timed out request"); result = client.execute(get); res = Boolean.parseBoolean(HttpClientUtils.readResponse(result)); Assert.assertEquals(false, res); } finally { client.getConnectionManager().shutdown(); } } private static final AttachmentKey EXISTING = AttachmentKey.create(Boolean.class); protected static HttpHandler getRootHandler(String s1, String server1) { final SessionCookieConfig sessionConfig = new SessionCookieConfig(); return jvmRoute("JSESSIONID", s1, path() .addPrefixPath("/session", new SessionAttachmentHandler(new SessionTestHandler(sessionConfig), new InMemorySessionManager(""), sessionConfig)) .addPrefixPath("/name", new StringSendHandler(server1)) .addPrefixPath("/url", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send(exchange.getRequestURI()); } }) .addPrefixPath("/path", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send(exchange.getRequestURI()); } }) .addPrefixPath("/fail", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if (firstFail) { firstFail = false; IoUtils.safeClose(exchange.getConnection()); return; } exchange.getResponseSender().send(exchange.getRequestURI() + ":" + firstFail); } }).addPrefixPath("/timeout", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if (exchange.getConnection().getAttachment(EXISTING) == null) { exchange.getConnection().putAttachment(EXISTING, true); exchange.getResponseSender().send("false"); } else { exchange.getResponseSender().send("true"); } } }).addPrefixPath("/close", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { IoUtils.safeClose(exchange.getConnection()); } }).addPrefixPath("/old", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if(exchange.isInIoThread()) { exchange.dispatch(this); return; } exchange.startBlocking(); exchange.setProtocol(Protocols.HTTP_1_0); exchange.getOutputStream().write(RESPONSE_BODY.getBytes(StandardCharsets.US_ASCII)); exchange.getOutputStream().flush(); } })); } protected static final class SessionTestHandler implements HttpHandler { private final SessionCookieConfig sessionConfig; protected SessionTestHandler(SessionCookieConfig sessionConfig) { this.sessionConfig = sessionConfig; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final SessionManager manager = exchange.getAttachment(SessionManager.ATTACHMENT_KEY); Session session = manager.getSession(exchange, sessionConfig); if (session == null) { session = manager.createSession(exchange, sessionConfig); session.setAttribute(COUNT, 0); } Integer count = (Integer) session.getAttribute(COUNT); session.setAttribute(COUNT, count + 1); exchange.getResponseSender().send("" + count); } } protected static final class StringSendHandler implements HttpHandler { private final String serverName; protected StringSendHandler(String serverName) { this.serverName = serverName; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send(serverName); } } } LoadBalancerConnectionPoolingTestCase.java000066400000000000000000000125371420065311100367650ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/proxypackage io.undertow.server.handlers.proxy; import io.undertow.Undertow; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.ServerConnection; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.ProxyIgnore; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.conn.PoolingClientConnectionManager; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.net.URI; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @RunWith(DefaultServer.class) @ProxyIgnore public class LoadBalancerConnectionPoolingTestCase { public static final int TTL = 2000; private static Undertow undertow; private static final Set activeConnections = Collections.newSetFromMap(new ConcurrentHashMap<>()); static final String host = DefaultServer.getHostAddress("default"); static int port = DefaultServer.getHostPort("default"); @BeforeClass public static void before() throws Exception { ProxyHandler proxyHandler = ProxyHandler.builder().setProxyClient(new LoadBalancingProxyClient() .setConnectionsPerThread(1) .setSoftMaxConnectionsPerThread(0) .setTtl(TTL) .setMaxQueueSize(1000) .addHost(new URI("http", null, host, port, null, null, null), "s1")) .setMaxRequestTime(10000).build(); // Default server uses 8 io threads which is hard to test against undertow = Undertow.builder() .setIoThreads(1) .addHttpListener(port + 1, host) .setHandler(proxyHandler) .build(); undertow.start(); DefaultServer.setRootHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { final ServerConnection con = exchange.getConnection(); if(!activeConnections.contains(con)) { System.out.println("added " + con); activeConnections.add(con); con.addCloseListener(new ServerConnection.CloseListener() { @Override public void closed(ServerConnection connection) { System.out.println("Closed " + connection); activeConnections.remove(connection); } }); } } }); } @AfterClass public static void after() { undertow.stop(); // sleep 1 s to prevent BindException (Address already in use) when running the CI try { Thread.sleep(1000); } catch (InterruptedException ignore) {} } @Test public void shouldReduceConnectionPool() throws Exception { ExecutorService executorService = Executors.newFixedThreadPool(10); PoolingClientConnectionManager conman = new PoolingClientConnectionManager(); conman.setDefaultMaxPerRoute(20); final TestHttpClient client = new TestHttpClient(conman); int requests = 20; final CountDownLatch latch = new CountDownLatch(requests); long ttlStartExpire = TTL + System.currentTimeMillis(); try { for (int i = 0; i < requests; ++i) { executorService.submit(new Runnable() { @Override public void run() { HttpGet get = new HttpGet("http://" + host + ":" + (port + 1)); try { HttpResponse response = client.execute(get); Assert.assertEquals(StatusCodes.OK, response.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(response); } catch (IOException e) { throw new RuntimeException(e); } finally { latch.countDown(); } } }); } if(!latch.await(2000, TimeUnit.MILLISECONDS)) { Assert.fail(); } } finally { client.getConnectionManager().shutdown(); executorService.shutdown(); } if(activeConnections.size() != 1) { //if the test is slow this line could be hit after the expire time //uncommon, but we guard against it to prevent intermittent failures if(System.currentTimeMillis() < ttlStartExpire) { Assert.fail("there should still be a connection"); } } long end = System.currentTimeMillis() + (TTL * 3); while (!activeConnections.isEmpty() && System.currentTimeMillis() < end) { Thread.sleep(100); } Assert.assertEquals(0, activeConnections.size()); } } LoadBalancingProxyAJPTestCase.java000066400000000000000000000050451420065311100351550ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/proxy/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy; import java.net.URI; import java.net.URISyntaxException; import org.junit.BeforeClass; import org.junit.runner.RunWith; import org.xnio.Options; import io.undertow.Undertow; import io.undertow.UndertowOptions; import io.undertow.testutils.DefaultServer; /** * Tests the load balancing proxy * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class LoadBalancingProxyAJPTestCase extends AbstractLoadBalancingProxyTestCase { @BeforeClass public static void setup() throws URISyntaxException { int port = DefaultServer.getHostPort("default"); server1 = Undertow.builder() .addAjpListener(port + 1, DefaultServer.getHostAddress("default")) .setSocketOption(Options.REUSE_ADDRESSES, true) .setServerOption(UndertowOptions.NO_REQUEST_TIMEOUT, IDLE_TIMEOUT) .setHandler(getRootHandler("s1", "server1")) .build(); server2 = Undertow.builder() .addAjpListener(port + 2, DefaultServer.getHostAddress("default")) .setSocketOption(Options.REUSE_ADDRESSES, true) .setServerOption(UndertowOptions.NO_REQUEST_TIMEOUT, IDLE_TIMEOUT) .setHandler(getRootHandler("s2", "server2")) .build(); server1.start(); server2.start(); DefaultServer.setRootHandler(ProxyHandler.builder().setProxyClient(new LoadBalancingProxyClient() .setConnectionsPerThread(16) .addHost(new URI("ajp", null, DefaultServer.getHostAddress("default"), port + 1, null, null, null), "s1") .addHost(new URI("ajp", null, DefaultServer.getHostAddress("default"), port + 2, null, null, null), "s2") ).setMaxRequestTime(10000) .setMaxConnectionRetries(2).build()); } } LoadBalancingProxyHTTP2TestCase.java000066400000000000000000000233011420065311100353770ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/proxy/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayDeque; import java.util.Deque; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.FutureResult; import org.xnio.IoFuture; import org.xnio.OptionMap; import org.xnio.Options; import io.undertow.Undertow; import io.undertow.UndertowOptions; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientExchange; import io.undertow.client.ClientRequest; import io.undertow.client.UndertowClient; import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.protocol.http2.Http2ServerConnection; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.HttpString; import io.undertow.util.Methods; import io.undertow.util.Protocols; import io.undertow.util.StatusCodes; import io.undertow.util.StringReadChannelListener; /** * Tests the load balancing proxy * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class LoadBalancingProxyHTTP2TestCase extends AbstractLoadBalancingProxyTestCase { @BeforeClass public static void setup() throws URISyntaxException { int port = DefaultServer.getHostPort("default"); final HttpHandler handler1 = getRootHandler("s1", "server1"); server1 = Undertow.builder() .addHttpsListener(port + 1, DefaultServer.getHostAddress("default"), DefaultServer.getServerSslContext()) .setServerOption(UndertowOptions.ENABLE_HTTP2, true) .setServerOption(UndertowOptions.NO_REQUEST_TIMEOUT, IDLE_TIMEOUT) .setSocketOption(Options.REUSE_ADDRESSES, true) .setHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if (!(exchange.getConnection() instanceof Http2ServerConnection)) { throw new RuntimeException("Not HTTP2"); } exchange.getResponseHeaders().add(new HttpString("X-Custom-Header"), "foo"); handler1.handleRequest(exchange); } }) .build(); final HttpHandler handler2 = getRootHandler("s2", "server2"); server2 = Undertow.builder() .addHttpsListener(port + 2, DefaultServer.getHostAddress("default"), DefaultServer.getServerSslContext()) .setServerOption(UndertowOptions.ENABLE_HTTP2, true) .setSocketOption(Options.REUSE_ADDRESSES, true) .setServerOption(UndertowOptions.NO_REQUEST_TIMEOUT, IDLE_TIMEOUT) .setHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if (!(exchange.getConnection() instanceof Http2ServerConnection)) { throw new RuntimeException("Not HTTP2"); } exchange.getResponseHeaders().add(new HttpString("X-Custom-Header"), "foo"); handler2.handleRequest(exchange); } }) .build(); server1.start(); server2.start(); UndertowXnioSsl ssl = new UndertowXnioSsl(DefaultServer.getWorker().getXnio(), OptionMap.EMPTY, DefaultServer.SSL_BUFFER_POOL, DefaultServer.createClientSslContext()); DefaultServer.setRootHandler(ProxyHandler.builder().setProxyClient(new LoadBalancingProxyClient() .setConnectionsPerThread(4) .addHost(new URI("https", null, DefaultServer.getHostAddress("default"), port + 1, null, null, null), "s1", ssl, OptionMap.create(UndertowOptions.ENABLE_HTTP2, true)) .addHost(new URI("https", null, DefaultServer.getHostAddress("default"), port + 2, null, null, null), "s2", ssl, OptionMap.create(UndertowOptions.ENABLE_HTTP2, true))) .setMaxRequestTime(10000) .setMaxConnectionRetries(2).build()); } @Before public void requireAlpn() { DefaultServer.assumeAlpnEnabled(); } @Test public void testHeadersAreLowercase() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/name"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); Header header = result.getFirstHeader("x-custom-header"); Assert.assertEquals("x-custom-header", header.getName()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testHttp2ClientMultipleStreamsThreadSafety() throws IOException, URISyntaxException, ExecutionException, InterruptedException, TimeoutException { //not actually a proxy test //but convent to put it here UndertowXnioSsl ssl = new UndertowXnioSsl(DefaultServer.getWorker().getXnio(), OptionMap.EMPTY, DefaultServer.SSL_BUFFER_POOL, DefaultServer.createClientSslContext()); final UndertowClient client = UndertowClient.getInstance(); final ClientConnection connection = client.connect(new URI("https", null, DefaultServer.getHostAddress(), DefaultServer.getHostPort() + 1, "/", null, null), DefaultServer.getWorker(), ssl, DefaultServer.getBufferPool(), OptionMap.create(UndertowOptions.ENABLE_HTTP2, true)).get(); final ExecutorService service = Executors.newFixedThreadPool(10); try { Deque> futures = new ArrayDeque<>(); for (int i = 0; i < 100; ++i) { final FutureResult future = new FutureResult<>(); futures.add(future); service.submit(new Callable() { @Override public String call() throws Exception { ClientRequest cr = new ClientRequest() .setMethod(Methods.GET) .setPath("/path") .setProtocol(Protocols.HTTP_1_1); connection.sendRequest(cr, new ClientCallback() { @Override public void completed(ClientExchange result) { result.setResponseListener(new ClientCallback() { @Override public void completed(ClientExchange result) { new StringReadChannelListener(DefaultServer.getBufferPool()) { @Override protected void stringDone(String string) { future.setResult(string); } @Override protected void error(IOException e) { future.setException(e); } }.setup(result.getResponseChannel()); } @Override public void failed(IOException e) { future.setException(e); } }); } @Override public void failed(IOException e) { future.setException(e); } }); return null; } }); } while (!futures.isEmpty()) { FutureResult future = futures.poll(); Assert.assertNotEquals(IoFuture.Status.WAITING, future.getIoFuture().awaitInterruptibly(10, TimeUnit.SECONDS)); Assert.assertEquals("/path", future.getIoFuture().get()); } } finally { service.shutdownNow(); } } } LoadBalancingProxyHTTP2ViaUpgradeTestCase.java000066400000000000000000000123151420065311100373520ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/proxy/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy; import io.undertow.Undertow; import io.undertow.UndertowOptions; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.protocol.http2.Http2ServerConnection; import io.undertow.server.protocol.http2.Http2UpgradeHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.HttpString; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.Options; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; /** * Tests the load balancing proxy * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class LoadBalancingProxyHTTP2ViaUpgradeTestCase extends AbstractLoadBalancingProxyTestCase { @BeforeClass public static void setup() throws URISyntaxException { int port = DefaultServer.getHostPort("default"); final HttpHandler handler1 = getRootHandler("s1", "server1"); server1 = Undertow.builder() .addHttpListener(port + 1, DefaultServer.getHostAddress("default")) .setServerOption(UndertowOptions.ENABLE_HTTP2, true) .setServerOption(UndertowOptions.NO_REQUEST_TIMEOUT, IDLE_TIMEOUT) .setSocketOption(Options.REUSE_ADDRESSES, true) .setHandler(new Http2UpgradeHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if (!(exchange.getConnection() instanceof Http2ServerConnection)) { throw new RuntimeException("Not HTTP2"); } exchange.getResponseHeaders().add(new HttpString("X-Custom-Header"), "foo"); System.out.println("server1 " + exchange.getRequestHeaders()); handler1.handleRequest(exchange); } })) .build(); final HttpHandler handler2 = getRootHandler("s2", "server2"); server2 = Undertow.builder() .addHttpListener(port + 2, DefaultServer.getHostAddress("default")) .setServerOption(UndertowOptions.ENABLE_HTTP2, true) .setServerOption(UndertowOptions.NO_REQUEST_TIMEOUT, IDLE_TIMEOUT) .setSocketOption(Options.REUSE_ADDRESSES, true) .setHandler(new Http2UpgradeHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if (!(exchange.getConnection() instanceof Http2ServerConnection)) { throw new RuntimeException("Not HTTP2"); } exchange.getResponseHeaders().add(new HttpString("X-Custom-Header"), "foo"); System.out.println("server2 " + exchange.getRequestHeaders()); handler2.handleRequest(exchange); } })) .build(); server1.start(); server2.start(); DefaultServer.setRootHandler(ProxyHandler.builder().setProxyClient(new LoadBalancingProxyClient() .setConnectionsPerThread(4) .addHost(new URI("h2c", null, DefaultServer.getHostAddress("default"), port + 1, null, null, null), "s1") .addHost(new URI("h2c", null, DefaultServer.getHostAddress("default"), port + 2, null, null, null), "s2")) .setMaxRequestTime(10000) .setMaxConnectionRetries(2).build()); } @Test public void testHeadersAreLowercase() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/name"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); Header header = result.getFirstHeader("x-custom-header"); Assert.assertEquals("x-custom-header", header.getName()); } finally { client.getConnectionManager().shutdown(); } } } LoadBalancingProxyHttpsTestCase.java000066400000000000000000000061771420065311100356540ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/proxy/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy; import io.undertow.Undertow; import io.undertow.UndertowOptions; import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.server.session.SessionCookieConfig; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.ProxyIgnore; import org.junit.BeforeClass; import org.junit.runner.RunWith; import org.xnio.OptionMap; import org.xnio.Options; import java.net.URI; import java.net.URISyntaxException; /** * Tests the load balancing proxy * * @author Stuart Douglas */ @RunWith(DefaultServer.class) @ProxyIgnore public class LoadBalancingProxyHttpsTestCase extends AbstractLoadBalancingProxyTestCase { @BeforeClass public static void setup() throws URISyntaxException { final SessionCookieConfig sessionConfig = new SessionCookieConfig(); int port = DefaultServer.getHostPort("default"); server1 = Undertow.builder() .addHttpsListener(port + 1, DefaultServer.getHostAddress("default"), DefaultServer.getServerSslContext()) .setSocketOption(Options.REUSE_ADDRESSES, true) .setServerOption(UndertowOptions.NO_REQUEST_TIMEOUT, IDLE_TIMEOUT) .setHandler(getRootHandler("s1", "server1")) .build(); server2 = Undertow.builder() .addHttpsListener(port + 2, DefaultServer.getHostAddress("default"), DefaultServer.getServerSslContext()) .setServerOption(UndertowOptions.ENABLE_SPDY, false) .setServerOption(UndertowOptions.NO_REQUEST_TIMEOUT, IDLE_TIMEOUT) .setSocketOption(Options.REUSE_ADDRESSES, true) .setHandler(getRootHandler("s2", "server2")) .build(); server1.start(); server2.start(); UndertowXnioSsl ssl = new UndertowXnioSsl(DefaultServer.getWorker().getXnio(), OptionMap.EMPTY, DefaultServer.SSL_BUFFER_POOL, DefaultServer.createClientSslContext()); DefaultServer.setRootHandler(ProxyHandler.builder().setProxyClient(new LoadBalancingProxyClient() .setConnectionsPerThread(4) .addHost(new URI("https", null, DefaultServer.getHostAddress("default"), port + 1, null, null, null), "s1", ssl) .addHost(new URI("https", null, DefaultServer.getHostAddress("default"), port + 2, null, null, null), "s2", ssl)) .setMaxRequestTime(10000) .setMaxConnectionRetries(2).build()); } } LoadBalancingProxyTestCase.java000066400000000000000000000060141420065311100346170ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/proxy/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy; import io.undertow.Undertow; import io.undertow.UndertowOptions; import io.undertow.predicate.Predicates; import io.undertow.server.handlers.encoding.ContentEncodingRepository; import io.undertow.server.handlers.encoding.EncodingHandler; import io.undertow.server.handlers.encoding.GzipEncodingProvider; import io.undertow.testutils.DefaultServer; import org.junit.BeforeClass; import org.junit.runner.RunWith; import org.xnio.Options; import java.net.URI; import java.net.URISyntaxException; /** * Tests the load balancing proxy * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class LoadBalancingProxyTestCase extends AbstractLoadBalancingProxyTestCase { @BeforeClass public static void setup() throws URISyntaxException { int port = DefaultServer.getHostPort("default"); server1 = Undertow.builder() .addHttpListener(port + 1, DefaultServer.getHostAddress("default")) .setSocketOption(Options.REUSE_ADDRESSES, true) .setServerOption(UndertowOptions.NO_REQUEST_TIMEOUT, IDLE_TIMEOUT) .setHandler(getRootHandler("s1", "server1")) .build(); server2 = Undertow.builder() .addHttpListener(port + 2, DefaultServer.getHostAddress("default")) .setSocketOption(Options.REUSE_ADDRESSES, true) .setServerOption(UndertowOptions.NO_REQUEST_TIMEOUT, IDLE_TIMEOUT) .setHandler(getRootHandler("s2", "server2")) .build(); server1.start(); server2.start(); ProxyHandler handler = ProxyHandler.builder().setProxyClient(new LoadBalancingProxyClient() .setConnectionsPerThread(4) .addHost(new URI("http", null, DefaultServer.getHostAddress("default"), port + 1, null, null, null), "s1") .addHost(new URI("http", null, DefaultServer.getHostAddress("default"), port + 2, null, null, null), "s2")) .setMaxRequestTime(10000) .setMaxConnectionRetries(2).build(); DefaultServer.setRootHandler(new EncodingHandler(handler, new ContentEncodingRepository() .addEncodingHandler("gzip", new GzipEncodingProvider(), 50, Predicates.truePredicate()))); } } LoadBalancingProxyWithCustomHostSelectorTestCase.java000066400000000000000000000106431420065311100412100ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/proxypackage io.undertow.server.handlers.proxy; import io.undertow.Undertow; import io.undertow.client.UndertowClient; import io.undertow.server.session.InMemorySessionManager; import io.undertow.server.session.SessionAttachmentHandler; import io.undertow.server.session.SessionCookieConfig; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.Options; import java.net.URI; import java.net.URISyntaxException; import static io.undertow.Handlers.jvmRoute; import static io.undertow.Handlers.path; @RunWith(DefaultServer.class) public class LoadBalancingProxyWithCustomHostSelectorTestCase { protected static Undertow server1; protected static Undertow server2; @BeforeClass public static void setup() throws URISyntaxException { final SessionCookieConfig sessionConfig = new SessionCookieConfig(); int port = DefaultServer.getHostPort("default"); server1 = Undertow.builder() .addHttpListener(port + 1, DefaultServer.getHostAddress("default")) .setSocketOption(Options.REUSE_ADDRESSES, true) .setHandler(jvmRoute("JSESSIONID", "s1", path() .addPrefixPath("/session", new SessionAttachmentHandler(new AbstractLoadBalancingProxyTestCase.SessionTestHandler(sessionConfig), new InMemorySessionManager(""), sessionConfig)) .addPrefixPath("/name", new AbstractLoadBalancingProxyTestCase.StringSendHandler("server1")))) .build(); server2 = Undertow.builder() .addHttpListener(port + 2, DefaultServer.getHostAddress("default")) .setSocketOption(Options.REUSE_ADDRESSES, true) .setHandler(jvmRoute("JSESSIONID", "s2", path() .addPrefixPath("/session", new SessionAttachmentHandler(new AbstractLoadBalancingProxyTestCase.SessionTestHandler(sessionConfig), new InMemorySessionManager(""), sessionConfig)) .addPrefixPath("/name", new AbstractLoadBalancingProxyTestCase.StringSendHandler("server2")))) .build(); server1.start(); server2.start(); LoadBalancingProxyClient.HostSelector hostSelector = new LoadBalancingProxyClient.HostSelector() { @Override public int selectHost(LoadBalancingProxyClient.Host[] availableHosts) { return 0; } }; DefaultServer.setRootHandler(ProxyHandler.builder().setProxyClient(new LoadBalancingProxyClient(UndertowClient.getInstance(), null, hostSelector) .setConnectionsPerThread(4) .addHost(new URI("http", null, DefaultServer.getHostAddress("default"), port + 1, null, null, null), "s1") .addHost(new URI("http", null, DefaultServer.getHostAddress("default"), port + 2, null, null, null), "s2")) .setMaxRequestTime(10000) .setMaxConnectionRetries(2).build()); } @AfterClass public static void teardown() { server1.stop(); server2.stop(); // sleep 1 s to prevent BindException (Address already in use) when running the CI try { Thread.sleep(1000); } catch (InterruptedException ignore) {} } // https://issues.jboss.org/browse/UNDERTOW-289 @Test public void testDistributeLoadToGivenHost() throws Throwable { final StringBuilder resultString = new StringBuilder(); for (int i = 0; i < 6; ++i) { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/name"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); resultString.append(HttpClientUtils.readResponse(result)); resultString.append(' '); } finally { client.getConnectionManager().shutdown(); } } Assert.assertTrue(resultString.toString().contains("server1")); Assert.assertFalse(resultString.toString().contains("server2")); } } ProxyHandlerXForwardedForTestCase.java000066400000000000000000000205031420065311100361520ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/proxypackage io.undertow.server.handlers.proxy; import static io.undertow.Handlers.jvmRoute; import static io.undertow.Handlers.path; import java.net.URI; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.OptionMap; import org.xnio.Options; import io.undertow.Undertow; import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.testutils.ProxyIgnore; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; /** * Created by ivannagy on 8/26/14. */ @RunWith(DefaultServer.class) @HttpOneOnly @ProxyIgnore public class ProxyHandlerXForwardedForTestCase { protected static Undertow server; protected static int port; protected static int sslPort; protected static int handlerPort; protected static UndertowXnioSsl ssl; @BeforeClass public static void setup() throws Exception { port = DefaultServer.getHostPort("default"); sslPort = port + 1; handlerPort = port + 2; DefaultServer.startSSLServer(); ssl = new UndertowXnioSsl(DefaultServer.getWorker().getXnio(), OptionMap.EMPTY, DefaultServer.SSL_BUFFER_POOL, DefaultServer.getClientSSLContext()); server = Undertow.builder() .addHttpsListener(handlerPort, DefaultServer.getHostAddress("default"), DefaultServer.getServerSslContext()) .setSocketOption(Options.REUSE_ADDRESSES, true) .setHandler(jvmRoute("JSESSIONID", "s1", path().addPrefixPath("/x-forwarded", new XForwardedHandler()))) .build(); server.start(); } @AfterClass public static void teardown() throws Exception { DefaultServer.stopSSLServer(); server.stop(); // sleep 1 s to prevent BindException (Address already in use) when running the CI try { Thread.sleep(1000); } catch (InterruptedException ignore) {} } private static void setProxyHandler(boolean rewriteHostHeader, boolean reuseXForwarded) throws Exception { DefaultServer.setRootHandler(ProxyHandler.builder().setProxyClient((new LoadBalancingProxyClient() .setConnectionsPerThread(4) .addHost(new URI("https", null, DefaultServer.getHostAddress("default"), handlerPort, null, null, null), "s1", ssl))) .setMaxRequestTime(10000) .setRewriteHostHeader(rewriteHostHeader) .setReuseXForwarded(reuseXForwarded).build()); } @Test public void testXForwarded() throws Exception { setProxyHandler(false, false); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/x-forwarded"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(port, Integer.parseInt(result.getFirstHeader(Headers.X_FORWARDED_PORT.toString()).getValue())); Assert.assertEquals("http", result.getFirstHeader(Headers.X_FORWARDED_PROTO.toString()).getValue()); Assert.assertEquals(DefaultServer.getHostAddress(), result.getFirstHeader(Headers.X_FORWARDED_HOST.toString()).getValue()); Assert.assertEquals(DefaultServer.getDefaultServerAddress().getAddress().getHostAddress(), result.getFirstHeader(Headers.X_FORWARDED_FOR.toString()).getValue()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testXForwardedSsl() throws Exception { setProxyHandler(false, false); TestHttpClient client = new TestHttpClient(); try { client.setSSLContext(DefaultServer.getClientSSLContext()); HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress() + "/x-forwarded"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(sslPort, Integer.parseInt(result.getFirstHeader(Headers.X_FORWARDED_PORT.toString()).getValue())); Assert.assertEquals("https", result.getFirstHeader(Headers.X_FORWARDED_PROTO.toString()).getValue()); Assert.assertEquals(DefaultServer.getHostAddress(), result.getFirstHeader(Headers.X_FORWARDED_HOST.toString()).getValue()); Assert.assertEquals(DefaultServer.getDefaultServerAddress().getAddress().getHostAddress(), result.getFirstHeader(Headers.X_FORWARDED_FOR.toString()).getValue()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testReuseXForwarded() throws Exception { setProxyHandler(false, true); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/x-forwarded"); get.addHeader(Headers.X_FORWARDED_FOR.toString(), "50.168.245.32"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(port, Integer.parseInt(result.getFirstHeader(Headers.X_FORWARDED_PORT.toString()).getValue())); Assert.assertEquals("http", result.getFirstHeader(Headers.X_FORWARDED_PROTO.toString()).getValue()); Assert.assertEquals(DefaultServer.getHostAddress(), result.getFirstHeader(Headers.X_FORWARDED_HOST.toString()).getValue()); Assert.assertEquals("50.168.245.32," + DefaultServer.getDefaultServerAddress().getAddress().getHostAddress(), result.getFirstHeader(Headers.X_FORWARDED_FOR.toString()).getValue()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testRewriteHostHeader() throws Exception { setProxyHandler(true, false); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/x-forwarded"); get.addHeader(Headers.X_FORWARDED_FOR.toString(), "50.168.245.32"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(port, Integer.parseInt(result.getFirstHeader(Headers.X_FORWARDED_PORT.toString()).getValue())); Assert.assertEquals("http", result.getFirstHeader(Headers.X_FORWARDED_PROTO.toString()).getValue()); Assert.assertEquals(String.format("%s:%d", DefaultServer.getHostAddress(), port), result.getFirstHeader(Headers.X_FORWARDED_HOST.toString()).getValue()); Assert.assertEquals(DefaultServer.getDefaultServerAddress().getAddress().getHostAddress(), result.getFirstHeader(Headers.X_FORWARDED_FOR.toString()).getValue()); } finally { client.getConnectionManager().shutdown(); } } protected static final class XForwardedHandler implements HttpHandler { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { // Copy the X-Fowarded* headers into the response if (exchange.getRequestHeaders().contains(Headers.X_FORWARDED_FOR)) exchange.getResponseHeaders().put(Headers.X_FORWARDED_FOR, exchange.getRequestHeaders().getFirst(Headers.X_FORWARDED_FOR)); if (exchange.getRequestHeaders().contains(Headers.X_FORWARDED_PROTO)) exchange.getResponseHeaders().put(Headers.X_FORWARDED_PROTO, exchange.getRequestHeaders().getFirst(Headers.X_FORWARDED_PROTO)); if (exchange.getRequestHeaders().contains(Headers.X_FORWARDED_HOST)) exchange.getResponseHeaders().put(Headers.X_FORWARDED_HOST, exchange.getRequestHeaders().getFirst(Headers.X_FORWARDED_HOST)); if (exchange.getRequestHeaders().contains(Headers.X_FORWARDED_PORT)) exchange.getResponseHeaders().put(Headers.X_FORWARDED_PORT, exchange.getRequestHeaders().getFirst(Headers.X_FORWARDED_PORT)); } } } ProxyPathHandlingTest.java000066400000000000000000000176251420065311100337200ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/proxypackage io.undertow.server.handlers.proxy; import io.undertow.Handlers; import io.undertow.Undertow; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.PathHandler; import io.undertow.testutils.TestHttpClient; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.After; import org.junit.Test; import java.io.IOException; import java.net.ServerSocket; import java.net.URI; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; public class ProxyPathHandlingTest { private final TargetServer targetServer = new TargetServer(); private final ProxyServer proxyServer = new ProxyServer(targetServer.uri); @After public void cleanup() { targetServer.stop(); proxyServer.stop(); // add a 1s sleep time to prevent BindException (Address already in use) when restarting the server try { Thread.sleep(1000); } catch (InterruptedException ignore) { } } @Test public void prefixRootToRoot() throws Exception { proxyServer.proxyPrefixPath("/", "/"); isProxied("", "/"); isProxied("/", "/"); isProxied("/foo", "/foo"); } @Test public void prefixRootToPath() throws Exception { proxyServer.proxyPrefixPath("/", "/path"); isProxied("", "/path/"); isProxied("/", "/path/"); isProxied("/foo", "/path/foo"); } @Test public void prefixPathToPath() throws Exception { proxyServer.proxyPrefixPath("/path", "/path"); isProxied("/path", "/path"); isProxied("/path/", "/path/"); isProxied("/path/foo", "/path/foo"); isNotProxied(""); isNotProxied("/"); isNotProxied("/foo"); } @Test public void prefixPathToRoot() throws Exception { proxyServer.proxyPrefixPath("/path", "/"); isProxied("/path", "/"); isProxied("/path/", "/"); isNotProxied(""); isNotProxied("/"); isNotProxied("/foo"); } @Test public void prefixPathSlashToRoot() throws Exception { proxyServer.proxyPrefixPath("/path/", "/"); isProxied("/path", "/"); isProxied("/path/", "/"); isNotProxied(""); isNotProxied("/"); isNotProxied("/foo"); } @Test public void exactRootToRoot() throws Exception { proxyServer.proxyExactPath("/", "/"); isProxied("", "/"); isProxied("/", "/"); isNotProxied("/foo"); } @Test public void exactRootToPath() throws Exception { proxyServer.proxyExactPath("/", "/path"); isProxied("", "/path"); isProxied("/", "/path"); isNotProxied("/foo"); } @Test public void exactRootToPathSlash() throws Exception { proxyServer.proxyExactPath("/", "/path/"); isProxied("", "/path/"); isProxied("/", "/path/"); isNotProxied("/foo"); } @Test public void exactPathToRoot() throws Exception { proxyServer.proxyExactPath("/path", "/"); isProxied("/path", "/"); isProxied("/path/", "/"); isNotProxied(""); isNotProxied("/"); isNotProxied("/foo"); isNotProxied("/path/foo"); } @Test public void exactPathSlashToRoot() throws Exception { proxyServer.proxyExactPath("/path/", "/"); isProxied("/path", "/"); isProxied("/path/", "/"); isNotProxied(""); isNotProxied("/"); isNotProxied("/foo"); isNotProxied("/path/foo"); } @Test public void exactPathToPath() throws Exception { proxyServer.proxyExactPath("/path", "/path"); isProxied("/path", "/path"); isProxied("/path/", "/path"); isNotProxied(""); isNotProxied("/"); isNotProxied("/foo"); isNotProxied("/path/foo"); } @Test public void exactPathToPathSlash() throws Exception { proxyServer.proxyExactPath("/path", "/path/"); isProxied("/path", "/path/"); isProxied("/path/", "/path/"); isNotProxied(""); isNotProxied("/"); isNotProxied("/foo"); isNotProxied("/path/foo"); } private void isProxied(String requestPath, String expectedTargetPath) throws IOException { assertEquals(200, httpGet(requestPath)); assertEquals(expectedTargetPath, targetServer.gotRequest(true)); } private void isNotProxied(String requestPath) throws IOException { assertEquals(404, httpGet(requestPath)); assertNull(targetServer.gotRequest(false)); } private int httpGet(String path) throws IOException { TestHttpClient http = new TestHttpClient(); HttpResponse response = http.execute(new HttpGet(proxyServer.uri + path)); return response.getStatusLine().getStatusCode(); } private static class ProxyServer { private final int port = FreePort.find(); private final Undertow server; private final PathHandler pathHandler = Handlers.path(); final String uri = "http://localhost:" + port; private final String targetUri; ProxyServer(String targetUri) { this.targetUri = targetUri; server = Undertow.builder() .addHttpListener(port, "0.0.0.0") .setHandler(pathHandler) .build(); server.start(); } void proxyPrefixPath(String proxyPath, String targetPath) { pathHandler.addPrefixPath(proxyPath, proxyHandler(targetPath)); } void proxyExactPath(String proxyPath, String targetPath) { pathHandler.addExactPath(proxyPath, proxyHandler(targetPath)); } void stop() { server.stop(); } private HttpHandler proxyHandler(String targetPath) { return ProxyHandler.builder().setProxyClient(( new SimpleProxyClientProvider(URI.create(targetUri + targetPath)))).build(); } } private static class TargetServer { private final int port = FreePort.find(); private final Undertow server; final String uri = "http://localhost:" + port; private final LinkedBlockingQueue gotRequestQueue = new LinkedBlockingQueue<>(); TargetServer() { server = Undertow.builder() .addHttpListener(port, "0.0.0.0") .setHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { gotRequestQueue.add(URI.create(exchange.getRequestURL()).getPath()); } }) .build(); server.start(); } void stop() { server.stop(); } String gotRequest(boolean wait) { String url = null; try { url = gotRequestQueue.poll( wait ? 10000 : 10, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); } return url; } } private static class FreePort { static int find() { int port = 0; while (port == 0) { ServerSocket socket = null; try { socket = new ServerSocket(0); port = socket.getLocalPort(); } catch (IOException e) { throw new RuntimeException("Failed finding free port", e); } finally { try { if (socket != null) socket.close(); } catch (IOException ignore) { } } } return port; } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/proxy/mod_cluster/000077500000000000000000000000001420065311100311765ustar00rootroot00000000000000AbstractModClusterTestBase.java000066400000000000000000000306321420065311100371660ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import static io.undertow.Handlers.jvmRoute; import static io.undertow.Handlers.path; import static io.undertow.testutils.DefaultServer.getClientSSLContext; import static io.undertow.testutils.DefaultServer.getHostAddress; import static io.undertow.testutils.DefaultServer.getHostPort; import java.io.IOException; import java.util.ArrayList; import java.util.List; import io.undertow.Undertow; import io.undertow.client.UndertowClient; import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.LocalNameResolvingHandler; import io.undertow.server.handlers.PathHandler; import io.undertow.server.handlers.ResponseCodeHandler; import io.undertow.server.session.InMemorySessionManager; import io.undertow.server.session.Session; import io.undertow.server.session.SessionAttachmentHandler; import io.undertow.server.session.SessionCookieConfig; import io.undertow.server.session.SessionManager; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.ProxyIgnore; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.cookie.Cookie; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.cookie.BasicClientCookie; import org.apache.http.message.BasicHeader; import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.runner.RunWith; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.ssl.XnioSsl; /** * @author Emanuel Muckenhuber */ @RunWith(DefaultServer.class) @ProxyIgnore public abstract class AbstractModClusterTestBase { protected static final MCMPTestClient.App NAME = new MCMPTestClient.App("/name", "localhost"); protected static final MCMPTestClient.App SESSION = new MCMPTestClient.App("/session", "localhost"); protected static Undertow[] servers; protected static DefaultHttpClient httpClient; protected static MCMPTestClient modClusterClient; protected static int port; protected static String hostName; private static ModCluster modCluster; private static XnioSsl xnioSsl; private static final UndertowClient undertowClient = UndertowClient.getInstance(); private static final String COUNT = "count"; protected List nodes; @BeforeClass public static final void beforeClass() { port = getHostPort("default"); hostName = getHostAddress("default"); xnioSsl = new UndertowXnioSsl(DefaultServer.getWorker().getXnio(), OptionMap.EMPTY, DefaultServer.SSL_BUFFER_POOL, getClientSSLContext()); } /** * Dynamically change the worker nodes protocol based on the test parameters * * @return the protocol type */ static String getType() { if (DefaultServer.isAjp()) { return "ajp"; } else if (DefaultServer.isHttps()) { return "https"; } else { return "http"; } } @BeforeClass public static void setupModCluster() { modCluster = ModCluster.builder(DefaultServer.getWorker(), undertowClient, xnioSsl).build(); final int serverPort = getHostPort("default"); final HttpHandler proxy = modCluster.createProxyHandler(); final HttpHandler mcmp = MCMPConfig.webBuilder() .setManagementHost(getHostAddress("default")) .setManagementPort(serverPort) .create(modCluster, ResponseCodeHandler.HANDLE_404); DefaultServer.setRootHandler(new LocalNameResolvingHandler(path(proxy).addPrefixPath("manager", mcmp))); modCluster.start(); httpClient = new DefaultHttpClient(); modClusterClient = new MCMPTestClient(httpClient, DefaultServer.getDefaultServerURL() + "/manager"); } @AfterClass public static void stopModCluster() { if (servers != null) { stopServers(); } modCluster.stop(); modCluster = null; httpClient.getConnectionManager().shutdown(); } /** * Register the nodes. Nodes registered using this method are automatically getting unregistered after the test. * * @param updateLoad update the load when registering, which will enable them * @param configs the configs * @throws IOException */ protected void registerNodes(boolean updateLoad, NodeTestConfig... configs) throws IOException { if (nodes == null) { nodes = new ArrayList<>(); } modClusterClient.info(); for (final NodeTestConfig config : configs) { nodes.add(config); modClusterClient.registerNode(config); } if (updateLoad) { updateLoad(configs); } } protected void updateLoad(final NodeTestConfig... configs) throws IOException { for (final NodeTestConfig config : configs) { modClusterClient.updateLoad(config.getJvmRoute(), 100); } } @After public void unregisterNodes() { try { modClusterClient.info(); } catch (IOException e) { e.printStackTrace(); } if (nodes != null) { for (final NodeTestConfig config : nodes) { try { modClusterClient.removeNode(config.getJvmRoute()); } catch (IOException e) { e.printStackTrace(); } } } nodes = null; // Clear all cookies after the test httpClient.getCookieStore().clear(); } static void stopServers() { if (servers != null) { for (final Undertow server : servers) { if (server == null) { continue; } try { server.stop(); } catch (Exception e) { e.printStackTrace(); } } servers = null; // sleep 2 s to prevent BindException (Address already in use) when running the CI try { Thread.sleep(2000); } catch (InterruptedException ignore) {} } } static void startServers(final NodeTestConfig... configs) { if(servers != null) { throw new IllegalStateException(); } final int l = configs.length; servers = new Undertow[l]; for (int i = 0; i < l; i++) { servers[i] = createNode(configs[i]); servers[i].start(); } } static String checkGet(final String context, int statusCode) throws IOException { return checkGet(context, statusCode, null); } static String checkGet(final String context, int statusCode, String route) throws IOException { final HttpGet get = get(context); if (route != null && getSessionRoute() == null) { BasicClientCookie cookie = new BasicClientCookie("JSESSIONID", "randomSessionID."+route); httpClient.getCookieStore().addCookie(cookie); } final HttpResponse result = httpClient.execute(get); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(statusCode, result.getStatusLine().getStatusCode()); if (route != null) { Assert.assertEquals(route, getSessionRoute()); } return response; } static HttpGet get(final String context) { return get(context, "localhost"); } static HttpGet get(final String context, final String host) { final HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + context); get.addHeader(new BasicHeader("Host", host)); return get; } protected static final class SessionTestHandler implements HttpHandler { private final String serverName; private final SessionCookieConfig sessionConfig; public SessionTestHandler(String serverName, SessionCookieConfig sessionConfig) { this.serverName = serverName; this.sessionConfig = sessionConfig; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final SessionManager manager = exchange.getAttachment(SessionManager.ATTACHMENT_KEY); Session session = manager.getSession(exchange, sessionConfig); if (session == null) { session = manager.createSession(exchange, sessionConfig); session.setAttribute(COUNT, 0); } Integer count = (Integer) session.getAttribute(COUNT); session.setAttribute(COUNT, count + 1); exchange.getResponseSender().send(serverName + ":" + count); } } protected static final class StringSendHandler implements HttpHandler { private final String serverName; protected StringSendHandler(String serverName) { this.serverName = serverName; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send(serverName); } } static Undertow createNode(final NodeTestConfig config) { final Undertow.Builder builder = Undertow.builder(); final String type = config.getType(); switch (type) { case "ajp": builder.addAjpListener(config.getPort(), config.getHostname()); break; case "http": builder.addHttpListener(config.getPort(), config.getHostname()); break; case "https": builder.addHttpsListener(config.getPort(), config.getHostname(), DefaultServer.getServerSslContext()); break; default: throw new IllegalArgumentException(type); } final SessionCookieConfig sessionConfig = new SessionCookieConfig(); if (config.getStickySessionCookie() != null) { sessionConfig.setCookieName(config.getStickySessionCookie()); } final PathHandler pathHandler = path(ResponseCodeHandler.HANDLE_200) .addPrefixPath("/name", new StringSendHandler(config.getJvmRoute())) .addPrefixPath("/session", new SessionAttachmentHandler(new SessionTestHandler(config.getJvmRoute(), sessionConfig), new InMemorySessionManager(""), sessionConfig)); config.setupHandlers(pathHandler); // Setup test handlers builder.setSocketOption(Options.REUSE_ADDRESSES, true) .setHandler(jvmRoute("JSESSIONID", config.getJvmRoute(), pathHandler)); return builder.build(); } static String getJVMRoute(final String sessionId) { int index = sessionId.indexOf('.'); if (index == -1) { return null; } String route = sessionId.substring(index + 1); index = route.indexOf('.'); if (index != -1) { route = route.substring(0, index); } return route; } static String getSessionRoute() { for (Cookie cookie : httpClient.getCookieStore().getCookies()) { if ("JSESSIONID".equals(cookie.getName())) { return getJVMRoute(cookie.getValue()); } } return null; } static NodeTestConfig[] createConfigs(int number) { final NodeTestConfig[] configs = new NodeTestConfig[number]; for (int i = 0; i < number; i++) { configs[i] = NodeTestConfig.builder() .setJvmRoute("server" + i) .setType("http") .setHostname("localhost") .setPort(port + i + 1); } return configs; } } BasicMCMPUnitTestCase.java000066400000000000000000000133411420065311100357560ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import java.io.IOException; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; /** * @author Emanuel Muckenhuber */ public class BasicMCMPUnitTestCase extends AbstractModClusterTestBase { static NodeTestConfig server1; static NodeTestConfig server2; static { server1 = NodeTestConfig.builder() .setJvmRoute("s1") .setType(getType()) .setHostname("localhost") .setPort(port + 1); server2 = NodeTestConfig.builder() .setJvmRoute("s2") .setType(getType()) .setHostname("localhost") .setPort(port + 2); } @BeforeClass public static void setup() { startServers(server1, server2); } @AfterClass public static void tearDown() { stopServers(); } @Test public void testBasic() throws IOException { registerNodes(false, server1, server2); modClusterClient.updateLoad("s1", 100); modClusterClient.updateLoad("s2", 1); modClusterClient.enableApp("s1", "/name", "localhost", "localhost:7777"); modClusterClient.enableApp("s1", "/session", "localhost", "localhost:7777"); modClusterClient.enableApp("s2", "/name", "localhost", "localhost:7777"); modClusterClient.enableApp("s2", "/session", "localhost", "localhost:7777"); // Ping modClusterClient.updateLoad("s1", -2); modClusterClient.updateLoad("s2", -2); for (int i = 0; i < 10; i++) { HttpGet get = get("/name"); HttpResponse result = httpClient.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } for (int i = 0; i < 10; i++) { HttpGet get = get("/session"); HttpResponse result = httpClient.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } } @Test public void testAppCommand() throws IOException { checkGet("/name", StatusCodes.NOT_FOUND); checkGet("/session", StatusCodes.NOT_FOUND); registerNodes(false, server1, server2); checkGet("/name", StatusCodes.NOT_FOUND); checkGet("/session", StatusCodes.NOT_FOUND); modClusterClient.enableApp("s1", "/name", "localhost", "localhost:7777"); modClusterClient.enableApp("s1", "/session", "localhost", "localhost:7777"); modClusterClient.enableApp("s2", "/name", "localhost", "localhost:7777"); modClusterClient.enableApp("s2", "/session", "localhost", "localhost:7777"); checkGet("/name", StatusCodes.SERVICE_UNAVAILABLE); checkGet("/session", StatusCodes.SERVICE_UNAVAILABLE); modClusterClient.updateLoad("s1", 100); modClusterClient.updateLoad("s2", 1); checkGet("/name", StatusCodes.OK); checkGet("/session", StatusCodes.OK); } @Test public void testErrorState() throws IOException { registerNodes(false, server1); modClusterClient.enableApp("s1", "/name", "localhost", "localhost:7777"); checkGet("/name", StatusCodes.SERVICE_UNAVAILABLE); modClusterClient.updateLoad("s1", 1); checkGet("/name", StatusCodes.OK); modClusterClient.updateLoad("s1", -1); checkGet("/name", StatusCodes.SERVICE_UNAVAILABLE); modClusterClient.updateLoad("s1", -2); checkGet("/name", StatusCodes.SERVICE_UNAVAILABLE); } @Test public void testPing() throws IOException { String response = modClusterClient.ping(null, "localhost", port + 1); Assert.assertFalse(response.contains("NOTOK")); response = modClusterClient.ping(server1.getType(), "localhost", port + 1); Assert.assertFalse(response.contains("NOTOK")); response = modClusterClient.ping(server2.getType(), "localhost", port + 2); Assert.assertFalse(response.contains("NOTOK")); response = modClusterClient.ping(null, "localhost", 0); Assert.assertTrue(response.contains("NOTOK")); response = modClusterClient.ping("ajp", "localhost", 0); Assert.assertTrue(response.contains("NOTOK")); response = modClusterClient.ping("http", "localhost", 0); Assert.assertTrue(response.contains("NOTOK")); } @Test public void testAddStoppedApp() throws IOException { registerNodes(false, server1); modClusterClient.stopApp("s1", "/name", "localhost", "localhost:7777"); final String info = modClusterClient.info(); Assert.assertTrue(info.contains("Context: /name, Status: STOPPED")); modClusterClient.removeApp("s1", "/name", "localhost", "localhost:7777"); } } MCMPTestClient.java000066400000000000000000000225441420065311100345240ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import java.io.Closeable; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.StatusCodes; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.message.BasicNameValuePair; import org.junit.Assert; /** * Basic mod_cluster management client. This can be used to simulate management requests to the mod_cluster manager. * * @author Emanuel Muckenhuber */ public class MCMPTestClient implements Closeable { public static final String CONFIG = new String("CONFIG"); public static final String ENABLE_APP = new String("ENABLE-APP"); public static final String DISABLE_APP = new String("DISABLE-APP"); public static final String STOP_APP = new String("STOP-APP"); public static final String REMOVE_APP = new String("REMOVE-APP"); public static final String STATUS = new String("STATUS"); public static final String DUMP = new String("DUMP"); public static final String INFO = new String("INFO"); public static final String PING = new String("PING"); public static final String GET = new String("GET"); private static final String[] YES_NO = new String[] { "Yes", "No" }; private final HttpClient client; private final String manager; private final String command; public MCMPTestClient(HttpClient client, String manager) { this.client = client; this.manager = manager; this.command = manager + "/*"; } public String info() throws IOException { final Request request = new Request(manager, INFO); final HttpResponse result = client.execute(request); return assertResponse(result); } public String registerNode(final NodeTestConfig config) throws IOException { final Request request = new Request(manager, CONFIG); final List pairs = new ArrayList(); addIfNotNull(pairs, MCMPConstants.BALANCER_STRING, config.getBalancerName()); addIfNotNull(pairs, MCMPConstants.STICKYSESSIONFORCE_STRING, config.getStickySessionForce(), YES_NO); addIfNotNull(pairs, MCMPConstants.STICKYSESSIONCOOKIE_STRING, config.getStickySessionCookie()); addIfNotNull(pairs, MCMPConstants.JVMROUTE_STRING, config.getJvmRoute()); addIfNotNull(pairs, MCMPConstants.DOMAIN_STRING, config.getDomain()); addIfNotNull(pairs, MCMPConstants.TYPE_STRING, config.getType()); addIfNotNull(pairs, MCMPConstants.HOST_STRING, config.getHostname()); addIfNotNull(pairs, MCMPConstants.PORT_STRING, config.getPort()); request.setEntity(createEntity(pairs)); final HttpResponse result = client.execute(request); return assertResponse(result); } static void addIfNotNull(final List pairs, final String key, final Boolean value, String[] inconsistentNames) { if (value != null) { pairs.add(new BasicNameValuePair(key, value ? inconsistentNames[0] : inconsistentNames[1])); } } static void addIfNotNull(final List pairs, final String key, final Integer value) { if (value != null) { pairs.add(new BasicNameValuePair(key, value.toString())); } } static void addIfNotNull(final List pairs, final String key, final String value) { if (value != null) { pairs.add(new BasicNameValuePair(key, value)); } } public String updateLoad(final String jvmRoute, int load) throws IOException { final Request request = new Request(manager, STATUS); request.setEntity(createEntity(new BasicNameValuePair("JVMRoute", jvmRoute), new BasicNameValuePair("Load", "" + load))); final HttpResponse result = client.execute(request); return assertResponse(result); } public String removeNode(String jvmRoute) throws IOException { final Request request = new Request(command, REMOVE_APP); request.setEntity(createEntity(new BasicNameValuePair("JVMRoute", jvmRoute))); final HttpResponse response = client.execute(request); return assertResponse(response); } public String enableApp(String jvmRoute, App app) throws IOException { return enableApp(jvmRoute, app.getContext(), app.getHosts()); } public String enableApp(String jvmRoute, String webApp, String... hosts) throws IOException { return executeAppCmd(ENABLE_APP, jvmRoute, webApp, hosts); } public String disableApp(String jvmRoute, App app) throws IOException { return disableApp(jvmRoute, app.getContext(), app.getHosts()); } public String disableApp(String jvmRoute, String webApp, String... hosts) throws IOException { return executeAppCmd(DISABLE_APP, jvmRoute, webApp, hosts); } public String stopApp(String jvmRoute, App app) throws IOException { return stopApp(jvmRoute, app.getContext(), app.getHosts()); } public String stopApp(String jvmRoute, String webApp, String... hosts) throws IOException { return executeAppCmd(STOP_APP, jvmRoute, webApp, hosts); } public String removeApp(String jvmRoute, App app) throws IOException { return removeApp(jvmRoute, app.getContext(), app.getHosts()); } public String removeApp(String jvmRoute, String webApp, String... hosts) throws IOException { return executeAppCmd(REMOVE_APP, jvmRoute, webApp, hosts); } public String ping(final String scheme, final String hostname, final int port) throws IOException { final Request request = new Request(manager, PING); final List pairs = new ArrayList<>(); addIfNotNull(pairs, MCMPConstants.SCHEME_STRING, scheme); addIfNotNull(pairs, MCMPConstants.HOST_STRING, hostname); addIfNotNull(pairs, MCMPConstants.PORT_STRING, port); request.setEntity(createEntity(pairs)); final HttpResponse response = client.execute(request); return HttpClientUtils.readResponse(response); } String executeAppCmd(final String command, final String jvmRoute, String webApp, String... hosts) throws IOException { final Request request = new Request(manager, command); request.setEntity(createEntity(new BasicNameValuePair("JVMRoute", jvmRoute), new BasicNameValuePair("context", webApp), new BasicNameValuePair("Alias", asString(Arrays.asList(hosts))))); final HttpResponse result = client.execute(request); return assertResponse(result); } @Override public void close() throws IOException { client.getConnectionManager().shutdown(); } static String assertResponse(final HttpResponse result) throws IOException { final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(response, StatusCodes.OK, result.getStatusLine().getStatusCode()); return response; } static HttpEntity createEntity(final NameValuePair... pairs) throws UnsupportedEncodingException { return createEntity(Arrays.asList(pairs)); } static HttpEntity createEntity(final List pairs) throws UnsupportedEncodingException { return new UrlEncodedFormEntity(pairs, StandardCharsets.US_ASCII); } static class Request extends HttpPost { private final String name; Request(String uri, String name) { this(URI.create(uri), name); } Request(URI uri, String name) { super(uri); this.name = name; } @Override public String getMethod() { return name; } } String asString(List names) { final StringBuilder builder = new StringBuilder(); final Iterator i = names.iterator(); while (i.hasNext()) { builder.append(i.next()); if (i.hasNext()) { builder.append(","); } } return builder.toString(); } static class App { private final String context; private final String[] hosts; App(String context, String... hosts) { this.context = context; this.hosts = hosts; } public String getContext() { return context; } public String[] getHosts() { return hosts; } } } ModClusterTestSetup.java000066400000000000000000000115411420065311100357260ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import java.io.IOException; import java.util.concurrent.TimeUnit; import io.undertow.Handlers; import io.undertow.Undertow; import io.undertow.server.HttpHandler; import io.undertow.server.handlers.ResponseCodeHandler; import io.undertow.server.handlers.builder.PredicatedHandlersParser; import org.xnio.OptionMap; import org.xnio.Xnio; import org.xnio.XnioWorker; /** * Server setup to the run the mod_cluster tests * * -ea -Djava.util.logging.manager=org.jboss.logmanager.LogManager -Dtest.level=DEBUG -Djava.net.preferIPv4Stack=true * * @author Emanuel Muckenhuber */ public class ModClusterTestSetup { /* the address and port to receive the MCMP elements */ static String chost = System.getProperty("io.undertow.examples.proxy.CADDRESS", "localhost"); static final int cport = Integer.parseInt(System.getProperty("io.undertow.examples.proxy.CPORT", "6666")); /* the address and port to receive normal requests */ static String phost = System.getProperty("io.undertow.examples.proxy.ADDRESS", "localhost"); static final int pport = Integer.parseInt(System.getProperty("io.undertow.examples.proxy.PORT", "8000")); public static void main(final String[] args) throws IOException { final Undertow server; final XnioWorker worker = Xnio.getInstance().createWorker(OptionMap.EMPTY); final ModCluster modCluster = ModCluster.builder(worker) .setHealthCheckInterval(TimeUnit.SECONDS.toMillis(3)) .setRemoveBrokenNodes(TimeUnit.SECONDS.toMillis(30)) .build(); try { if (chost == null) { // We are going to guess it. chost = java.net.InetAddress.getLocalHost().getHostName(); System.out.println("Using: " + chost + ":" + cport); } modCluster.start(); // Create the proxy and mgmt handler final HttpHandler proxy = modCluster.createProxyHandler(); final MCMPConfig config = MCMPConfig.builder() .setManagementHost(chost) .setManagementPort(cport) .enableAdvertise() .setSecurityKey("secret") .getParent() .build(); final MCMPConfig webConfig = MCMPConfig.webBuilder() .setManagementHost(chost) .setManagementPort(cport) .build(); // Setup specific rewrite rules for the mod_cluster tests. final HttpHandler root = Handlers.predicates( PredicatedHandlersParser.parse( "regex[pattern='cluster.domain.com', value='%{i,Host}'] and equals[%R, '/'] -> rewrite['/myapp/MyCount']\n" + "regex[pattern='cluster.domain.org', value='%{i,Host}'] and regex['/(.*)'] -> rewrite['/myapp/${1}']\n" + "regex[pattern='cluster.domain.net', value='%{i,Host}'] and regex['/test/(.*)'] -> rewrite['/myapp/${1}']\n" + "regex[pattern='cluster.domain.info', value='%{i,Host}'] and path-template['/{one}/{two}'] -> rewrite['/test/${two}?partnerpath=/${one}&%q']\n", ModClusterTestSetup.class.getClassLoader() ), proxy); final HttpHandler mcmp = config.create(modCluster, root); final HttpHandler web = webConfig.create(modCluster, ResponseCodeHandler.HANDLE_404); server = Undertow.builder() .addHttpListener(cport, chost) .addHttpListener(pport, phost) .setHandler(Handlers.path(mcmp).addPrefixPath("/mod_cluster_manager", web)) .build(); server.start(); // Start advertising the mcmp handler modCluster.advertise(config); final Runnable r = new Runnable() { @Override public void run() { modCluster.stop(); server.stop(); } }; Runtime.getRuntime().addShutdownHook(new Thread(r)); } catch (Exception e) { e.printStackTrace(); } } } NodeTestConfig.java000066400000000000000000000136311420065311100346410ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import io.undertow.server.handlers.PathHandler; /** * Unit test configuration for a node. * * @author Emanuel Muckenhuber */ class NodeTestConfig implements Cloneable { private String jvmRoute; private String domain; private String type; private String hostname; private Integer port; private Boolean flushPackets; private Integer flushwait; private Integer ping; private Integer smax; private Integer ttl; private Integer timeout; private String BalancerName; private Boolean stickySession; private String stickySessionCookie; private String stickySessionPath; private Boolean stickySessionRemove; private Boolean stickySessionForce; private Integer waitWorker; private Integer maxattempts; private NodeTestHandlers testHandlers; static NodeTestConfig builder() { return new NodeTestConfig(); } public String getBalancerName() { return BalancerName; } public NodeTestConfig setBalancerName(String balancerName) { this.BalancerName = balancerName; return this; } public Boolean getStickySession() { return stickySession; } public NodeTestConfig setStickySession(Boolean stickySession) { this.stickySession = stickySession; return this; } public String getStickySessionCookie() { return stickySessionCookie; } public NodeTestConfig setStickySessionCookie(String stickySessionCookie) { this.stickySessionCookie = stickySessionCookie; return this; } public String getStickySessionPath() { return stickySessionPath; } public NodeTestConfig setStickySessionPath(String stickySessionPath) { this.stickySessionPath = stickySessionPath; return this; } public Boolean getStickySessionRemove() { return stickySessionRemove; } public NodeTestConfig setStickySessionRemove(Boolean stickySessionRemove) { this.stickySessionRemove = stickySessionRemove; return this; } public Boolean getStickySessionForce() { return stickySessionForce; } public NodeTestConfig setStickySessionForce(Boolean stickySessionForce) { this.stickySessionForce = stickySessionForce; return this; } public Integer getWaitWorker() { return waitWorker; } public NodeTestConfig setWaitWorker(Integer waitWorker) { this.waitWorker = waitWorker; return this; } public Integer getMaxattempts() { return maxattempts; } public NodeTestConfig setMaxattempts(Integer maxattempts) { this.maxattempts = maxattempts; return this; } public String getJvmRoute() { return jvmRoute; } public NodeTestConfig setJvmRoute(String jvmRoute) { this.jvmRoute = jvmRoute; return this; } public String getDomain() { return domain; } public NodeTestConfig setDomain(String domain) { this.domain = domain; return this; } public String getType() { return type; } public NodeTestConfig setType(String type) { this.type = type; return this; } public String getHostname() { return hostname; } public NodeTestConfig setHostname(String hostname) { this.hostname = hostname; return this; } public Integer getPort() { return port; } public NodeTestConfig setPort(Integer port) { this.port = port; return this; } public Boolean getFlushPackets() { return flushPackets; } public NodeTestConfig setFlushPackets(Boolean flushPackets) { this.flushPackets = flushPackets; return this; } public Integer getFlushwait() { return flushwait; } public NodeTestConfig setFlushwait(Integer flushwait) { this.flushwait = flushwait; return this; } public Integer getPing() { return ping; } public NodeTestConfig setPing(Integer ping) { this.ping = ping; return this; } public Integer getSmax() { return smax; } public NodeTestConfig setSmax(Integer smax) { this.smax = smax; return this; } public Integer getTtl() { return ttl; } public NodeTestConfig setTtl(Integer ttl) { this.ttl = ttl; return this; } public Integer getTimeout() { return timeout; } public NodeTestConfig setTimeout(Integer timeout) { this.timeout = timeout; return this; } public NodeTestHandlers getTestHandlers() { return testHandlers; } public NodeTestConfig setTestHandlers(NodeTestHandlers testHandlers) { this.testHandlers = testHandlers; return this; } void setupHandlers(final PathHandler pathHandler) { if (testHandlers != null) { testHandlers.setup(pathHandler, this); } } @Override protected NodeTestConfig clone() { try { return (NodeTestConfig) super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } } NodeTestHandlers.java000066400000000000000000000016751420065311100352010ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import io.undertow.server.handlers.PathHandler; /** * @author Emanuel Muckenhuber */ interface NodeTestHandlers { void setup(final PathHandler handler, final NodeTestConfig config); } RouteIteratorFactoryTestCase.java000066400000000000000000000152411420065311100375610ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import static io.undertow.server.handlers.proxy.RouteIteratorFactory.ParsingCompatibility.MOD_CLUSTER; import static io.undertow.server.handlers.proxy.RouteIteratorFactory.ParsingCompatibility.MOD_JK; import static io.undertow.server.handlers.proxy.RouteParsingStrategy.NONE; import static io.undertow.server.handlers.proxy.RouteParsingStrategy.SINGLE; import static io.undertow.server.handlers.proxy.RouteParsingStrategy.RANKED; import java.util.Iterator; import io.undertow.server.handlers.proxy.RouteIteratorFactory; import org.junit.Assert; import org.junit.Test; /** * Tests route/affinity parsing mechanism including ranked routing. * * @author Radoslav Husar */ public class RouteIteratorFactoryTestCase { @Test public void testModJkLikeRouteParsing() { // Disabled sticky sessions on the load balancer Iterator ri = new RouteIteratorFactory(NONE, MOD_JK).iterator("mKaJwtWjqgxFbSSlaKZeGly_RMPKCg13JXe-6R_h.node1.domain"); Assert.assertFalse(ri.hasNext()); // Default behavior ri = new RouteIteratorFactory(SINGLE, MOD_JK).iterator("mKaJwtWjqgxFbSSlaKZeGly_RMPKCg13JXe-6R_h"); Assert.assertFalse(ri.hasNext()); // No ranked routing support taking as route only between first "." and second "." ri = new RouteIteratorFactory(SINGLE, MOD_JK).iterator("mKaJwtWjqgxFbSSlaKZeGly_RMPKCg13JXe-6R_h.node1.domain1.something"); Assert.assertTrue(ri.hasNext()); Assert.assertEquals("node1", ri.next().toString()); Assert.assertFalse(ri.hasNext()); // Ranked routing support but no route given ri = new RouteIteratorFactory(RANKED, MOD_JK, ".").iterator("mKaJwtWjqgxFbSSlaKZeGly_RMPKCg13JXe-6R_h"); Assert.assertFalse(ri.hasNext()); // Multi-route support with the same character delimiter as sessionID delimiter '.' -- overriding domain support parsing ri = new RouteIteratorFactory(RANKED, MOD_JK, ".").iterator("mKaJwtWjqgxFbSSlaKZeGly_RMPKCg13JXe-6R_h.node1.node2.node3"); Assert.assertTrue(ri.hasNext()); Assert.assertEquals("node1", ri.next().toString()); Assert.assertTrue(ri.hasNext()); Assert.assertEquals("node2", ri.next().toString()); Assert.assertTrue(ri.hasNext()); Assert.assertEquals("node3", ri.next().toString()); Assert.assertFalse(ri.hasNext()); } @Test public void testModClusterRouteParsing() { // Disabled sticky sessions Iterator ri = new RouteIteratorFactory(NONE, MOD_CLUSTER).iterator("mKaJwtWjqgxFbSSlaKZeGly_RMPKCg13JXe-6R_h"); Assert.assertFalse(ri.hasNext()); ri = new RouteIteratorFactory(NONE, MOD_CLUSTER).iterator("mKaJwtWjqgxFbSSlaKZeGly_RMPKCg13JXe-6R_h.node1.node2"); Assert.assertFalse(ri.hasNext()); // No ranked routing support and no route given or null sessionId ri = new RouteIteratorFactory(SINGLE, MOD_CLUSTER).iterator("mKaJwtWjqgxFbSSlaKZeGly_RMPKCg13JXe-6R_h"); Assert.assertFalse(ri.hasNext()); ri = new RouteIteratorFactory(SINGLE, MOD_CLUSTER).iterator(null); Assert.assertFalse(ri.hasNext()); // No ranked routing support treating everything after '.' as route ri = new RouteIteratorFactory(SINGLE, MOD_CLUSTER).iterator("mKaJwtWjqgxFbSSlaKZeGly_RMPKCg13JXe-6R_h.node1.node2.node3"); Assert.assertTrue(ri.hasNext()); Assert.assertEquals("node1.node2.node3", ri.next().toString()); Assert.assertFalse(ri.hasNext()); // Ranked routing support but no route given ri = new RouteIteratorFactory(RANKED, MOD_CLUSTER, ".").iterator("mKaJwtWjqgxFbSSlaKZeGly_RMPKCg13JXe-6R_h"); Assert.assertFalse(ri.hasNext()); // Multi-route support with the same character delimiter as sessionID delimiter '.' ri = new RouteIteratorFactory(RANKED, MOD_CLUSTER, ".").iterator("mKaJwtWjqgxFbSSlaKZeGly_RMPKCg13JXe-6R_h.node1.node2.node3"); Assert.assertTrue(ri.hasNext()); Assert.assertEquals("node1", ri.next().toString()); Assert.assertTrue(ri.hasNext()); Assert.assertEquals("node2", ri.next().toString()); Assert.assertTrue(ri.hasNext()); Assert.assertEquals("node3", ri.next().toString()); Assert.assertFalse(ri.hasNext()); // Multi-route support with a different character delimiter ':' ri = new RouteIteratorFactory(RANKED, MOD_CLUSTER, ":").iterator("mKaJwtWjqgxFbSSlaKZeGly_RMPKCg13JXe-6R_h.node1:node2.1:node3.1"); Assert.assertTrue(ri.hasNext()); Assert.assertEquals("node1", ri.next().toString()); Assert.assertTrue(ri.hasNext()); Assert.assertEquals("node2.1", ri.next().toString()); Assert.assertTrue(ri.hasNext()); Assert.assertEquals("node3.1", ri.next().toString()); Assert.assertFalse(ri.hasNext()); // Multi-route support with messy inputs ri = new RouteIteratorFactory(RANKED, MOD_CLUSTER, ":").iterator("mKaJwtWjqgxFbSSlaKZeGly_RMPKCg13JXe-6R_h.node1::node2::"); Assert.assertTrue(ri.hasNext()); Assert.assertEquals("node1", ri.next().toString()); Assert.assertTrue(ri.hasNext()); Assert.assertEquals("", ri.next().toString()); Assert.assertTrue(ri.hasNext()); Assert.assertEquals("node2", ri.next().toString()); Assert.assertTrue(ri.hasNext()); Assert.assertEquals("", ri.next().toString()); Assert.assertFalse(ri.hasNext()); // Multi-route multi-character delimiter support ri = new RouteIteratorFactory(RANKED, MOD_CLUSTER, "|||").iterator("mKaJwtWjqgxFbSSlaKZeGly_RMPKCg13JXe-6R_h.node1|||node2|||node3"); Assert.assertTrue(ri.hasNext()); Assert.assertEquals("node1", ri.next().toString()); Assert.assertTrue(ri.hasNext()); Assert.assertEquals("node2", ri.next().toString()); Assert.assertTrue(ri.hasNext()); Assert.assertEquals("node3", ri.next().toString()); Assert.assertFalse(ri.hasNext()); } } StickySessionForceUnitTestCase.java000066400000000000000000000210631420065311100400510ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import io.undertow.util.StatusCodes; import java.io.IOException; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; /** * Test sticky session force == false; behavior * * @author Emanuel Muckenhuber */ public class StickySessionForceUnitTestCase extends AbstractModClusterTestBase { static NodeTestConfig server1; static NodeTestConfig server2; static { server1 = NodeTestConfig.builder() .setStickySessionForce(false) // Force = false .setJvmRoute("server1") .setType(getType()) .setHostname("localhost") .setPort(port + 1); server2 = NodeTestConfig.builder() .setStickySessionForce(false) // Force = false .setJvmRoute("server2") .setType(getType()) .setHostname("localhost") .setPort(port + 2); } @BeforeClass public static void setup() { startServers(server1, server2); } @AfterClass public static void tearDown() { stopServers(); } @Test public void testNoDomainRemovedContext() throws IOException { // If no domain is configured apps cannot failover registerNodes(true, server1, server2); modClusterClient.enableApp(server1.getJvmRoute(), SESSION); modClusterClient.enableApp(server2.getJvmRoute(), SESSION); final String response = checkGet("/session", StatusCodes.OK); if (response.startsWith(server1.getJvmRoute())) { modClusterClient.removeApp(server1.getJvmRoute(), SESSION); } else { modClusterClient.removeApp(server2.getJvmRoute(), SESSION); } checkGet("/session", StatusCodes.OK); } @Test public void testNoDomainStoppedContext() throws IOException { // If no domain is configured apps cannot failover registerNodes(true, server1, server2); modClusterClient.enableApp(server1.getJvmRoute(), SESSION); modClusterClient.enableApp(server2.getJvmRoute(), SESSION); final String response = checkGet("/session", StatusCodes.OK); if (response.startsWith(server1.getJvmRoute())) { modClusterClient.stopApp(server1.getJvmRoute(), SESSION); } else { modClusterClient.stopApp(server2.getJvmRoute(), SESSION); } checkGet("/session", StatusCodes.OK); } @Test public void testNoDomainNodeInError() throws IOException { // If no domain is configured apps cannot failover registerNodes(true, server1, server2); modClusterClient.enableApp(server1.getJvmRoute(), SESSION); modClusterClient.enableApp(server2.getJvmRoute(), SESSION); final String response = checkGet("/session", StatusCodes.OK); if (response.startsWith(server1.getJvmRoute())) { modClusterClient.updateLoad(server1.getJvmRoute(), -1); } else { modClusterClient.updateLoad(server2.getJvmRoute(), -1); } checkGet("/session", StatusCodes.OK); } @Test public void testDifferentDomainRemovedContext() throws IOException { // Test failover in a different domain final NodeTestConfig config1 = server1.clone().setDomain("domain1"); final NodeTestConfig config2 = server2.clone().setDomain("domain2"); registerNodes(true, config1, config2); modClusterClient.enableApp(server1.getJvmRoute(), SESSION); modClusterClient.enableApp(server2.getJvmRoute(), SESSION); final String response = checkGet("/session", StatusCodes.OK); if (response.startsWith(server1.getJvmRoute())) { modClusterClient.removeApp(server1.getJvmRoute(), SESSION); } else { modClusterClient.removeApp(server2.getJvmRoute(), SESSION); } checkGet("/session", StatusCodes.OK); } @Test public void testDifferentDomainStoppedContext() throws IOException { // Test failover in a different domain final NodeTestConfig config1 = server1.clone().setDomain("domain1"); final NodeTestConfig config2 = server2.clone().setDomain("domain2"); registerNodes(true, config1, config2); modClusterClient.enableApp(server1.getJvmRoute(), SESSION); modClusterClient.enableApp(server2.getJvmRoute(), SESSION); final String response = checkGet("/session", StatusCodes.OK); if (response.startsWith(server1.getJvmRoute())) { modClusterClient.stopApp(server1.getJvmRoute(), SESSION); } else { modClusterClient.stopApp(server2.getJvmRoute(), SESSION); } checkGet("/session", StatusCodes.OK); } @Test public void testDifferentDomainNodeInError() throws IOException { // Test failover in a different domain final NodeTestConfig config1 = server1.clone().setDomain("domain1"); final NodeTestConfig config2 = server2.clone().setDomain("domain2"); registerNodes(true, config1, config2); modClusterClient.enableApp(server1.getJvmRoute(), SESSION); modClusterClient.enableApp(server2.getJvmRoute(), SESSION); final String response = checkGet("/session", StatusCodes.OK); if (response.startsWith(server1.getJvmRoute())) { modClusterClient.updateLoad(server1.getJvmRoute(), -1); } else { modClusterClient.updateLoad(server2.getJvmRoute(), -1); } checkGet("/session", StatusCodes.OK); } @Test public void testDomainStoppedContext() throws IOException { // Test failover in the same domain final NodeTestConfig config1 = server1.clone().setDomain("domain1"); final NodeTestConfig config2 = server2.clone().setDomain("domain1"); registerNodes(true, config1, config2); modClusterClient.enableApp(server1.getJvmRoute(), SESSION); modClusterClient.enableApp(server2.getJvmRoute(), SESSION); final String response = checkGet("/session", StatusCodes.OK); if (response.startsWith(server1.getJvmRoute())) { modClusterClient.stopApp(server1.getJvmRoute(), SESSION); } else { modClusterClient.stopApp(server2.getJvmRoute(), SESSION); } checkGet("/session", StatusCodes.OK); } @Test public void testDomainRemovedContext() throws IOException { // Test failover in the same domain final NodeTestConfig config1 = server1.clone().setDomain("domain1"); final NodeTestConfig config2 = server2.clone().setDomain("domain1"); registerNodes(true, config1, config2); modClusterClient.enableApp(server1.getJvmRoute(), SESSION); modClusterClient.enableApp(server2.getJvmRoute(), SESSION); final String response = checkGet("/session", StatusCodes.OK); if (response.startsWith(server1.getJvmRoute())) { modClusterClient.removeApp(server1.getJvmRoute(), SESSION); } else { modClusterClient.removeApp(server2.getJvmRoute(), SESSION); } checkGet("/session", StatusCodes.OK); } @Test public void testDomainNodeInError() throws IOException { // Test failover in the same domain final NodeTestConfig config1 = server1.clone().setDomain("domain1"); final NodeTestConfig config2 = server2.clone().setDomain("domain1"); registerNodes(true, config1, config2); modClusterClient.enableApp(server1.getJvmRoute(), SESSION); modClusterClient.enableApp(server2.getJvmRoute(), SESSION); final String response = checkGet("/session", StatusCodes.OK); if (response.startsWith(server1.getJvmRoute())) { modClusterClient.updateLoad(server1.getJvmRoute(), -1); } else { modClusterClient.updateLoad(server2.getJvmRoute(), -1); } checkGet("/session", StatusCodes.OK); } } StickySessionUnitTestCase.java000066400000000000000000000224171420065311100370760ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/proxy/mod_cluster/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.proxy.mod_cluster; import io.undertow.util.StatusCodes; import java.io.IOException; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; /** * Test failover with force sticky session == true; (which is the default) * * @author Emanuel Muckenhuber */ public class StickySessionUnitTestCase extends AbstractModClusterTestBase { static NodeTestConfig server1; static NodeTestConfig server2; static { server1 = NodeTestConfig.builder() .setJvmRoute("server1") .setType(getType()) .setHostname("localhost") .setPort(port + 1); server2 = NodeTestConfig.builder() .setJvmRoute("server2") .setType(getType()) .setHostname("localhost") .setPort(port + 2); } @BeforeClass public static void setup() { startServers(server1, server2); } @AfterClass public static void tearDown() { stopServers(); } @Test public void testDisabledApp() throws IOException { // registerNodes(true, server1, server2); modClusterClient.enableApp(server1.getJvmRoute(), SESSION); modClusterClient.enableApp(server2.getJvmRoute(), SESSION); final String response = checkGet("/session", StatusCodes.OK); final String jvmRoute; if (response.startsWith(server1.getJvmRoute())) { jvmRoute = server1.getJvmRoute(); } else { jvmRoute = server2.getJvmRoute(); } modClusterClient.disableApp(jvmRoute, SESSION); for (int i = 0; i < 20 ; i++) { checkGet("/session", StatusCodes.OK, jvmRoute).startsWith(jvmRoute); } } @Test public void testNoDomainRemovedContext() throws IOException { // If no domain is configured apps cannot failover registerNodes(true, server1, server2); modClusterClient.enableApp(server1.getJvmRoute(), SESSION); modClusterClient.enableApp(server2.getJvmRoute(), SESSION); final String response = checkGet("/session", StatusCodes.OK); if (response.startsWith(server1.getJvmRoute())) { modClusterClient.removeApp(server1.getJvmRoute(), SESSION); } else { modClusterClient.removeApp(server2.getJvmRoute(), SESSION); } checkGet("/session", StatusCodes.SERVICE_UNAVAILABLE); } @Test public void testNoDomainStoppedContext() throws IOException { // If no domain is configured apps cannot failover registerNodes(true, server1, server2); modClusterClient.enableApp(server1.getJvmRoute(), SESSION); modClusterClient.enableApp(server2.getJvmRoute(), SESSION); final String response = checkGet("/session", StatusCodes.OK); if (response.startsWith(server1.getJvmRoute())) { modClusterClient.stopApp(server1.getJvmRoute(), SESSION); } else { modClusterClient.stopApp(server2.getJvmRoute(), SESSION); } checkGet("/session", StatusCodes.SERVICE_UNAVAILABLE); } @Test public void testNoDomainNodeInError() throws IOException { // If no domain is configured apps cannot failover registerNodes(true, server1, server2); modClusterClient.enableApp(server1.getJvmRoute(), SESSION); modClusterClient.enableApp(server2.getJvmRoute(), SESSION); final String response = checkGet("/session", StatusCodes.OK); if (response.startsWith(server1.getJvmRoute())) { modClusterClient.updateLoad(server1.getJvmRoute(), -1); } else { modClusterClient.updateLoad(server2.getJvmRoute(), -1); } checkGet("/session", StatusCodes.SERVICE_UNAVAILABLE); } @Test public void testDifferentDomainRemovedContext() throws IOException { // Test failover in a different domain final NodeTestConfig config1 = server1.clone().setDomain("domain1"); final NodeTestConfig config2 = server2.clone().setDomain("domain2"); registerNodes(true, config1, config2); modClusterClient.enableApp(server1.getJvmRoute(), SESSION); modClusterClient.enableApp(server2.getJvmRoute(), SESSION); final String response = checkGet("/session", StatusCodes.OK); if (response.startsWith(server1.getJvmRoute())) { modClusterClient.removeApp(server1.getJvmRoute(), SESSION); } else { modClusterClient.removeApp(server2.getJvmRoute(), SESSION); } checkGet("/session", StatusCodes.SERVICE_UNAVAILABLE); } @Test public void testDifferentDomainStoppedContext() throws IOException { // Test failover in a different domain final NodeTestConfig config1 = server1.clone().setDomain("domain1"); final NodeTestConfig config2 = server2.clone().setDomain("domain2"); registerNodes(true, config1, config2); modClusterClient.enableApp(server1.getJvmRoute(), SESSION); modClusterClient.enableApp(server2.getJvmRoute(), SESSION); final String response = checkGet("/session", StatusCodes.OK); if (response.startsWith(server1.getJvmRoute())) { modClusterClient.stopApp(server1.getJvmRoute(), SESSION); } else { modClusterClient.stopApp(server2.getJvmRoute(), SESSION); } checkGet("/session", StatusCodes.SERVICE_UNAVAILABLE); } @Test public void testDifferentDomainNodeInError() throws IOException { // Test failover in a different domain final NodeTestConfig config1 = server1.clone().setDomain("domain1"); final NodeTestConfig config2 = server2.clone().setDomain("domain2"); registerNodes(true, config1, config2); modClusterClient.enableApp(server1.getJvmRoute(), SESSION); modClusterClient.enableApp(server2.getJvmRoute(), SESSION); final String response = checkGet("/session", StatusCodes.OK); if (response.startsWith(server1.getJvmRoute())) { modClusterClient.updateLoad(server1.getJvmRoute(), -1); } else { modClusterClient.updateLoad(server2.getJvmRoute(), -1); } checkGet("/session", StatusCodes.SERVICE_UNAVAILABLE); } @Test public void testDomainStoppedContext() throws IOException { // Test failover in the same domain final NodeTestConfig config1 = server1.clone().setDomain("domain1"); final NodeTestConfig config2 = server2.clone().setDomain("domain1"); registerNodes(true, config1, config2); modClusterClient.enableApp(server1.getJvmRoute(), SESSION); modClusterClient.enableApp(server2.getJvmRoute(), SESSION); final String response = checkGet("/session", StatusCodes.OK); if (response.startsWith(server1.getJvmRoute())) { modClusterClient.stopApp(server1.getJvmRoute(), SESSION); } else { modClusterClient.stopApp(server2.getJvmRoute(), SESSION); } checkGet("/session", StatusCodes.OK); } @Test public void testDomainRemovedContext() throws IOException { // Test failover in the same domain final NodeTestConfig config1 = server1.clone().setDomain("domain1"); final NodeTestConfig config2 = server2.clone().setDomain("domain1"); registerNodes(true, config1, config2); modClusterClient.enableApp(server1.getJvmRoute(), SESSION); modClusterClient.enableApp(server2.getJvmRoute(), SESSION); final String response = checkGet("/session", StatusCodes.OK); if (response.startsWith(server1.getJvmRoute())) { modClusterClient.removeApp(server1.getJvmRoute(), SESSION); } else { modClusterClient.removeApp(server2.getJvmRoute(), SESSION); } checkGet("/session", StatusCodes.OK); } @Test public void testDomainNodeInError() throws IOException { // Test failover in the same domain final NodeTestConfig config1 = server1.clone().setDomain("domain1"); final NodeTestConfig config2 = server2.clone().setDomain("domain1"); registerNodes(true, config1, config2); modClusterClient.enableApp(server1.getJvmRoute(), SESSION); modClusterClient.enableApp(server2.getJvmRoute(), SESSION); final String response = checkGet("/session", StatusCodes.OK); if (response.startsWith(server1.getJvmRoute())) { modClusterClient.updateLoad(server1.getJvmRoute(), -1); } else { modClusterClient.updateLoad(server2.getJvmRoute(), -1); } checkGet("/session", StatusCodes.OK); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/range.txt000066400000000000000000000000121420065311100273230ustar00rootroot000000000000000123456789undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/session/000077500000000000000000000000001420065311100271605ustar00rootroot00000000000000InMemorySessionTestCase.java000066400000000000000000000311411420065311100345030ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/session/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.session; import java.io.IOException; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.session.InMemorySessionManager; import io.undertow.server.session.Session; import io.undertow.server.session.SessionAttachmentHandler; import io.undertow.server.session.SessionCookieConfig; import io.undertow.server.session.SessionManager; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.HttpString; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.BasicCookieStore; import io.undertow.testutils.TestHttpClient; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; /** * basic test of in memory session functionality * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class InMemorySessionTestCase { public static final String COUNT = "count"; @Test public void inMemorySessionTest() throws IOException { TestHttpClient client = new TestHttpClient(); client.setCookieStore(new BasicCookieStore()); try { final SessionCookieConfig sessionConfig = new SessionCookieConfig(); final SessionAttachmentHandler handler = new SessionAttachmentHandler(new InMemorySessionManager(""), sessionConfig); handler.setNext(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final SessionManager manager = exchange.getAttachment(SessionManager.ATTACHMENT_KEY); Session session = manager.getSession(exchange, sessionConfig); if (session == null) { session = manager.createSession(exchange, sessionConfig); session.setAttribute(COUNT, 0); } Integer count = (Integer) session.getAttribute(COUNT); exchange.getResponseHeaders().add(new HttpString(COUNT), count.toString()); session.setAttribute(COUNT, ++count); } }); DefaultServer.setRootHandler(handler); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/notamatchingpath"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); Header[] header = result.getHeaders(COUNT); Assert.assertEquals("0", header[0].getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/notamatchingpath"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); header = result.getHeaders(COUNT); Assert.assertEquals("1", header[0].getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/notamatchingpath"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); header = result.getHeaders(COUNT); Assert.assertEquals("2", header[0].getValue()); } finally { client.getConnectionManager().shutdown(); } } @Test public void inMemoryMaxSessionsTest() throws IOException { TestHttpClient client1 = new TestHttpClient(); client1.setCookieStore(new BasicCookieStore()); TestHttpClient client2 = new TestHttpClient(); client2.setCookieStore(new BasicCookieStore()); try { final SessionCookieConfig sessionConfig = new SessionCookieConfig(); final SessionAttachmentHandler handler = new SessionAttachmentHandler(new InMemorySessionManager("", 1, true), sessionConfig); handler.setNext(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final SessionManager manager = exchange.getAttachment(SessionManager.ATTACHMENT_KEY); Session session = manager.getSession(exchange, sessionConfig); if (session == null) { session = manager.createSession(exchange, sessionConfig); session.setAttribute(COUNT, 0); } Integer count = (Integer) session.getAttribute(COUNT); exchange.getResponseHeaders().add(new HttpString(COUNT), count.toString()); session.setAttribute(COUNT, ++count); } }); DefaultServer.setRootHandler(handler); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/notamatchingpath"); HttpResponse result = client1.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); Header[] header = result.getHeaders(COUNT); Assert.assertEquals("0", header[0].getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/notamatchingpath"); result = client1.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); header = result.getHeaders(COUNT); Assert.assertEquals("1", header[0].getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/notamatchingpath"); result = client2.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); header = result.getHeaders(COUNT); Assert.assertEquals("0", header[0].getValue()); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/notamatchingpath"); result = client1.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); header = result.getHeaders(COUNT); Assert.assertEquals("0", header[0].getValue()); } finally { client1.getConnectionManager().shutdown(); client2.getConnectionManager().shutdown(); } } @Test // https://issues.redhat.com/browse/UNDERTOW-1419 public void inMemorySessionTimeoutExpirationTest() throws IOException, InterruptedException { final int maxInactiveIntervalInSeconds = 1; final int accessorThreadSleepInMilliseconds = 200; TestHttpClient client = new TestHttpClient(); client.setCookieStore(new BasicCookieStore()); try { final SessionCookieConfig sessionConfig = new SessionCookieConfig(); final SessionAttachmentHandler handler = new SessionAttachmentHandler(new InMemorySessionManager(""), sessionConfig); handler.setNext(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final SessionManager manager = exchange.getAttachment(SessionManager.ATTACHMENT_KEY); Session session = manager.getSession(exchange, sessionConfig); if (session == null) { // set 1 second timeout for this session expiration manager.setDefaultSessionTimeout(maxInactiveIntervalInSeconds); session = manager.createSession(exchange, sessionConfig); session.setAttribute(COUNT, 0); // let's call getAttribute() some times to be sure that the session timeout is no longer bumped // by the method invocation Runnable r = new Runnable() { public void run() { Session innerThreadSession = manager.getSession(exchange, sessionConfig); int iterations = ((maxInactiveIntervalInSeconds * 1000)/accessorThreadSleepInMilliseconds); for (int i = 0; i <= iterations; i++) { try { Thread.sleep(accessorThreadSleepInMilliseconds); } catch (InterruptedException e) { System.out.println( String.format("Unexpected error during Thread.sleep(): %s", e.getMessage())); } if (innerThreadSession != null) { try { System.out.println(String.format("Session is still valid. Attribute is: %s", innerThreadSession.getAttribute(COUNT).toString())); if (i == iterations) { System.out.println("Session should not still be valid!"); } } catch (IllegalStateException e) { if ((e instanceof IllegalStateException) && e.getMessage().startsWith("UT000010")) { System.out.println( String.format("This is expected as session is not valid anymore: %s", e.getMessage())); } else { System.out.println( String.format("Unexpected exception while calling session.getAttribute(): %s", e.getMessage())); } } } } } }; Thread thread = new Thread(r); thread.start(); } // here the server is accessing one session attribute, so we're sure that the bumped timeout // issue is being replicated and we can test for regression Integer count = (Integer) session.getAttribute(COUNT); exchange.getResponseHeaders().add(new HttpString(COUNT), count.toString()); session.setAttribute(COUNT, ++count); } }); DefaultServer.setRootHandler(handler); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/notamatchingpath"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); Header[] header = result.getHeaders(COUNT); Assert.assertEquals("0", header[0].getValue()); Thread.sleep(2 * 1000L); // after 2 seconds from the last call, the session expiration timeout hasn't been bumped anymore, // so now "COUNT" should be still set to 0 (zero) get = new HttpGet(DefaultServer.getDefaultServerURL() + "/notamatchingpath"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); header = result.getHeaders(COUNT); Assert.assertEquals("0", header[0].getValue()); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/session/SSLSessionTestCase.java000066400000000000000000000112461420065311100334700ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.session; import java.io.IOException; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.session.InMemorySessionManager; import io.undertow.server.session.Session; import io.undertow.server.session.SessionAttachmentHandler; import io.undertow.server.session.SessionManager; import io.undertow.server.session.SslSessionConfig; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.ProxyIgnore; import io.undertow.util.HttpString; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import io.undertow.testutils.TestHttpClient; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; /** * basic test of in memory session functionality * * @author Stuart Douglas */ @RunWith(DefaultServer.class) @ProxyIgnore @HttpOneOnly public class SSLSessionTestCase { public static final String COUNT = "count"; @Test public void testSslSession() throws IOException { TestHttpClient client = new TestHttpClient(); try { InMemorySessionManager sessionManager = new InMemorySessionManager(""); final SslSessionConfig sessionConfig = new SslSessionConfig(sessionManager); final SessionAttachmentHandler handler = new SessionAttachmentHandler(sessionManager, sessionConfig) .setNext(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final SessionManager manager = exchange.getAttachment(SessionManager.ATTACHMENT_KEY); Session session = manager.getSession(exchange, sessionConfig); if (session == null) { session = manager.createSession(exchange, sessionConfig); session.setAttribute(COUNT, 0); } Integer count = (Integer) session.getAttribute(COUNT); exchange.getResponseHeaders().add(new HttpString(COUNT), count.toString()); session.setAttribute(COUNT, ++count); } }); DefaultServer.startSSLServer(); client.setSSLContext(DefaultServer.getClientSSLContext()); DefaultServer.setRootHandler(handler); HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress() + "/notamatchingpath"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); Header[] header = result.getHeaders(COUNT); Assert.assertEquals("0", header[0].getValue()); get = new HttpGet(DefaultServer.getDefaultServerSSLAddress() + "/notamatchingpath"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); header = result.getHeaders(COUNT); Assert.assertEquals("1", header[0].getValue()); get = new HttpGet(DefaultServer.getDefaultServerSSLAddress() + "/notamatchingpath"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); header = result.getHeaders(COUNT); Assert.assertEquals("2", header[0].getValue()); Assert.assertEquals(0, client.getCookieStore().getCookies().size()); } finally { DefaultServer.stopSSLServer(); client.getConnectionManager().shutdown(); } } } SSLSessionWithExpandedBufferTestCase.java000066400000000000000000000041011420065311100370400ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/session/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.session; import java.nio.ByteBuffer; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.testutils.ProxyIgnore; import org.junit.BeforeClass; import org.junit.runner.RunWith; /** * Runs {@link SSLSessionTestCase} with an expanded buffer SSLEngine, * and verifies if {@link javax.net.ssl.SSLEngineResult.Status#BUFFER_OVERFLOW} * is handled appropriately. * * @author Flavia Rainone */ @RunWith(DefaultServer.class) @ProxyIgnore @HttpOneOnly public class SSLSessionWithExpandedBufferTestCase extends SSLSessionTestCase { @BeforeClass public static void setup() throws Exception { final SSLContext context = SSLContext.getDefault(); final SSLEngine firstEngine = context.createSSLEngine(); firstEngine.setUseClientMode(false); final SSLEngine anotherEngine = context.createSSLEngine(); anotherEngine.setUseClientMode(false); final ByteBuffer expandBufferHandshake = ByteBuffer .wrap(new byte[] { 0x16, 0x3, 0x3, 0x71, 0x41 }); final ByteBuffer unwrapDest = ByteBuffer.allocate(64 * 1024); // enable large fragment buffers in all engines in the JVM firstEngine.unwrap(expandBufferHandshake, unwrapDest); unwrapDest.clear(); } } URLRewritingSessionTestCase.java000066400000000000000000000152201420065311100353010ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/session/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.session; import java.io.IOException; import java.util.Deque; import java.util.Map; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.session.InMemorySessionManager; import io.undertow.server.session.PathParameterSessionConfig; import io.undertow.server.session.Session; import io.undertow.server.session.SessionAttachmentHandler; import io.undertow.server.session.SessionManager; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.HttpString; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.BasicCookieStore; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * basic test of in memory session functionality * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class URLRewritingSessionTestCase { public static final String COUNT = "count"; @BeforeClass public static void setup() { final PathParameterSessionConfig sessionConfig = new PathParameterSessionConfig(); final SessionAttachmentHandler handler = new SessionAttachmentHandler(new InMemorySessionManager(""), sessionConfig); handler.setNext(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final SessionManager manager = exchange.getAttachment(SessionManager.ATTACHMENT_KEY); Session session = manager.getSession(exchange, sessionConfig); if (session == null) { session = manager.createSession(exchange, sessionConfig); session.setAttribute(COUNT, 0); } else { Assert.assertEquals("/notamatchingpath;jsessionid=" + session.getId(), exchange.getRequestURI()); } Integer count = (Integer) session.getAttribute(COUNT); exchange.getResponseHeaders().add(new HttpString(COUNT), count.toString()); session.setAttribute(COUNT, ++count); for (Map.Entry> qp : exchange.getQueryParameters().entrySet()) { exchange.getResponseHeaders().add(new HttpString(qp.getKey()), qp.getValue().getFirst()); } if (exchange.getQueryString().isEmpty()) { exchange.getResponseSender().send(sessionConfig.rewriteUrl(DefaultServer.getDefaultServerURL() + "/notamatchingpath", session.getId())); } else { exchange.getResponseSender().send(sessionConfig.rewriteUrl(DefaultServer.getDefaultServerURL() + "/notamatchingpath?" + exchange.getQueryString(), session.getId())); } } }); DefaultServer.setRootHandler(handler); } @Test public void testURLRewriting() throws IOException { TestHttpClient client = new TestHttpClient(); client.setCookieStore(new BasicCookieStore()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/notamatchingpath;foo=bar"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String url = HttpClientUtils.readResponse(result); Header[] header = result.getHeaders(COUNT); Assert.assertEquals("0", header[0].getValue()); get = new HttpGet(url); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); url = HttpClientUtils.readResponse(result); header = result.getHeaders(COUNT); Assert.assertEquals("1", header[0].getValue()); get = new HttpGet(url); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); url = HttpClientUtils.readResponse(result); header = result.getHeaders(COUNT); Assert.assertEquals("2", header[0].getValue()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testURLRewritingWithQueryParameters() throws IOException { TestHttpClient client = new TestHttpClient(); client.setCookieStore(new BasicCookieStore()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/notamatchingpath?a=b;c"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String url = HttpClientUtils.readResponse(result); Header[] header = result.getHeaders(COUNT); Assert.assertEquals("0", header[0].getValue()); Assert.assertEquals("b;c", result.getHeaders("a")[0].getValue()); get = new HttpGet(url); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); url = HttpClientUtils.readResponse(result); header = result.getHeaders(COUNT); Assert.assertEquals("1", header[0].getValue()); Assert.assertEquals("b;c", result.getHeaders("a")[0].getValue()); get = new HttpGet(url); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); url = HttpClientUtils.readResponse(result); header = result.getHeaders(COUNT); Assert.assertEquals("2", header[0].getValue()); Assert.assertEquals("b;c", result.getHeaders("a")[0].getValue()); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/sse/000077500000000000000000000000001420065311100262675ustar00rootroot00000000000000ServerSentEventTestCase.java000066400000000000000000000277111420065311100336210ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/handlers/sse/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.handlers.sse; import io.undertow.server.handlers.encoding.ContentEncodingRepository; import io.undertow.server.handlers.encoding.DeflateEncodingProvider; import io.undertow.server.handlers.encoding.EncodingHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import io.undertow.util.WorkerUtils; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DecompressingHttpClient; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.IoUtils; import org.xnio.XnioIoThread; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ServerSentEventTestCase { @Test public void testSSE() throws IOException { TestHttpClient client = new TestHttpClient(); try { DefaultServer.setRootHandler(new ServerSentEventHandler(new ServerSentEventConnectionCallback() { @Override public void connected(ServerSentEventConnection connection, String lastEventId) { connection.send("msg 1", new ServerSentEventConnection.EventCallback() { @Override public void done(ServerSentEventConnection connection, String data, String event, String id) { connection.send("msg\n2", new ServerSentEventConnection.EventCallback() { @Override public void done(ServerSentEventConnection connection, String data, String event, String id) { connection.shutdown(); } @Override public void failed(ServerSentEventConnection connection, String data, String event, String id, IOException e) { e.printStackTrace(); IoUtils.safeClose(connection); } }); } @Override public void failed(ServerSentEventConnection connection, String data, String event, String id, IOException e) { e.printStackTrace(); IoUtils.safeClose(connection); } }); } })); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("data:msg 1\n\ndata:msg\ndata:2\n\n", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testRetry() throws IOException { TestHttpClient client = new TestHttpClient(); try { DefaultServer.setRootHandler(new ServerSentEventHandler(new ServerSentEventConnectionCallback() { @Override public void connected(ServerSentEventConnection connection, String lastEventId) { connection.sendRetry(1000, new ServerSentEventConnection.EventCallback() { @Override public void done(ServerSentEventConnection connection, String data, String event, String id) { connection.shutdown(); } @Override public void failed(ServerSentEventConnection connection, String data, String event, String id, IOException e) { e.printStackTrace(); IoUtils.safeClose(connection); } }); } })); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("retry:1000\n\n", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testProgressiveSSEWithCompression() throws IOException { final AtomicReference connectionReference = new AtomicReference<>(); DecompressingHttpClient client = new DecompressingHttpClient(new TestHttpClient()); try { DefaultServer.setRootHandler(new EncodingHandler(new ContentEncodingRepository() .addEncodingHandler("deflate", new DeflateEncodingProvider(), 50)) .setNext(new ServerSentEventHandler(new ServerSentEventConnectionCallback() { @Override public void connected(ServerSentEventConnection connection, String lastEventId) { connectionReference.set(connection); connection.send("msg 1", new ServerSentEventConnection.EventCallback() { @Override public void done(ServerSentEventConnection connection, String data, String event, String id) { } @Override public void failed(ServerSentEventConnection connection, String data, String event, String id, IOException e) { e.printStackTrace(); IoUtils.safeClose(connection); } }); } }))); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); InputStream stream = result.getEntity().getContent(); assertData(stream, "data:msg 1\n\n"); connectionReference.get().send("msg 2"); assertData(stream, "data:msg 2\n\n"); connectionReference.get().close(); } finally { client.getConnectionManager().shutdown(); } } private void assertData(InputStream stream, String data) throws IOException { byte[] d = data.getBytes(StandardCharsets.US_ASCII); int index = 0; byte[] buf = new byte[100]; while (index < d.length) { int r = stream.read(buf); if(r == -1 ){ Assert.fail("unexpected end of stream at index " + index); } int rem = d.length - index; if(r > rem) { Assert.fail("Read too much data index: " + index + " expected: " + data + " read: " + new String(buf, 0 , r)); } for(int i = 0; i < r; ++i) { Assert.assertEquals("Comparison failed " + "index: " + index + " expected: " + data + " read: " + new String(buf, 0 , r),d[index++], buf[i]); } } } @Test public void testLargeMessage() throws IOException { TestHttpClient client = new TestHttpClient(); final StringBuilder sb = new StringBuilder(); for(int i =0; i < 10000; ++i) { sb.append("hello world "); } try { DefaultServer.setRootHandler(new ServerSentEventHandler(new ServerSentEventConnectionCallback() { @Override public void connected(ServerSentEventConnection connection, String lastEventId) { connection.send(sb.toString(), new ServerSentEventConnection.EventCallback() { @Override public void done(ServerSentEventConnection connection, String data, String event, String id) { connection.shutdown(); } @Override public void failed(ServerSentEventConnection connection, String data, String event, String id, IOException e) { e.printStackTrace(); IoUtils.safeClose(connection); } }); } })); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("data:" + sb.toString() + "\n\n", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testConnectionFail() throws IOException, InterruptedException { final Socket socket = new Socket(DefaultServer.getHostAddress("default"), DefaultServer.getHostPort("default")); final CountDownLatch latch = new CountDownLatch(1); final CountDownLatch connected = new CountDownLatch(1); DefaultServer.setRootHandler(new ServerSentEventHandler(new ServerSentEventConnectionCallback() { @Override public void connected(final ServerSentEventConnection connection, final String lastEventId) { final XnioIoThread thread = (XnioIoThread) Thread.currentThread(); connected.countDown(); thread.execute(new Runnable() { @Override public void run() { connection.send("hello", new ServerSentEventConnection.EventCallback() { @Override public void done(ServerSentEventConnection connection, String data, String event, String id) { } @Override public void failed(ServerSentEventConnection connection, String data, String event, String id, IOException e) { latch.countDown(); } }); if(latch.getCount() > 0) { WorkerUtils.executeAfter(thread, this, 100, TimeUnit.MILLISECONDS); } } }); } })); InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); out.write(("GET / HTTP/1.1\r\nHost:" + DefaultServer.getHostAddress() +"\r\n\r\n").getBytes()); out.flush(); if(!connected.await(10, TimeUnit.SECONDS)) { Assert.fail(); } out.close(); in.close(); if(!latch.await(10, TimeUnit.SECONDS)) { Assert.fail(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/protocol/000077500000000000000000000000001420065311100255365ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/protocol/ajp/000077500000000000000000000000001420065311100263105ustar00rootroot00000000000000AjpCharacterEncodingTestCase.java000066400000000000000000000073051420065311100345330ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/protocol/ajp/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.ajp; import io.undertow.Undertow; import io.undertow.UndertowOptions; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.proxy.LoadBalancingProxyClient; import io.undertow.server.handlers.proxy.ProxyHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.ProxyIgnore; import io.undertow.util.FileUtils; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.OptionMap; import java.io.IOException; import java.net.Socket; import java.net.URI; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) @ProxyIgnore public class AjpCharacterEncodingTestCase { private static final int PORT = DefaultServer.getHostPort() + 10; private static Undertow undertow; private static OptionMap old; @BeforeClass public static void setup() throws Exception { undertow = Undertow.builder() .setServerOption(UndertowOptions.URL_CHARSET, "MS949") .setServerOption(UndertowOptions.ALLOW_UNESCAPED_CHARACTERS_IN_URL, true) .addListener( new Undertow.ListenerBuilder() .setType(Undertow.ListenerType.AJP) .setHost(DefaultServer.getHostAddress()) .setPort(PORT) ).setHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("RESULT:" + exchange.getQueryParameters().get("p").getFirst()); } }) .build(); undertow.start(); DefaultServer.setRootHandler(ProxyHandler.builder().setProxyClient(new LoadBalancingProxyClient().addHost(new URI("ajp://" + DefaultServer.getHostAddress() + ":" + PORT))).build()); old = DefaultServer.getUndertowOptions(); DefaultServer.setUndertowOptions(OptionMap.create(UndertowOptions.ALLOW_UNESCAPED_CHARACTERS_IN_URL, true, UndertowOptions.URL_CHARSET, "MS949")); } @AfterClass public static void after() { DefaultServer.setUndertowOptions(old); undertow.stop(); // sleep 1 s to prevent BindException (Address already in use) when running the CI try { Thread.sleep(1000); } catch (InterruptedException ignore) {} } @Test public void sendHttpRequest() throws IOException { Socket socket = new Socket(DefaultServer.getHostAddress(), DefaultServer.getHostPort()); socket.getOutputStream().write("GET /path?p=한%20글 HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n".getBytes("MS949")); String result = FileUtils.readFile(socket.getInputStream()); Assert.assertTrue("Failed to find expected result \n" + result, result.contains("한 글")); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/protocol/ajp/AjpParsingUnitTestCase.java000066400000000000000000000235211420065311100335100ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.ajp; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Deque; import java.util.Map; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import org.xnio.IoUtils; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.category.UnitTest; import io.undertow.util.BadRequestException; import io.undertow.util.Headers; import io.undertow.util.Methods; import io.undertow.util.Protocols; /** * @author Stuart Douglas */ @Category(UnitTest.class) public class AjpParsingUnitTestCase { private static final ByteBuffer buffer; static { final InputStream stream = AjpParsingUnitTestCase.class.getResourceAsStream("sample-ajp-request"); try { ByteArrayOutputStream out = new ByteArrayOutputStream(); int r = 0; byte[] buf = new byte[1024]; while ((r = stream.read(buf)) > 0) { out.write(buf, 0, r); } buffer = ByteBuffer.wrap(out.toByteArray()); } catch (IOException e) { throw new RuntimeException(e); } finally { IoUtils.safeClose(stream); } } public static final AjpRequestParser AJP_REQUEST_PARSER = new AjpRequestParser("UTF-8", true, 100, 100, false, true); @Test public void testAjpParsing() throws IOException, BadRequestException { final ByteBuffer buffer = AjpParsingUnitTestCase.buffer.duplicate(); HttpServerExchange result = new HttpServerExchange(null); final AjpRequestParseState state = new AjpRequestParseState(); AJP_REQUEST_PARSER.parse(buffer, state, result); Assert.assertEquals(165, state.dataSize); Assert.assertTrue(state.isComplete()); Assert.assertEquals(0, buffer.remaining()); testResult(result); } @Test public void testByteByByteAjpParsing() throws IOException, BadRequestException { final ByteBuffer buffer = AjpParsingUnitTestCase.buffer.duplicate(); HttpServerExchange result = new HttpServerExchange(null); final AjpRequestParseState state = new AjpRequestParseState(); int limit = buffer.limit(); for (int i = 1; i <= limit; ++i) { buffer.limit(i); AJP_REQUEST_PARSER.parse(buffer, state, result); } Assert.assertEquals(165, state.dataSize); Assert.assertTrue(state.isComplete()); testResult(result); } private void testResult(final HttpServerExchange exchange) { Assert.assertSame(Methods.GET, exchange.getRequestMethod()); Assert.assertEquals(Protocols.HTTP_1_1, exchange.getProtocol()); Assert.assertEquals(3, exchange.getRequestHeaders().getHeaderNames().size()); Assert.assertEquals("localhost:7777", exchange.getRequestHeaders().getFirst(Headers.HOST)); Assert.assertEquals("Apache-HttpClient/4.1.3 (java 1.5)", exchange.getRequestHeaders().getFirst(Headers.USER_AGENT)); Assert.assertEquals("Keep-Alive", exchange.getRequestHeaders().getFirst(Headers.CONNECTION)); } @Test public void testCharsetHandling() throws Exception { ByteBuffer data = createAjpRequest("/hi".getBytes(StandardCharsets.UTF_8), "param=value".getBytes(StandardCharsets.UTF_8)); HttpServerExchange result = new HttpServerExchange(null); AjpRequestParseState state = new AjpRequestParseState(); AJP_REQUEST_PARSER.parse(data, state, result); Assert.assertFalse(state.badRequest); Assert.assertEquals("/hi", result.getRequestPath()); Assert.assertEquals("/hi", result.getRequestURI()); Assert.assertEquals("param=value", result.getQueryString()); data = createAjpRequest("/한글이름".getBytes(StandardCharsets.UTF_8), "param=한글이름".getBytes(StandardCharsets.UTF_8)); result = new HttpServerExchange(null); state = new AjpRequestParseState(); AJP_REQUEST_PARSER.parse(data, state, result); Assert.assertFalse(state.badRequest); Assert.assertEquals("/한글이름", result.getRequestPath()); Assert.assertEquals("/한글이름", result.getRequestURI()); Assert.assertEquals("param=한글이름", result.getQueryString()); } @Test public void testInvalidQueryString() throws Exception { ByteBuffer data = createAjpRequest("/hi".getBytes(StandardCharsets.UTF_8), "param=value%http".getBytes(StandardCharsets.UTF_8)); HttpServerExchange result = new HttpServerExchange(null); AjpRequestParseState state = new AjpRequestParseState(); AJP_REQUEST_PARSER.parse(data, state, result); Assert.assertTrue(state.badRequest); } @Test public void testPathParamsQueryString() throws Exception { ByteBuffer data = createAjpRequest("/hi;path=param".getBytes(StandardCharsets.UTF_8), "param=value".getBytes(StandardCharsets.UTF_8)); HttpServerExchange result = new HttpServerExchange(null); AjpRequestParseState state = new AjpRequestParseState(); AJP_REQUEST_PARSER.parse(data, state, result); Assert.assertFalse(state.badRequest); Assert.assertEquals("/hi", result.getRequestPath()); Assert.assertEquals("/hi;path=param", result.getRequestURI()); Assert.assertEquals("param=value", result.getQueryString()); Map> paramsMap = result.getPathParameters(); Assert.assertNotNull(paramsMap); Assert.assertEquals(1, paramsMap.size()); assertPathParamValue(paramsMap, "path", "param"); } @Test public void testPathParamMatrixQueryString() throws Exception { ByteBuffer data = createAjpRequest("/hi1;path1=param1;path2=param2/hi2;path3=param3/hi3/hi4/hi5;path4=param4;path5=param5/hi6".getBytes(StandardCharsets.UTF_8), "param=value".getBytes(StandardCharsets.UTF_8)); HttpServerExchange result = new HttpServerExchange(null); AjpRequestParseState state = new AjpRequestParseState(); AJP_REQUEST_PARSER.parse(data, state, result); Assert.assertFalse(state.badRequest); Assert.assertEquals("/hi1/hi2/hi3/hi4/hi5/hi6", result.getRequestPath()); Assert.assertEquals("/hi1;path1=param1;path2=param2/hi2;path3=param3/hi3/hi4/hi5;path4=param4;path5=param5/hi6", result.getRequestURI()); Assert.assertEquals("param=value", result.getQueryString()); Map> paramsMap = result.getPathParameters(); Assert.assertNotNull(paramsMap); Assert.assertEquals(5, paramsMap.size()); assertPathParamValue(paramsMap, "path1", "param1"); assertPathParamValue(paramsMap, "path2", "param2"); assertPathParamValue(paramsMap, "path3", "param3"); assertPathParamValue(paramsMap, "path4", "param4"); assertPathParamValue(paramsMap, "path5", "param5"); } private void assertPathParamValue(Map> pathParams, String pathParam, String pathParamValue) { final Deque pathValue = pathParams.get(pathParam); Assert.assertNotNull(pathValue); Assert.assertArrayEquals(new String[]{pathParamValue}, pathValue.toArray()); } protected ByteBuffer createAjpRequest(byte[] path, byte[] query) { ByteBuffer data = ByteBuffer.allocate(1000); data.put((byte) 0x12); data.put((byte) 0x34); data.put((byte) 0); //size data.put((byte) 0); data.put((byte) 2); data.put((byte) 2); //GET method putString(data, "HTTP/1.1"); putString(data, path); putString(data, "");//REMOTE_ADDRESS putString(data, "");//REMOTE_HOST putString(data, "");//SERVER_NAME putInt(data, 100); //SERVER_PORT data.put((byte) 0); //IS_SSL putInt(data, 0); //number of headers putQueryAttribute(data, query); // Attribute - query string data.put((byte) 0xFF); int dataLength = data.position() - 4; data.put(2, (byte) ((dataLength >> 8) & 0xFF)); data.put(3, (byte) (dataLength & 0xFF)); data.flip(); return data; } static void putInt(final ByteBuffer buf, int value) { buf.put((byte) ((value >> 8) & 0xFF)); buf.put((byte) (value & 0xFF)); } static void putString(final ByteBuffer buf, String value) { final int length = value.length(); putInt(buf, length); for (int i = 0; i < length; ++i) { buf.put((byte) value.charAt(i)); } buf.put((byte) 0); } static void putString(final ByteBuffer buf, byte[] value) { final int length = value.length; putInt(buf, length); for (int i = 0; i < length; ++i) { buf.put(value[i]); } buf.put((byte) 0); } static void putQueryAttribute(final ByteBuffer buf, byte[] value) { final int length = value.length; putInt(buf, 0x05); putInt(buf, length); for (int i = 0; i < length; ++i) { buf.put(value[i]); } buf.put((byte) 0); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/protocol/ajp/sample-ajp-request000066400000000000000000000002511420065311100317500ustar00rootroot000000000000004HTTP/1.1/notamatchingpath 127.0.0.1 localhosta localhost:7777 Keep-Alive"Apache-HttpClient/4.1.3 (java 1.5) AJP_REMOTE_PORT53097undertow-2.2.16.Final/core/src/test/java/io/undertow/server/protocol/http/000077500000000000000000000000001420065311100265155ustar00rootroot00000000000000ContentOverrunTestCase.java000066400000000000000000000071021420065311100337300ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/protocol/http/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http; import io.undertow.Handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.BlockingHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.HttpOneOnly; import io.undertow.testutils.ProxyIgnore; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.nio.charset.StandardCharsets; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) @ProxyIgnore @HttpOneOnly public class ContentOverrunTestCase { @BeforeClass public static void setup() { HttpHandler overlyLong = new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.setResponseContentLength(10); exchange.getOutputStream().write("Overly long content".getBytes(StandardCharsets.UTF_8)); } }; HttpHandler responseNotAllowed = new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.setStatusCode(204); exchange.getOutputStream().write("Overly long content".getBytes(StandardCharsets.UTF_8)); } }; DefaultServer.setRootHandler(Handlers.path().addPrefixPath("/204", new BlockingHandler(responseNotAllowed)).addPrefixPath("/long", new BlockingHandler(overlyLong))); } @Test public void testContentOn204() throws Exception { final TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/204"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.NO_CONTENT, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testContentPastContentLength() throws Exception { final TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/long"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("Overly lon", response); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/protocol/http/ParserResumeTestCase.java000066400000000000000000000113061420065311100334320ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http; import io.undertow.UndertowOptions; import io.undertow.testutils.category.UnitTest; import io.undertow.server.HttpServerExchange; import io.undertow.util.HttpString; import io.undertow.util.Methods; import io.undertow.util.Protocols; import io.undertow.util.BadRequestException; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import org.xnio.OptionMap; import java.nio.ByteBuffer; /** * Tests that the parser can resume when it is given partial input * * @author Stuart Douglas */ @Category(UnitTest.class) public class ParserResumeTestCase { public static final String DATA = "GET http://www.somehost.net/apath%20with%20spaces%20and%20I%C3%B1t%C3%ABrn%C3%A2ti%C3%B4n%C3%A0li%C5%BE%C3%A6ti%C3%B8n?key1=value1&key2=I%C3%B1t%C3%ABrn%C3%A2ti%C3%B4n%C3%A0li%C5%BE%C3%A6ti%C3%B8n HTTP/1.1\r\nHost: www.somehost.net\r\nOtherHeader: some\r\n value\r\nHostee:another\r\nAccept-garbage: a\r\n\r\ntttt"; public static final HttpRequestParser PARSER = HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)); final ParseState context = new ParseState(10); @Test public void testMethodSplit() { byte[] in = DATA.getBytes(); for (int i = 0; i < in.length - 4; ++i) { try { testResume(i, in); } catch (Throwable e) { throw new RuntimeException("Test failed at split " + i, e); } } } @Test public void testOneCharacterAtATime() throws BadRequestException { context.reset(); byte[] in = DATA.getBytes(); HttpServerExchange result = new HttpServerExchange(null); ByteBuffer buffer = ByteBuffer.wrap(in); int oldLimit = buffer.limit(); buffer.limit(1); while (context.state != ParseState.PARSE_COMPLETE) { PARSER.handle(buffer, context, result); if(context.state != ParseState.PARSE_COMPLETE) { buffer.limit(buffer.limit() + 1); } } Assert.assertEquals(oldLimit, buffer.limit() + 4); runAssertions(result); } private void testResume(final int split, byte[] in) throws BadRequestException { context.reset(); HttpServerExchange result = new HttpServerExchange(null); ByteBuffer buffer = ByteBuffer.wrap(in); buffer.limit(split); PARSER.handle(buffer, context, result); buffer.limit(buffer.capacity()); PARSER.handle(buffer, context, result); runAssertions(result); Assert.assertEquals(4, buffer.remaining()); } private void runAssertions(final HttpServerExchange result) { Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("/apath with spaces and Iñtërnâtiônàližætiøn", result.getRelativePath()); Assert.assertEquals("http://www.somehost.net/apath%20with%20spaces%20and%20I%C3%B1t%C3%ABrn%C3%A2ti%C3%B4n%C3%A0li%C5%BE%C3%A6ti%C3%B8n", result.getRequestURI()); Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol()); Assert.assertEquals("www.somehost.net", result.getRequestHeaders().getFirst(new HttpString("Host"))); Assert.assertEquals("some value", result.getRequestHeaders().getFirst(new HttpString("OtherHeader"))); Assert.assertEquals("another", result.getRequestHeaders().getFirst(new HttpString("Hostee"))); Assert.assertEquals("a", result.getRequestHeaders().getFirst(new HttpString("Accept-garbage"))); Assert.assertEquals(4, result.getRequestHeaders().getHeaderNames().size()); Assert.assertEquals(ParseState.PARSE_COMPLETE, context.state); Assert.assertEquals("key1=value1&key2=I%C3%B1t%C3%ABrn%C3%A2ti%C3%B4n%C3%A0li%C5%BE%C3%A6ti%C3%B8n", result.getQueryString()); Assert.assertEquals("value1", result.getQueryParameters().get("key1").getFirst()); Assert.assertEquals("Iñtërnâtiônàližætiøn", result.getQueryParameters().get("key2").getFirst()); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/protocol/http/SimpleParserTestCase.java000066400000000000000000001072141420065311100334270ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http; import io.undertow.UndertowOptions; import io.undertow.testutils.category.UnitTest; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.Methods; import io.undertow.util.Protocols; import io.undertow.util.BadRequestException; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import org.xnio.OptionMap; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; /** * Basic test of the HTTP parser functionality. *

* This tests parsing the same basic request, over and over, with minor differences. *

* * @author Stuart Douglas */ @Category(UnitTest.class) public class SimpleParserTestCase { private final ParseState parseState = new ParseState(-1); @Test public void testEncodedSlashDisallowed() throws BadRequestException { byte[] in = "GET /somepath%2FotherPath HTTP/1.1\r\n\r\n".getBytes(); final ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.EMPTY).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("/somepath%2FotherPath", result.getRequestURI()); Assert.assertEquals("/somepath%2FotherPath", result.getRequestPath()); } @Test public void testEncodedSlashAllowed() throws BadRequestException { byte[] in = "GET /somepath%2fotherPath HTTP/1.1\r\n\r\n".getBytes(); final ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("/somepath/otherPath", result.getRequestPath()); Assert.assertEquals("/somepath%2fotherPath", result.getRequestURI()); } @Test public void testColonSlashInURL() throws BadRequestException { byte[] in = "GET /a/http://myurl.com/b/c HTTP/1.1\r\n\r\n".getBytes(); final ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("/a/http://myurl.com/b/c", result.getRequestPath()); Assert.assertEquals("/a/http://myurl.com/b/c", result.getRequestURI()); } @Test public void testColonSlashInFullURL() throws BadRequestException { byte[] in = "GET http://foo.com/a/http://myurl.com/b/c HTTP/1.1\r\n\r\n".getBytes(); final ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("/a/http://myurl.com/b/c", result.getRequestPath()); Assert.assertEquals("http://foo.com/a/http://myurl.com/b/c", result.getRequestURI()); } @Test public void testMatrixParamFlag() throws BadRequestException { byte[] in = "GET /somepath;p1 HTTP/1.1\r\n\r\n".getBytes(); ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("/somepath;p1", result.getRequestURI()); Assert.assertEquals("/somepath", result.getRequestPath()); Assert.assertEquals(1, result.getPathParameters().size()); Assert.assertEquals("p1", result.getPathParameters().keySet().toArray()[0]); Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol()); Assert.assertFalse(result.isHostIncludedInRequestURI()); } @Test public void testMatrixParamFlagEndingWithNormalPath() throws BadRequestException { byte[] in = "GET /somepath;p1/more HTTP/1.1\r\n\r\n".getBytes(); ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("/somepath;p1/more", result.getRequestURI()); Assert.assertEquals("/somepath/more", result.getRequestPath()); Assert.assertEquals(1, result.getPathParameters().size()); Assert.assertEquals("p1", result.getPathParameters().keySet().toArray()[0]); Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol()); Assert.assertFalse(result.isHostIncludedInRequestURI()); } @Test public void testMultipleMatrixParamsOfSameName() throws BadRequestException { byte[] in = "GET /somepath;p1=v1;p1=v2 HTTP/1.1\r\n\r\n".getBytes(); ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("/somepath;p1=v1;p1=v2", result.getRequestURI()); Assert.assertEquals("/somepath", result.getRequestPath()); Assert.assertEquals(1, result.getPathParameters().size()); Assert.assertEquals("p1", result.getPathParameters().keySet().toArray()[0]); Assert.assertEquals("v1", result.getPathParameters().get("p1").getFirst()); Assert.assertEquals("v2", result.getPathParameters().get("p1").getLast()); Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol()); Assert.assertFalse(result.isHostIncludedInRequestURI()); } @Test public void testCommaSeparatedParamValues() throws BadRequestException { byte[] in = "GET /somepath;p1=v1,v2 HTTP/1.1\r\n\r\n".getBytes(); ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("/somepath;p1=v1,v2", result.getRequestURI()); Assert.assertEquals("/somepath", result.getRequestPath()); Assert.assertEquals(1, result.getPathParameters().size()); Assert.assertEquals("p1", result.getPathParameters().keySet().toArray()[0]); Assert.assertEquals("v1", result.getPathParameters().get("p1").getFirst()); Assert.assertEquals("v2", result.getPathParameters().get("p1").getLast()); Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol()); Assert.assertFalse(result.isHostIncludedInRequestURI()); } @Test public void testServletURLWithPathParam() throws BadRequestException { byte[] in = "GET http://localhost:7777/servletContext/aaaa/b;param=1 HTTP/1.1\r\n\r\n".getBytes(); ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("http://localhost:7777/servletContext/aaaa/b;param=1", result.getRequestURI()); Assert.assertEquals("/servletContext/aaaa/b", result.getRequestPath()); Assert.assertEquals(1, result.getPathParameters().size()); Assert.assertEquals("param", result.getPathParameters().keySet().toArray()[0]); Assert.assertEquals("1", result.getPathParameters().get("param").getFirst()); Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol()); Assert.assertTrue(result.isHostIncludedInRequestURI()); } @Test public void testServletURLWithPathParamEndingWithNormalPath() throws BadRequestException { byte[] in = "GET http://localhost:7777/servletContext/aaaa/b;param=1/cccc HTTP/1.1\r\n\r\n".getBytes(); ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("http://localhost:7777/servletContext/aaaa/b;param=1/cccc", result.getRequestURI()); Assert.assertEquals("/servletContext/aaaa/b/cccc", result.getRequestPath()); Assert.assertEquals(1, result.getPathParameters().size()); Assert.assertEquals("param", result.getPathParameters().keySet().toArray()[0]); Assert.assertEquals("1", result.getPathParameters().get("param").getFirst()); Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol()); Assert.assertTrue(result.isHostIncludedInRequestURI()); } @Test public void testServletURLWithPathParams() throws BadRequestException { byte[] in = "GET http://localhost:7777/servletContext/aa/b;foo=bar;mysessioncookie=mSwrYUX8_e3ukAylNMkg3oMRglB4-YjxqeWqXQsI HTTP/1.1\r\n\r\n".getBytes(); ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("http://localhost:7777/servletContext/aa/b;foo=bar;mysessioncookie=mSwrYUX8_e3ukAylNMkg3oMRglB4-YjxqeWqXQsI", result.getRequestURI()); Assert.assertEquals("/servletContext/aa/b", result.getRequestPath()); Assert.assertEquals(2, result.getPathParameters().size()); Assert.assertEquals("bar", result.getPathParameters().get("foo").getFirst()); Assert.assertEquals("mSwrYUX8_e3ukAylNMkg3oMRglB4-YjxqeWqXQsI", result.getPathParameters().get("mysessioncookie").getFirst()); Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol()); Assert.assertTrue(result.isHostIncludedInRequestURI()); } @Test public void testServletPathWithPathParam() throws BadRequestException { byte[] in = "GET /servletContext/aaaa/b;param=1 HTTP/1.1\r\n\r\n".getBytes(); ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("/servletContext/aaaa/b;param=1", result.getRequestURI()); Assert.assertEquals("/servletContext/aaaa/b", result.getRequestPath()); Assert.assertEquals(1, result.getPathParameters().size()); Assert.assertEquals("param", result.getPathParameters().keySet().toArray()[0]); Assert.assertEquals("1", result.getPathParameters().get("param").getFirst()); Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol()); Assert.assertFalse(result.isHostIncludedInRequestURI()); } @Test public void testServletPathWithPathParams() throws BadRequestException { byte[] in = "GET /servletContext/aa/b;foo=bar;mysessioncookie=mSwrYUX8_e3ukAylNMkg3oMRglB4-YjxqeWqXQsI HTTP/1.1\r\n\r\n".getBytes(); ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("/servletContext/aa/b;foo=bar;mysessioncookie=mSwrYUX8_e3ukAylNMkg3oMRglB4-YjxqeWqXQsI", result.getRequestURI()); Assert.assertEquals("/servletContext/aa/b", result.getRequestPath()); Assert.assertEquals(2, result.getPathParameters().size()); Assert.assertEquals("bar", result.getPathParameters().get("foo").getFirst()); Assert.assertEquals("mSwrYUX8_e3ukAylNMkg3oMRglB4-YjxqeWqXQsI", result.getPathParameters().get("mysessioncookie").getFirst()); Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol()); Assert.assertFalse(result.isHostIncludedInRequestURI()); } @Test public void testRootMatrixParam() throws BadRequestException { // TODO decide what should happen for a single semicolon as the path URI and other edge cases byte[] in = "GET ; HTTP/1.1\r\n\r\n".getBytes(); ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals(";", result.getRequestURI()); Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol()); } @Test public void testMatrixParametersWithQueryString() throws BadRequestException { byte[] in = "GET /somepath;p1=v1;p2=v2?q1=v3 HTTP/1.1\r\n\r\n".getBytes(); ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("/somepath;p1=v1;p2=v2", result.getRequestURI()); Assert.assertEquals("/somepath", result.getRequestPath()); //Assert.assertEquals("q1=v3", result.getQueryString()); Assert.assertEquals("v1", result.getPathParameters().get("p1").getFirst()); Assert.assertEquals("v2", result.getPathParameters().get("p2").getFirst()); Assert.assertEquals("q1=v3", result.getQueryString()); Assert.assertEquals("v3", result.getQueryParameters().get("q1").getFirst()); Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol()); Assert.assertFalse(result.isHostIncludedInRequestURI()); } @Test public void testMultiLevelMatrixParameter() throws BadRequestException { byte[] in = "GET /some;p1=v1/path;p1=v2?q1=v3 HTTP/1.1\r\n\r\n".getBytes(); ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("/some;p1=v1/path;p1=v2", result.getRequestURI()); Assert.assertEquals("/some/path", result.getRequestPath()); Assert.assertEquals("q1=v3", result.getQueryString()); Assert.assertEquals("v1", result.getPathParameters().get("p1").getFirst()); Assert.assertEquals("v2", result.getPathParameters().get("p1").getLast()); Assert.assertEquals("v3", result.getQueryParameters().get("q1").getFirst()); Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol()); Assert.assertFalse(result.isHostIncludedInRequestURI()); } @Test public void testServletURLMultiLevelMatrixParameter() throws BadRequestException { byte[] in = "GET http://localhost:7777/some;p1=v1/path;p1=v2?q1=v3 HTTP/1.1\r\n\r\n".getBytes(); ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("http://localhost:7777/some;p1=v1/path;p1=v2", result.getRequestURI()); Assert.assertEquals("/some/path", result.getRequestPath()); Assert.assertEquals("q1=v3", result.getQueryString()); Assert.assertEquals("v1", result.getPathParameters().get("p1").getFirst()); Assert.assertEquals("v2", result.getPathParameters().get("p1").getLast()); Assert.assertEquals("v3", result.getQueryParameters().get("q1").getFirst()); Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol()); Assert.assertTrue(result.isHostIncludedInRequestURI()); } @Test public void testMultiLevelMatrixParameters() throws BadRequestException { byte[] in = "GET /some;p1=v1/path;p2=v2?q1=v3 HTTP/1.1\r\n\r\n".getBytes(); ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("/some;p1=v1/path;p2=v2", result.getRequestURI()); Assert.assertEquals("/some/path", result.getRequestPath()); Assert.assertEquals("q1=v3", result.getQueryString()); Assert.assertEquals("v1", result.getPathParameters().get("p1").getFirst()); Assert.assertEquals("v2", result.getPathParameters().get("p2").getFirst()); Assert.assertEquals("v3", result.getQueryParameters().get("q1").getFirst()); Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol()); Assert.assertFalse(result.isHostIncludedInRequestURI()); } @Test public void testMultiLevelMatrixParameterEndingWithNormalPathAndQuery() throws BadRequestException { byte[] in = "GET /some;p1=v1/path;p1=v2/more?q1=v3 HTTP/1.1\r\n\r\n".getBytes(); ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("/some;p1=v1/path;p1=v2/more", result.getRequestURI()); Assert.assertEquals("/some/path/more", result.getRequestPath()); Assert.assertEquals("q1=v3", result.getQueryString()); Assert.assertEquals("v1", result.getPathParameters().get("p1").getFirst()); Assert.assertEquals("v2", result.getPathParameters().get("p1").getLast()); Assert.assertEquals("v3", result.getQueryParameters().get("q1").getFirst()); Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol()); Assert.assertFalse(result.isHostIncludedInRequestURI()); } @Test public void testServletURLMultiLevelMatrixParameterEndingWithNormalPathAndQuery() throws BadRequestException { byte[] in = "GET http://localhost:7777/some;p1=v1/path;p1=v2/more?q1=v3 HTTP/1.1\r\n\r\n".getBytes(); ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("http://localhost:7777/some;p1=v1/path;p1=v2/more", result.getRequestURI()); Assert.assertEquals("/some/path/more", result.getRequestPath()); Assert.assertEquals("q1=v3", result.getQueryString()); Assert.assertEquals("v1", result.getPathParameters().get("p1").getFirst()); Assert.assertEquals("v2", result.getPathParameters().get("p1").getLast()); Assert.assertEquals("v3", result.getQueryParameters().get("q1").getFirst()); Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol()); Assert.assertTrue(result.isHostIncludedInRequestURI()); } @Test public void testFullUrlRootPath() throws BadRequestException { byte[] in = "GET http://myurl.com HTTP/1.1\r\n\r\n".getBytes(); final ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("/", result.getRequestPath()); Assert.assertEquals("http://myurl.com", result.getRequestURI()); Assert.assertTrue(result.isHostIncludedInRequestURI()); } @Test public void testSth() throws BadRequestException { byte[] in = "GET http://myurl.com/goo;foo=bar;blah=foobar HTTP/1.1\r\n\r\n".getBytes(); final ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("/goo", result.getRequestPath()); Assert.assertEquals("http://myurl.com/goo;foo=bar;blah=foobar", result.getRequestURI()); Assert.assertEquals(2, result.getPathParameters().size()); Assert.assertTrue(result.isHostIncludedInRequestURI()); } @Test(expected = BadRequestException.class) public void testLineEndingInsteadOfSpacesAfterVerb() throws BadRequestException { byte[] in = "GET\r/somepath HTTP/1.1\r\nHost: www.somehost.net\r\nOtherHeader: some\r\n value\r\n\r\n".getBytes(); runTest(in); } @Test(expected = BadRequestException.class) public void testLineEndingInsteadOfSpacesAfterPath() throws BadRequestException { byte[] in = "GET /somepath\rHTTP/1.1\r\nHost: www.somehost.net\r\nOtherHeader: some\r\n value\r\n\r\n".getBytes(); runTest(in); } @Test(expected = BadRequestException.class) public void testLineEndingInsteadOfSpacesAfterVerb2() throws BadRequestException { byte[] in = "GET\n/somepath HTTP/1.1\r\nHost: www.somehost.net\r\nOtherHeader: some\r\n value\r\n\r\n".getBytes(); runTest(in); } @Test(expected = BadRequestException.class) public void testLineEndingInsteadOfSpacesAfterVerb3() throws BadRequestException { byte[] in = "FOO\n/somepath HTTP/1.1\r\nHost: www.somehost.net\r\nOtherHeader: some\r\n value\r\n\r\n".getBytes(); runTest(in); } @Test(expected = BadRequestException.class) public void testLineEndingInsteadOfSpacesAfterPath2() throws BadRequestException { byte[] in = "GET /somepath\nHTTP/1.1\r\nHost: www.somehost.net\r\nOtherHeader: some\r\n value\r\n\r\n".getBytes(); runTest(in); } @Test public void testSimpleRequest() throws BadRequestException { byte[] in = "GET /somepath HTTP/1.1\r\nHost: www.somehost.net\r\nOtherHeader: some\r\n value\r\n\r\n".getBytes(); runTest(in); } @Test public void testDifferentCaseHeaders() throws BadRequestException { final ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); byte[] in = "GET /somepath HTTP/1.1\r\nHost: www.somehost.net\r\nhost: other\r\n\r\n".getBytes(); HttpRequestParser.instance(OptionMap.EMPTY).handle(ByteBuffer.wrap(in), context, result); Assert.assertArrayEquals(result.getRequestHeaders().get("HOST").toArray(), new String[] {"www.somehost.net", "other"}); } @Test(expected = BadRequestException.class) public void testTabInsteadOfSpaceAfterVerb() throws BadRequestException { byte[] in = "GET\t/somepath HTTP/1.1\r\nHost: www.somehost.net\r\nOtherHeader: some\r\n value\r\n\r\n".getBytes(); runTest(in); } @Test(expected = BadRequestException.class) public void testTabInsteadOfSpaceAfterVerb2() throws BadRequestException { byte[] in = "FOO\t/somepath HTTP/1.1\r\nHost: www.somehost.net\r\nOtherHeader: some\r\n value\r\n\r\n".getBytes(); runTest(in); } @Test(expected = BadRequestException.class) public void testTabInsteadOfSpaceAfterPath() throws BadRequestException { byte[] in = "GET\t/somepath HTTP/1.1\r\nHost: www.somehost.net\r\nOtherHeader: some\r\n value\r\n\r\n".getBytes(); runTest(in); } @Test(expected = BadRequestException.class) public void testInvalidCharacterInPath() throws BadRequestException { byte[] in = "GET /some>path HTTP/1.1\r\nHost: www.somehost.net\r\nOtherHeader: some\r\n value\r\n\r\n".getBytes(); runTest(in); } @Test(expected = BadRequestException.class) public void testInvalidCharacterInQueryString1() throws BadRequestException { byte[] in = "GET /somepath?foo>f=bar HTTP/1.1\r\nHost: www.somehost.net\r\nOtherHeader: some\r\n value\r\n\r\n".getBytes(); runTest(in); } @Test(expected = BadRequestException.class) public void testInvalidCharacterInQueryString2() throws BadRequestException { byte[] in = "GET /somepath?foo=ba>r HTTP/1.1\r\nHost: www.somehost.net\r\nOtherHeader: some\r\n value\r\n\r\n".getBytes(); runTest(in); } @Test(expected = BadRequestException.class) public void testInvalidCharacterInPathParam1() throws BadRequestException { byte[] in = "GET /somepath;foo>f=bar HTTP/1.1\r\nHost: www.somehost.net\r\nOtherHeader: some\r\n value\r\n\r\n".getBytes(); runTest(in); } @Test(expected = BadRequestException.class) public void testInvalidCharacterInPathParam2() throws BadRequestException { byte[] in = "GET /somepath;foo=ba>r HTTP/1.1\r\nHost: www.somehost.net\r\nOtherHeader: some\r\n value\r\n\r\n".getBytes(); runTest(in); } @Test public void testSimpleRequestWithHeaderCaching() throws BadRequestException { byte[] in = "GET /somepath HTTP/1.1\r\nHost: www.somehost.net\r\nOtherHeader: foo\r\n\r\n".getBytes(); runTest(in, "foo"); in = "GET /somepath HTTP/1.1\r\nHost: www.somehost.net\r\nOtherHeader: foo\r\n\r\n".getBytes(); runTest(in, "foo"); in = "GET /somepath HTTP/1.1\r\nHost: www.somehost.net\r\nOtherHeader: some value\r\n\r\n".getBytes(); runTest(in); in = "GET /somepath HTTP/1.1\r\nHost: www.somehost.net\r\nOtherHeader: some value\r\n\r\n".getBytes(); runTest(in); } @Test public void testCarriageReturnLineEnds() throws BadRequestException { byte[] in = "GET /somepath HTTP/1.1\rHost: www.somehost.net\rOtherHeader: some\r value\r\r\n".getBytes(); runTest(in); } @Test public void testLineFeedsLineEnds() throws BadRequestException { byte[] in = "GET /somepath HTTP/1.1\nHost: www.somehost.net\nOtherHeader: some\n value\n\n".getBytes(); runTest(in); } @Test(expected = BadRequestException.class) public void testTabWhitespace() throws BadRequestException { byte[] in = "GET\t/somepath\tHTTP/1.1\nHost: \t www.somehost.net\nOtherHeader:\tsome\n \t value\n\r\n".getBytes(); runTest(in); } @Test public void testCanonicalPath() throws BadRequestException { byte[] in = "GET http://www.somehost.net/somepath HTTP/1.1\nHost: \t www.somehost.net\nOtherHeader:\tsome\n \t value\n\r\n".getBytes(); final ParseState context = new ParseState(5); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.EMPTY).handle(ByteBuffer.wrap(in), context, result); Assert.assertEquals("/somepath", result.getRelativePath()); Assert.assertEquals("http://www.somehost.net/somepath", result.getRequestURI()); } @Test public void testNoHeaders() throws BadRequestException { byte[] in = "GET /aa HTTP/1.1\n\n\n".getBytes(); final ParseState context = new ParseState(0); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.EMPTY).handle(ByteBuffer.wrap(in), context, result); Assert.assertTrue(context.isComplete()); Assert.assertEquals("/aa", result.getRelativePath()); } @Test public void testQueryParams() throws BadRequestException { byte[] in = "GET http://www.somehost.net/somepath?a=b&b=c&d&e&f= HTTP/1.1\nHost: \t www.somehost.net\nOtherHeader:\tsome\n \t value\n\r\n".getBytes(); final ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.EMPTY).handle(ByteBuffer.wrap(in), context, result); Assert.assertEquals("/somepath", result.getRelativePath()); Assert.assertEquals("http://www.somehost.net/somepath", result.getRequestURI()); Assert.assertEquals("a=b&b=c&d&e&f=", result.getQueryString()); Assert.assertEquals("b", result.getQueryParameters().get("a").getFirst()); Assert.assertEquals("c", result.getQueryParameters().get("b").getFirst()); Assert.assertEquals("", result.getQueryParameters().get("d").getFirst()); Assert.assertEquals("", result.getQueryParameters().get("e").getFirst()); Assert.assertEquals("", result.getQueryParameters().get("f").getFirst()); } @Test public void testSameHttpStringReturned() throws BadRequestException { byte[] in = "GET http://www.somehost.net/somepath HTTP/1.1\nHost: \t www.somehost.net\nAccept-Charset:\tsome\n \t value\n\r\n".getBytes(); final ParseState context1 = new ParseState(10); HttpServerExchange result1 = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.EMPTY).handle(ByteBuffer.wrap(in), context1, result1); final ParseState context2 = new ParseState(10); HttpServerExchange result2 = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.EMPTY).handle(ByteBuffer.wrap(in), context2, result2); Assert.assertSame(result1.getProtocol(), result2.getProtocol()); Assert.assertSame(result1.getRequestMethod(), result2.getRequestMethod()); for (final HttpString header : result1.getRequestHeaders().getHeaderNames()) { boolean found = false; for (final HttpString header2 : result1.getRequestHeaders().getHeaderNames()) { if (header == header2) { found = true; break; } } if (header.equals(Headers.HOST)) { Assert.assertSame(Headers.HOST, header); } Assert.assertTrue("Could not found header " + header, found); } } /** * Test for having mixed + and %20 in path for encoding spaces https://issues.jboss.org/browse/UNDERTOW-1193 */ @Test public void testPlusSignVsSpaceEncodingInPath() throws BadRequestException { byte[] in = "GET http://myurl.com/+/mypath%20with%20spaces HTTP/1.1\r\n\r\n".getBytes(); final ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("+ in path shouldn't be treated as space, caused probably by https://issues.jboss.org/browse/UNDERTOW-1193", "/+/mypath with spaces", result.getRequestPath()); Assert.assertEquals("http://myurl.com/+/mypath%20with%20spaces", result.getRequestURI()); } @Test public void testEmptyQueryParams() throws BadRequestException { byte[] in = "GET /clusterbench/requestinfo//?;?=44&test=OK;devil=3&&&&&&&&&&&&&&&&&&&&&&&&&&&&777=666 HTTP/1.1\r\n\r\n".getBytes(); final ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.EMPTY).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("/clusterbench/requestinfo//", result.getRequestURI()); Assert.assertEquals("/clusterbench/requestinfo//", result.getRequestPath()); Assert.assertEquals(3, result.getQueryParameters().size()); Assert.assertEquals("OK;devil=3", result.getQueryParameters().get("test").getFirst()); Assert.assertEquals("666", result.getQueryParameters().get("777").getFirst()); Assert.assertEquals("44", result.getQueryParameters().get(";?").getFirst()); } @Test(expected = BadRequestException.class) public void testNonEncodedAsciiCharacters() throws UnsupportedEncodingException, BadRequestException { byte[] in = "GET /bÃ¥r HTTP/1.1\r\n\r\n".getBytes("ISO-8859-1"); final ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.EMPTY).handle(ByteBuffer.wrap(in), context, result); } @Test public void testNonEncodedAsciiCharactersExplicitlyAllowed() throws UnsupportedEncodingException, BadRequestException { byte[] in = "GET /bÃ¥r HTTP/1.1\r\n\r\n".getBytes("ISO-8859-1"); final ParseState context = new ParseState(10); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_UNESCAPED_CHARACTERS_IN_URL, true)).handle(ByteBuffer.wrap(in), context, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("/bår", result.getRequestPath()); Assert.assertEquals("/bÃ¥r", result.getRequestURI()); //not decoded } private void runTest(final byte[] in) throws BadRequestException { runTest(in, "some value"); } private void runTest(final byte[] in, String lastHeader) throws BadRequestException { parseState.reset(); HttpServerExchange result = new HttpServerExchange(null); HttpRequestParser.instance(OptionMap.EMPTY).handle(ByteBuffer.wrap(in), parseState, result); Assert.assertSame(Methods.GET, result.getRequestMethod()); Assert.assertEquals("/somepath", result.getRequestURI()); Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol()); Assert.assertEquals(2, result.getRequestHeaders().getHeaderNames().size()); Assert.assertEquals("www.somehost.net", result.getRequestHeaders().getFirst(new HttpString("Host"))); Assert.assertEquals(lastHeader, result.getRequestHeaders().getFirst(new HttpString("OtherHeader"))); Assert.assertEquals(ParseState.PARSE_COMPLETE, parseState.state); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/protocol/http2/000077500000000000000000000000001420065311100265775ustar00rootroot00000000000000HTTP2ViaUpgradeTestCase.java000066400000000000000000000400041420065311100336260ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/protocol/http2/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http2; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; import io.netty.channel.EventLoopGroup; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpClientCodec; import io.netty.handler.codec.http.HttpClientUpgradeHandler; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http2.DefaultHttp2Connection; import io.netty.handler.codec.http2.DefaultHttp2FrameReader; import io.netty.handler.codec.http2.DefaultHttp2FrameWriter; import io.netty.handler.codec.http2.DelegatingDecompressorFrameListener; import io.netty.handler.codec.http2.Http2ClientUpgradeCodec; import io.netty.handler.codec.http2.Http2Connection; import io.netty.handler.codec.http2.Http2FrameLogger; import io.netty.handler.codec.http2.Http2FrameReader; import io.netty.handler.codec.http2.Http2FrameWriter; import io.netty.handler.codec.http2.Http2InboundFrameLogger; import io.netty.handler.codec.http2.Http2OutboundFrameLogger; import io.netty.handler.codec.http2.Http2Settings; import io.netty.handler.codec.http2.HttpConversionUtil; import io.netty.handler.codec.http2.HttpToHttp2ConnectionHandler; import io.netty.handler.codec.http2.HttpToHttp2ConnectionHandlerBuilder; import io.netty.handler.codec.http2.InboundHttp2ToHttpAdapterBuilder; import io.netty.handler.logging.LogLevel; import io.undertow.Handlers; import io.undertow.Undertow; import io.undertow.UndertowOptions; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.session.SessionCookieConfig; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.util.Headers; import io.undertow.util.HttpString; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.Options; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.Iterator; import java.util.Map.Entry; import java.util.SortedMap; import java.util.TreeMap; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; /** * Tests the load balancing proxy * * @author Stuart Douglas */ @RunWith(DefaultServer.class) @HttpOneOnly public class HTTP2ViaUpgradeTestCase { static Undertow server; static volatile String message; private static final LinkedBlockingDeque messages = new LinkedBlockingDeque<>(); @BeforeClass public static void setup() throws URISyntaxException { final SessionCookieConfig sessionConfig = new SessionCookieConfig(); int port = DefaultServer.getHostPort("default"); server = Undertow.builder() .addHttpListener(port + 1, DefaultServer.getHostAddress("default")) .setServerOption(UndertowOptions.ENABLE_HTTP2, true) .setSocketOption(Options.REUSE_ADDRESSES, true) .setHandler(Handlers.header(new Http2UpgradeHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if (!(exchange.getConnection() instanceof Http2ServerConnection)) { throw new RuntimeException("Not HTTP2"); } exchange.getResponseHeaders().add(new HttpString("X-Custom-Header"), "foo"); exchange.getResponseSender().send(message); } }, "h2c", "h2c-17"), Headers.SEC_WEB_SOCKET_ACCEPT_STRING, "fake")) //work around Netty bug, it assumes that every upgrade request that does not have this header is an old style websocket upgrade .build(); server.start(); } @AfterClass public static void stop() { server.stop(); // sleep 1 s to prevent BindException (Address already in use) when running the CI try { Thread.sleep(1000); } catch (InterruptedException ignore) {} } @Test public void testHttp2WithNettyClient() throws Exception { message = "Hello World"; EventLoopGroup workerGroup = new NioEventLoopGroup(); Http2ClientInitializer initializer = new Http2ClientInitializer(Integer.MAX_VALUE); try { // Configure the client. Bootstrap b = new Bootstrap(); b.group(workerGroup); b.channel(NioSocketChannel.class); b.option(ChannelOption.SO_KEEPALIVE, true); final int port = DefaultServer.getHostPort("default") + 1; final String host = DefaultServer.getHostAddress("default"); b.remoteAddress(host, port); b.handler(initializer); // Start the client. Channel channel = b.connect().syncUninterruptibly().channel(); Http2SettingsHandler http2SettingsHandler = initializer.settingsHandler(); http2SettingsHandler.awaitSettings(5, TimeUnit.SECONDS); HttpResponseHandler responseHandler = initializer.responseHandler(); int streamId = 3; URI hostName = URI.create("http://" + host + ':' + port); System.err.println("Sending request(s)..."); // Create a simple GET request. final ChannelPromise promise = channel.newPromise(); responseHandler.put(streamId, promise); FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, hostName.toString()); request.headers().add(HttpHeaderNames.HOST, hostName); request.headers().add(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP); request.headers().add(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.DEFLATE); channel.writeAndFlush(request); streamId += 2; promise.await(10, TimeUnit.SECONDS); Assert.assertEquals(message, messages.poll()); System.out.println("Finished HTTP/2 request(s)"); // Wait until the connection is closed. channel.close().syncUninterruptibly(); } finally { workerGroup.shutdownGracefully(); } } static class Http2ClientInitializer extends ChannelInitializer { private static final Http2FrameLogger logger = new Http2FrameLogger(LogLevel.INFO, Http2ClientInitializer.class); private final int maxContentLength; private HttpToHttp2ConnectionHandler connectionHandler; private HttpResponseHandler responseHandler; private Http2SettingsHandler settingsHandler; Http2ClientInitializer(int maxContentLength) { this.maxContentLength = maxContentLength; } @Override public void initChannel(SocketChannel ch) throws Exception { final Http2Connection connection = new DefaultHttp2Connection(false); connectionHandler = new HttpToHttp2ConnectionHandlerBuilder() .connection(connection) .frameListener(new DelegatingDecompressorFrameListener(connection, new InboundHttp2ToHttpAdapterBuilder(connection) .maxContentLength(maxContentLength) .propagateSettings(true) .build())) .build(); responseHandler = new HttpResponseHandler(); settingsHandler = new Http2SettingsHandler(ch.newPromise()); configureClearText(ch); } public HttpResponseHandler responseHandler() { return responseHandler; } public Http2SettingsHandler settingsHandler() { return settingsHandler; } protected void configureEndOfPipeline(ChannelPipeline pipeline) { pipeline.addLast(settingsHandler, responseHandler); } /** * Configure the pipeline for a cleartext upgrade from HTTP to HTTP/2. */ private void configureClearText(SocketChannel ch) { HttpClientCodec sourceCodec = new HttpClientCodec(); Http2ClientUpgradeCodec upgradeCodec = new Http2ClientUpgradeCodec(connectionHandler); HttpClientUpgradeHandler upgradeHandler = new HttpClientUpgradeHandler(sourceCodec, upgradeCodec, 65536); ch.pipeline().addLast(sourceCodec, upgradeHandler, new UpgradeRequestHandler(), new UserEventLogger()); } protected String fetchUpgradeHandlerURL() { return "/sdf"; } /** * A handler that triggers the cleartext upgrade to HTTP/2 by sending an initial HTTP request. */ private final class UpgradeRequestHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { DefaultFullHttpRequest upgradeRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, fetchUpgradeHandlerURL()); upgradeRequest.headers().add(Headers.HOST_STRING, "default"); ctx.writeAndFlush(upgradeRequest); ctx.fireChannelActive(); // Done with this handler, remove it from the pipeline. ctx.pipeline().remove(this); configureEndOfPipeline(ctx.pipeline()); } } /** * Class that logs any User Events triggered on this channel. */ private static class UserEventLogger extends ChannelInboundHandlerAdapter { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { System.out.println("User Event Triggered: " + evt); ctx.fireUserEventTriggered(evt); } } private static Http2FrameReader frameReader() { return new Http2InboundFrameLogger(new DefaultHttp2FrameReader(), logger); } private static Http2FrameWriter frameWriter() { return new Http2OutboundFrameLogger(new DefaultHttp2FrameWriter(), logger); } } static class Http2SettingsHandler extends SimpleChannelInboundHandler { private ChannelPromise promise; /** * Create new instance * * @param promise Promise object used to notify when first settings are received */ Http2SettingsHandler(ChannelPromise promise) { this.promise = promise; } /** * Wait for this handler to be added after the upgrade to HTTP/2, and for initial preface * handshake to complete. * * @param timeout Time to wait * @param unit {@link java.util.concurrent.TimeUnit} for {@code timeout} * @throws Exception if timeout or other failure occurs */ public void awaitSettings(long timeout, TimeUnit unit) throws Exception { if (!promise.awaitUninterruptibly(timeout, unit)) { throw new IllegalStateException("Timed out waiting for settings"); } if (!promise.isSuccess()) { throw new RuntimeException(promise.cause()); } } @Override protected void channelRead0(ChannelHandlerContext ctx, Http2Settings msg) throws Exception { promise.setSuccess(); // Only care about the first settings message ctx.pipeline().remove(this); } } static class HttpResponseHandler extends SimpleChannelInboundHandler { private SortedMap streamidPromiseMap; HttpResponseHandler() { streamidPromiseMap = new TreeMap(); } /** * Create an association between an anticipated response stream id and a {@link io.netty.channel.ChannelPromise} * * @param streamId The stream for which a response is expected * @param promise The promise object that will be used to wait/notify events * @return The previous object associated with {@code streamId} * @see HttpResponseHandler#awaitResponses(long, java.util.concurrent.TimeUnit) */ public ChannelPromise put(int streamId, ChannelPromise promise) { return streamidPromiseMap.put(streamId, promise); } /** * Wait (sequentially) for a time duration for each anticipated response * * @param timeout Value of time to wait for each response * @param unit Units associated with {@code timeout} * @see HttpResponseHandler#put(int, io.netty.channel.ChannelPromise) */ public void awaitResponses(long timeout, TimeUnit unit) { Iterator> itr = streamidPromiseMap.entrySet().iterator(); while (itr.hasNext()) { Entry entry = itr.next(); ChannelPromise promise = entry.getValue(); if (!promise.awaitUninterruptibly(timeout, unit)) { throw new IllegalStateException("Timed out waiting for response on stream id " + entry.getKey()); } if (!promise.isSuccess()) { throw new RuntimeException(promise.cause()); } System.out.println("---Stream id: " + entry.getKey() + " received---"); itr.remove(); } } @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse msg) throws Exception { Integer streamId = msg.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text()); if (streamId == null) { System.err.println("HttpResponseHandler unexpected message received: " + msg); return; } ChannelPromise promise = streamidPromiseMap.get(streamId); if (promise == null) { System.err.println("Message received for unknown stream id " + streamId); } else { // Do stuff with the message (for now just print it) ByteBuf content = msg.content(); if (content.isReadable()) { int contentLength = content.readableBytes(); byte[] arr = new byte[contentLength]; content.readBytes(arr); messages.add(new String(arr, StandardCharsets.UTF_8)); } promise.setSuccess(); } } } } HTTP2ViaUpgradeWithUnEncodedURLCharactersTestCase.java000066400000000000000000000056721420065311100406460ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/protocol/http2/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.protocol.http2; import java.net.URISyntaxException; import org.junit.BeforeClass; import org.junit.runner.RunWith; import org.xnio.Options; import io.undertow.Handlers; import io.undertow.Undertow; import io.undertow.UndertowOptions; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.session.SessionCookieConfig; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.util.Headers; import io.undertow.util.HttpString; /** * Tests the load balancing proxy * * @author Stuart Douglas * @author baranowb */ @RunWith(DefaultServer.class) @HttpOneOnly public class HTTP2ViaUpgradeWithUnEncodedURLCharactersTestCase extends HTTP2ViaUpgradeTestCase{ @BeforeClass public static void setup() throws URISyntaxException { final SessionCookieConfig sessionConfig = new SessionCookieConfig(); int port = DefaultServer.getHostPort("default"); server = Undertow.builder() .addHttpListener(port + 1, DefaultServer.getHostAddress("default")) .setServerOption(UndertowOptions.ENABLE_HTTP2, true) .setServerOption(UndertowOptions.ALLOW_UNESCAPED_CHARACTERS_IN_URL, true) .setSocketOption(Options.REUSE_ADDRESSES, true) .setHandler(Handlers.header(new Http2UpgradeHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if (!(exchange.getConnection() instanceof Http2ServerConnection)) { throw new RuntimeException("Not HTTP2"); } exchange.getResponseHeaders().add(new HttpString("X-Custom-Header"), "foo"); exchange.getResponseSender().send(message); } }, "h2c", "h2c-17"), Headers.SEC_WEB_SOCKET_ACCEPT_STRING, "fake")) //work around Netty bug, it assumes that every upgrade request that does not have this header is an old style websocket upgrade .build(); server.start(); } protected String fetchUpgradeHandlerURL() { return "/^?query=^"; } } Http2EndExchangeTestCase.java000066400000000000000000000203271420065311100341160ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/protocol/http2/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * 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. */ package io.undertow.server.protocol.http2; import io.undertow.Undertow; import io.undertow.UndertowOptions; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientExchange; import io.undertow.client.ClientRequest; import io.undertow.client.UndertowClient; import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.BlockingHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.util.Headers; import io.undertow.util.Methods; import io.undertow.util.Protocols; import org.jboss.logging.Logger; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.IoUtils; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.Xnio; import org.xnio.XnioWorker; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static io.undertow.testutils.StopServerWithExternalWorkerUtils.stopWorker; @RunWith(DefaultServer.class) @HttpOneOnly public class Http2EndExchangeTestCase { private static final Logger log = Logger.getLogger(Http2EndExchangeTestCase.class); private static final String MESSAGE = "/message"; private static final OptionMap DEFAULT_OPTIONS; private static URI ADDRESS; static { final OptionMap.Builder builder = OptionMap.builder() .set(Options.WORKER_IO_THREADS, 8) .set(Options.TCP_NODELAY, true) .set(Options.KEEP_ALIVE, true) .set(Options.WORKER_NAME, "Client"); DEFAULT_OPTIONS = builder.getMap(); } @Test public void testHttp2EndExchangeWithBrokenConnection() throws Exception { int port = DefaultServer.getHostPort("default"); final CountDownLatch requestStartedLatch = new CountDownLatch(1); final CompletableFuture testResult = new CompletableFuture<>(); Undertow server = Undertow.builder() .addHttpsListener(port + 1, DefaultServer.getHostAddress("default"), DefaultServer.getServerSslContext()) .setServerOption(UndertowOptions.ENABLE_HTTP2, true) .setSocketOption(Options.REUSE_ADDRESSES, true) .setHandler(new BlockingHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if (!exchange.getProtocol().equals(Protocols.HTTP_2_0)) { testResult.completeExceptionally(new RuntimeException("Not HTTP/2 request")); return; } requestStartedLatch.countDown(); log.debug("Received Request"); Thread.sleep(2000); //do some pretend work if (exchange.isComplete()) { testResult.complete("FAILED, exchange ended in the background"); return; } try { exchange.getOutputStream().write("Bogus Data".getBytes(StandardCharsets.UTF_8)); exchange.getOutputStream().flush(); testResult.complete("FAILED, should not have completed successfully"); return; } catch (IOException expected) { } if (!exchange.isComplete()) { testResult.complete("Failed, should have completed the exchange"); } else { testResult.complete("PASSED"); } } })) .build(); server.start(); try { ADDRESS = new URI("https://" + DefaultServer.getHostAddress() + ":" + (port + 1)); } catch (URISyntaxException e) { throw new RuntimeException(e); } // Create xnio worker final Xnio xnio = Xnio.getInstance(); final XnioWorker xnioWorker = xnio.createWorker(null, DEFAULT_OPTIONS); try { final UndertowClient client = createClient(); final ClientConnection connection = client.connect(ADDRESS, xnioWorker, new UndertowXnioSsl(xnioWorker.getXnio(), OptionMap.EMPTY, DefaultServer.getClientSSLContext()), DefaultServer.getBufferPool(), OptionMap.create(UndertowOptions.ENABLE_HTTP2, true)).get(); try { connection.getIoThread().execute(new Runnable() { @Override public void run() { final ClientRequest request = new ClientRequest().setMethod(Methods.GET).setPath(MESSAGE); request.getRequestHeaders().put(Headers.HOST, DefaultServer.getHostAddress()); connection.sendRequest(request, new ClientCallback() { @Override public void completed(ClientExchange result) { try { log.debug("Callback invoked"); new Thread(new Runnable() { @Override public void run() { try { requestStartedLatch.await(10, TimeUnit.SECONDS); result.getRequestChannel().getIoThread().execute(new Runnable() { @Override public void run() { IoUtils.safeClose(result.getConnection()); log.debug("Closed Connection"); } }); } catch (Exception e) { testResult.completeExceptionally(e); } } }).start(); } catch (Exception e) { testResult.completeExceptionally(e); } } @Override public void failed(IOException e) { testResult.completeExceptionally(e); } }); } }); Assert.assertEquals("PASSED", testResult.get(10, TimeUnit.SECONDS)); } finally { IoUtils.safeClose(connection); } } finally { stopWorker(xnioWorker); server.stop(); // sleep 1 s to prevent BindException (Address already in use) when running the CI try { Thread.sleep(1000); } catch (InterruptedException ignore) {} } } static UndertowClient createClient() { return UndertowClient.getInstance(); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/protocol/proxy/000077500000000000000000000000001420065311100267175ustar00rootroot00000000000000ProxyProtocolTestCase.java000066400000000000000000000646331420065311100340160ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/protocol/proxypackage io.undertow.server.protocol.proxy; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import io.undertow.Undertow; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.util.FileUtils; import io.undertow.util.HttpString; import org.junit.Assert; import org.junit.Test; /** * Tests the proxy protocol * * @author Stuart Douglas * @author Jan Stourac * @author Ulrich Herberg */ public class ProxyProtocolTestCase { private static final byte[] SIG = new byte[] {0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A}; private static final byte[] NAME = "PROXY ".getBytes(StandardCharsets.US_ASCII); private static final byte PROXY = 0x21; private static final byte LOCAL = 0x20; private static final byte TCPv4 = 0x11; private static final byte TCPv6 = 0x21; // Undertow with HTTP listener and proxy-protocol enabled private Undertow undertow = Undertow.builder().addListener( new Undertow.ListenerBuilder() .setType(Undertow.ListenerType.HTTP) .setHost(DefaultServer.getHostAddress()) .setUseProxyProtocol(true) .setPort(0) ).setHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.setPersistent(false); exchange.getResponseHeaders().put(new HttpString("result"), exchange.getSourceAddress().toString().replace("[","").replace("]","") + " " + exchange.getDestinationAddress().toString().replace("[","").replace("]","")); } }).build(); // Undertow with HTTPS listener and proxy-protocol enabled private Undertow undertowSsl = Undertow.builder().addListener( new Undertow.ListenerBuilder() .setType(Undertow.ListenerType.HTTPS) .setSslContext(DefaultServer.getServerSslContext()) .setHost(DefaultServer.getHostAddress()) .setUseProxyProtocol(true) .setPort(0) ).setHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.setPersistent(false); exchange.getResponseHeaders().put(new HttpString("result"), exchange.getSourceAddress().toString() + " " + exchange.getDestinationAddress().toString()); } }).build(); @Test public void testProxyProtocolTcp4() throws Exception { // simple valid request String request = "PROXY TCP4 1.2.3.4 5.6.7.8 444 555\r\nGET / HTTP/1.0\r\n\r\n"; String expectedResponse = "result: /1.2.3.4:444 /5.6.7.8:555"; proxyProtocolRequestResponseCheck(request, expectedResponse); // check port range request = "PROXY TCP4 1.2.3.4 5.6.7.8 0 65535\r\nGET / HTTP/1.0\r\n\r\n"; expectedResponse = "result: /1.2.3.4:0 /5.6.7.8:65535"; proxyProtocolRequestResponseCheck(request, expectedResponse); } @Test public void testProxyProtocolTcp4Negative() throws Exception { // wrong number of spaces in requests String request = "PROXY TCP4 1.2.3.4 5.6.7.8 444 555\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "PROXY TCP4 1.2.3.4 5.6.7.8 444 555\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "PROXY TCP4 1.2.3.4 5.6.7.8 444 555\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "PROXY TCP4 1.2.3.4 5.6.7.8 444 555\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "PROXY TCP4 1.2.3.4 5.6.7.8 444 555\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // missing destination port request = "PROXY TCP4 1.2.3.4 5.6.7.8 444\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // missing destination address request = "PROXY TCP4 1.2.3.4 444 555\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // missing \n on the first line of the request request = "PROXY TCP4 1.2.3.4 5.6.7.8 444 555\rGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // missing \r on the first line of the request request = "PROXY TCP4 1.2.3.4 5.6.7.8 444 555\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // src address contains 0 characters at the beginning request = "PROXY TCP4 001.002.003.004 5.6.7.8 444 555\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // dst address contains '0' characters at the beginning request = "PROXY TCP4 1.2.3.4 005.006.007.008 444 555\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // src/dst ports out of range request = "PROXY TCP4 1.2.3.4 5.6.7.8 111444 555\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "PROXY TCP4 1.2.3.4 005.006.007.008 444 111555\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "PROXY TCP4 1.2.3.4 005.006.007.008 -444 555\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "PROXY TCP4 1.2.3.4 005.006.007.008 444 -555\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // src/dst ports contains '0' characters at the beginning request = "PROXY TCP4 1.2.3.4 5.6.7.8 0444 555\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "PROXY TCP4 1.2.3.4 5.6.7.8 444 0555\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // src address contains invalid characters request = "PROXY TCP4 277.2.3.4 5.6.7.8 444 555\r\nGET / " + "HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // dst address contains invalid characters request = "PROXY TCP4 1.2.3.4 5d.6.7.8 444 555\r\nGET / " + "HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // unallowed character after PROXY string request = "PROXY, TCP4 1.2.3.4 5.6.7.8 444 555\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // IPv6 address when TCP4 is used request = "PROXY TCP4 fe80::56ee:75ff:fe44:85bc fe80::5ec5:d4ff:fede:66d8 444 555\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); } @Test public void testProxyProtocolTcp6() throws Exception { // simple valid request String request = "PROXY TCP6 fe80::56ee:75ff:fe44:85bc fe80::5ec5:d4ff:fede:66d8 444 555\r\nGET / " + "HTTP/1.0\r\n\r\n"; String expectedResponse = "result: /fe80:0:0:0:56ee:75ff:fe44:85bc:444 /fe80:0:0:0:5ec5:d4ff:fede:66d8:555"; proxyProtocolRequestResponseCheck(request, expectedResponse); // check port range request = "PROXY TCP6 fe80::56ee:75ff:fe44:85bc fe80::5ec5:d4ff:fede:66d8 0 65535\r\nGET / HTTP/1.0\r\n\r\n"; expectedResponse = "result: /fe80:0:0:0:56ee:75ff:fe44:85bc:0 /fe80:0:0:0:5ec5:d4ff:fede:66d8:65535"; proxyProtocolRequestResponseCheck(request, expectedResponse); } @Test public void testProxyProtocolTcp6Negative() throws Exception { // wrong number of spaces in requests String request = "PROXY TCP6 fe80::56ee:75ff:fe44:85bc fe80::5ec5:d4ff:fede:66d8 444 555\r\nGET / " + "HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "PROXY TCP6 fe80::56ee:75ff:fe44:85bc fe80::5ec5:d4ff:fede:66d8 444 555\r\nGET / " + "HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "PROXY TCP6 fe80::56ee:75ff:fe44:85bc fe80::5ec5:d4ff:fede:66d8 444 555\r\nGET / " + "HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "PROXY TCP6 fe80::56ee:75ff:fe44:85bc fe80::5ec5:d4ff:fede:66d8 444 555\r\nGET / " + "HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "PROXY TCP6 fe80::56ee:75ff:fe44:85bc fe80::5ec5:d4ff:fede:66d8 444 555\r\nGET / " + "HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // missing destination port request = "PROXY TCP6 fe80::56ee:75ff:fe44:85bc fe80::5ec5:d4ff:fede:66d8 444\r\nGET / " + "HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // missing destination address request = "PROXY TCP6 fe80::56ee:75ff:fe44:85bc 444 555\r\nGET / " + "HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // missing \n on the first line of the request request = "PROXY TCP6 fe80::56ee:75ff:fe44:85bc fe80::5ec5:d4ff:fede:66d8 444 555\rGET / " + "HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // missing \r on the first line of the request request = "PROXY TCP6 fe80::56ee:75ff:fe44:85bc fe80::5ec5:d4ff:fede:66d8 444 555\nGET / " + "HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // src address contains invalid characters request = "PROXY TCP6 fz80::56ee:75ff:fe44:85bc fe80::5ec5:d4ff:fede:66d8 444 555\r\nGET / " + "HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // dst address contains invalid characters request = "PROXY TCP6 fe80::56ee:75ff:fe44:85bc fe80::5zc5:d4ff:fede:66d8 444 555\r\nGET / " + "HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // src/dst ports out of range request = "PROXY TCP6 fe80::56ee:75ff:fe44:85bc fe80::5ec5:d4ff:fede:66d8 111444 555\r\nGET / " + "HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "PROXY TCP6 fe80::56ee:75ff:fe44:85bc fe80::5ec5:d4ff:fede:66d8 444 111555\r\nGET / " + "HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "PROXY TCP6 fe80::56ee:75ff:fe44:85bc fe80::5ec5:d4ff:fede:66d8 -444 555\r\nGET / " + "HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "PROXY TCP6 fe80::56ee:75ff:fe44:85bc fe80::5ec5:d4ff:fede:66d8 444 -555\r\nGET / " + "HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // src/dst ports contains '0' characters at the beginning request = "PROXY TCP6 fe80::56ee:75ff:fe44:85bc fe80::5ec5:d4ff:fede:66d8 0444 555\r\nGET / " + "HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "PROXY TCP6 fe80::56ee:75ff:fe44:85bc fe80::5ec5:d4ff:fede:66d8 444 0555\r\nGET / " + "HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // unallowed character after PROXY string request = "PROXY, TCP6 fe80::56ee:75ff:fe44:85bc fe80::5ec5:d4ff:fede:66d8 444 555\r\nGET / " + "HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); // IPv6 address when TCP4 is used request = "PROXY TCP6 1.2.3.4 5.6.7.8 444 555\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); } /** * General negative tests for proxy-protocol. We expect that server closes connection sending no data. * * @throws Exception */ @Test public void testProxyProtocolNegative() throws Exception { String request = "NONSENSE\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "NONSENSE TCP4 1.2.3.4 5.6.7.8 444 555\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "NONSENSE\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "PROXY\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "PROXY\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "PROXY NONSENSE\r\n"; proxyProtocolRequestResponseCheck(request, ""); request = "PROXY NONSENSE 1.2.3.4 5.6.7.8 444 555\r\nGET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, ""); } /** * Starts an undertow server with HTTP listener and performs request to the server with given request string. * Then response from the server is checked with given expected response string. Undertow is stopped in the end. * * @param request request string that is send to server * @param expectedResponse expected response string that we expect from the server * @throws Exception */ private void proxyProtocolRequestResponseCheck(String request, String expectedResponse) throws Exception { try { undertow.start(); int port = ((InetSocketAddress) undertow.getListenerInfo().get(0).getAddress()).getPort(); Socket s = new Socket(DefaultServer.getHostAddress(), port); s.getOutputStream().write(request.getBytes(StandardCharsets.US_ASCII)); String result = FileUtils.readFile(s.getInputStream()); Assert.assertTrue(result, result.contains(expectedResponse)); } finally { undertow.stop(); // sleep 1 s to prevent BindException (Address already in use) when running the tests try { Thread.sleep(1000); } catch (InterruptedException ignore) {} } } /** * Main cases are covered in plain-text HTTP connection tests. So here is just simple check that connection can * be established also via HTTPS. * * @throws Exception */ @Test public void testProxyProtocolSSl() throws Exception { String request = "PROXY TCP4 1.2.3.4 5.6.7.8 444 555\r\n"; String requestHttp = "GET / HTTP/1.0\r\n\r\n"; String expectedResponse = "result: /1.2.3.4:444 /5.6.7.8:555"; proxyProtocolRequestResponseCheck(request, requestHttp, expectedResponse); // negative test request = "PROXY TCP4 1.2.3.4 5.6.7.8 444 555\r\n"; requestHttp = "GET / HTTP/1.0\r\n\r\n"; proxyProtocolRequestResponseCheck(request, requestHttp, ""); } @Test public void testProxyProtocolV2Tcp4() throws Exception { // simple valid request byte[] header = createProxyHeaderV2(PROXY, TCPv4, 12, InetAddress.getByName("1.2.3.4"), InetAddress.getByName("5.6.7.8"),444,555); String requestHttp = "GET / HTTP/1.0\r\n\r\n"; String expectedResponse = "result: /1.2.3.4:444 /5.6.7.8:555"; proxyProtocolRequestResponseCheck(header, requestHttp, expectedResponse); // check port range header = createProxyHeaderV2(PROXY, TCPv4, 12, InetAddress.getByName("1.2.3.4"), InetAddress.getByName("5.6.7.8"),0,65535); expectedResponse = "result: /1.2.3.4:0 /5.6.7.8:65535"; proxyProtocolRequestResponseCheck(header, requestHttp, expectedResponse); // check extra len header = createProxyHeaderV2(PROXY, TCPv4, 100, InetAddress.getByName("1.2.3.4"), InetAddress.getByName("5.6.7.8"),444,555); expectedResponse = "result: /1.2.3.4:444 /5.6.7.8:555"; proxyProtocolRequestResponseCheck(header, requestHttp, expectedResponse); } /** * Main cases are covered in plain-text HTTP connection tests. So here is just simple check that connection can * be established also via HTTPS. * * @throws Exception */ @Test public void testProxyProtocolV2SSl() throws Exception { // simple valid request byte[] header = createProxyHeaderV2(PROXY, TCPv4, 12, InetAddress.getByName("1.2.3.4"), InetAddress.getByName("5.6.7.8"),444,555); String requestHttp = "GET / HTTP/1.0\r\n\r\n"; String expectedResponse = "result: /1.2.3.4:444 /5.6.7.8:555"; proxyProtocolRequestResponseCheck(header, requestHttp, expectedResponse); } @Test public void testProxyProtocolV2Tcp4Negative() throws Exception { String requestHttp = "GET / HTTP/1.0\r\n\r\n"; byte[] request; // missing destination port request = createProxyHeaderV2(PROXY, TCPv4, 10, InetAddress.getByName("1.2.3.4"), InetAddress.getByName("5.6.7.8"),444,null); proxyProtocolRequestResponseCheck(request, requestHttp, ""); // missing destination address request = createProxyHeaderV2(PROXY, TCPv4, 8, InetAddress.getByName("1.2.3.4"), null,444,555); proxyProtocolRequestResponseCheck(request, requestHttp, ""); // invalid family request = createProxyHeaderV2(PROXY, (byte) 0x42, 12, InetAddress.getByName("1.2.3.4"), InetAddress.getByName("5.6.7.8"),444,555); proxyProtocolRequestResponseCheck(request, requestHttp, ""); // len too low request = createProxyHeaderV2(PROXY, TCPv4, 4, InetAddress.getByName("1.2.3.4"), null,null,null); proxyProtocolRequestResponseCheck(request, requestHttp, ""); } @Test public void testProxyProtocolV2Tcp6() throws Exception { String requestHttp = "GET / HTTP/1.0\r\n\r\n"; byte[] request; // simple valid request request = createProxyHeaderV2(PROXY, TCPv6, 36, InetAddress.getByName("fe80::56ee:75ff:fe44:85bc"), InetAddress.getByName("fe80::5ec5:d4ff:fede:66d8"),444,555); String expectedResponse = "result: /fe80:0:0:0:56ee:75ff:fe44:85bc:444 /fe80:0:0:0:5ec5:d4ff:fede:66d8:555"; proxyProtocolRequestResponseCheck(request, requestHttp, expectedResponse); // check port range request = createProxyHeaderV2(PROXY, TCPv6, 36, InetAddress.getByName("fe80::56ee:75ff:fe44:85bc"), InetAddress.getByName("fe80::5ec5:d4ff:fede:66d8"),0,65535); expectedResponse = "result: /fe80:0:0:0:56ee:75ff:fe44:85bc:0 /fe80:0:0:0:5ec5:d4ff:fede:66d8:65535"; proxyProtocolRequestResponseCheck(request, requestHttp, expectedResponse); } @Test public void testProxyProtocolV2Tcp6Negative() throws Exception { String requestHttp = "GET / HTTP/1.0\r\n\r\n"; byte[] request; // missing destination port request = createProxyHeaderV2(PROXY, TCPv6, 34, InetAddress.getByName("fe80::56ee:75ff:fe44:85bc"), InetAddress.getByName("fe80::5ec5:d4ff:fede:66d8"),444,null); proxyProtocolRequestResponseCheck(request, requestHttp, ""); // missing destination address request = createProxyHeaderV2(PROXY, TCPv6, 20, InetAddress.getByName("1.2.3.4"), null,444,555); proxyProtocolRequestResponseCheck(request, requestHttp, ""); // invalid family request = createProxyHeaderV2(PROXY, (byte) 0x42, 36, InetAddress.getByName("fe80::56ee:75ff:fe44:85bc"), InetAddress.getByName("fe80::5ec5:d4ff:fede:66d8"),444,555); proxyProtocolRequestResponseCheck(request, requestHttp, ""); // len too low request = createProxyHeaderV2(PROXY, TCPv6, 16, InetAddress.getByName("fe80::56ee:75ff:fe44:85bc"), null,null,null); proxyProtocolRequestResponseCheck(request, requestHttp, ""); } @Test public void testProxyProtocolV2Local() throws Exception { String requestHttp = "GET / HTTP/1.0\r\n\r\n"; byte[] request; // simple valid request request = createProxyHeaderV2(LOCAL, (byte) 0, 0, null, null,null,null); String expectedResponse; if (isIpV6()) { expectedResponse = "result: /0:0:0:0:0:0:0:1"; } else { expectedResponse = "result: /127.0.0.1"; } proxyProtocolRequestResponseCheck(request, requestHttp, expectedResponse); } /** * General negative tests for proxy-protocol. We expect that server closes connection sending no data. * * @throws Exception */ @Test public void testProxyProtocolV2Negative() throws Exception { String requestHttp = "GET / HTTP/1.0\r\n\r\n"; byte[] request; // wrong version request = createProxyHeaderV2((byte) 0, TCPv4, 12, InetAddress.getByName("1.2.3.4"), InetAddress.getByName("5.6.7.8"),444,555); proxyProtocolRequestResponseCheck(request, requestHttp, ""); // wrong signature (starting with NAME) request = new byte[]{NAME[0], 0x0, 0x0, 0x0}; proxyProtocolRequestResponseCheck(request, requestHttp, ""); // wrong signature (starting with SIG) request = new byte[]{SIG[0], 0x0, 0x0, 0x0}; proxyProtocolRequestResponseCheck(request, requestHttp, ""); // wrong signature (starting with 0x0) request = new byte[]{0x0, 0x0, 0x0, 0x0}; proxyProtocolRequestResponseCheck(request, requestHttp, ""); } private static byte[] createProxyHeaderV2(Byte ver_cmd, Byte family, Integer len, InetAddress sourceAddress, InetAddress destAddress, Integer sourcePort, Integer destPort) { ByteBuffer buffer = ByteBuffer.allocate(16 + len); buffer.put(SIG); if (ver_cmd != null) { buffer.put((byte) (ver_cmd & 0xff)); // ver=2: V2, cmd=1: PROXY / 2: LOCAL } if (family != null) { buffer.put((byte) (family & 0xff)); // 0x11: TCPv4 / 0x21: TCPv6 } if (len != null) { buffer.putShort((short) (len & 0xffff)); // len=12 } if (sourceAddress != null) { buffer.put(sourceAddress.getAddress()); } if (destAddress != null) { buffer.put(destAddress.getAddress()); } if (sourcePort != null) { buffer.putShort((short) (sourcePort & 0xffff)); } if (destPort != null) { buffer.putShort((short) (destPort & 0xffff)); } return buffer.array(); } /** * Starts an undertow server with HTTPS listener and performs request to the server with given request proxy * string and HTTP request. Then response from the server is checked with given expected response string. * Undertow is stopped in the end. * * @param requestProxy request string with proxy-protocol part * @param requestHttp request string with HTTP part * @param expectedResponse expected response string that we expect from the server * @throws Exception */ private void proxyProtocolRequestResponseCheck(String requestProxy, String requestHttp, String expectedResponse) throws Exception { proxyProtocolRequestResponseCheck(requestProxy.getBytes(StandardCharsets.US_ASCII), requestHttp, expectedResponse); } /** * Starts an undertow server with HTTP listener and performs request to the server with given request string. * Then response from the server is checked with given expected response string. Undertow is stopped in the end. * * @param request request string that is send to server * @param expectedResponse expected response string that we expect from the server * @throws Exception */ private void proxyProtocolRequestResponseCheck(byte[] request, String requestHttp, String expectedResponse) throws Exception { try { undertow.start(); int port = ((InetSocketAddress) undertow.getListenerInfo().get(0).getAddress()).getPort(); Socket s = new Socket(DefaultServer.getHostAddress(), port); s.getOutputStream().write(request); // if expectedResponse is empty, we expect server to close connection due to bad request if (!expectedResponse.isEmpty()) { s.getOutputStream().write(requestHttp.getBytes(StandardCharsets.US_ASCII)); } String result = FileUtils.readFile(s.getInputStream()); Assert.assertTrue(result, result.contains(expectedResponse)); } finally { undertow.stop(); // sleep 1 s to prevent BindException (Address already in use) when restarting the server try { Thread.sleep(1000); } catch (InterruptedException ignore) {} } } @Test public void testProxyProtocolUnknownEmpty() throws Exception { doTestProxyProtocolUnknown(""); } @Test public void testProxyProtocolUnknownSpace() throws Exception { doTestProxyProtocolUnknown(" "); } @Test public void testProxyProtocolUnknownJunk() throws Exception { doTestProxyProtocolUnknown(" mekmitasdigoat"); } /** * Starts an undertow server with HTTP listener and performs request to the server with request proxy with * UNKNOWN protocol string and HTTP request. Then response from the server is checked with given expected * response string. Undertow is stopped in the end. * * @param extra extra content added after protocol type - "UNKNOWN" - server should ignore this information * @throws Exception */ public void doTestProxyProtocolUnknown(String extra) throws Exception { try { undertow.start(); InetSocketAddress serverAddress = (InetSocketAddress) undertow.getListenerInfo().get(0).getAddress(); Socket s = new Socket(serverAddress.getAddress(), serverAddress.getPort()); String expected = String.format("result: /%s:%d /%s:%d", s.getLocalAddress().getHostAddress(), s .getLocalPort(), serverAddress.getAddress().getHostAddress(), serverAddress.getPort()); s.getOutputStream().write(("PROXY UNKNOWN" + extra + "\r\nGET / HTTP/1.0\r\n\r\n").getBytes (StandardCharsets.US_ASCII)); String result = FileUtils.readFile(s.getInputStream()); Assert.assertTrue(result, result.contains(expected)); } finally { undertow.stop(); // sleep 1 s to prevent BindException (Address already in use) when restarting the server try { Thread.sleep(1000); } catch (InterruptedException ignore) {} } } private static boolean isIpV6() { String preferIpV6Property = System.getProperty("java.net.preferIPv6Addresses"); return preferIpV6Property != null && preferIpV6Property.toLowerCase().equals("true"); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/security/000077500000000000000000000000001420065311100255445ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/security/AuthenticationTestBase.java000066400000000000000000000336201420065311100330250ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.security; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.AuthenticationMode; import io.undertow.security.api.NotificationReceiver; import io.undertow.security.api.SecurityContext; import io.undertow.security.api.SecurityNotification; import io.undertow.security.handlers.AuthenticationCallHandler; import io.undertow.security.handlers.AuthenticationConstraintHandler; import io.undertow.security.handlers.AuthenticationMechanismsHandler; import io.undertow.security.handlers.CachedAuthenticatedSessionHandler; import io.undertow.security.handlers.NotificationReceiverHandler; import io.undertow.security.handlers.SecurityInitialHandler; import io.undertow.security.idm.Account; import io.undertow.security.idm.Credential; import io.undertow.security.idm.DigestCredential; import io.undertow.security.idm.GSSContextCredential; import io.undertow.security.idm.IdentityManager; import io.undertow.security.idm.PasswordCredential; import io.undertow.security.idm.X509CertificateCredential; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.TestHttpClient; import io.undertow.util.HeaderMap; import io.undertow.util.HexConverter; import io.undertow.util.HttpString; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.ietf.jgss.GSSException; import org.junit.Before; import org.junit.Test; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** * Base class for the authentication tests. * * @author Darran Lofthouse */ public abstract class AuthenticationTestBase { private static final Charset UTF_8 = StandardCharsets.UTF_8; protected static final IdentityManager identityManager; protected static final AuditReceiver auditReceiver = new AuditReceiver(); static { final Set certUsers = new HashSet<>(); certUsers.add("CN=Test Client,OU=OU,O=Org,L=City,ST=State,C=GB"); final Set gssApiUsers = new HashSet<>(); gssApiUsers.add("jduke@UNDERTOW.IO"); final Map passwordUsers = new HashMap<>(2); passwordUsers.put("userOne", "passwordOne".toCharArray()); passwordUsers.put("userTwo", "passwordTwo".toCharArray()); passwordUsers.put("encodingUser", "password-ü".toCharArray()); identityManager = new IdentityManager() { @Override public Account verify(Account account) { // An existing account so for testing assume still valid. return account; } @Override public Account verify(String id, Credential credential) { Account account = getAccount(id); if (account != null && verifyCredential(account, credential)) { return account; } return null; } @Override public Account verify(Credential credential) { if (credential instanceof X509CertificateCredential) { final Principal p = ((X509CertificateCredential) credential).getCertificate().getSubjectX500Principal(); if (certUsers.contains(p.getName())) { return new Account() { @Override public Principal getPrincipal() { return p; } @Override public Set getRoles() { return Collections.emptySet(); } }; } } else if (credential instanceof GSSContextCredential) { try { final GSSContextCredential gssCredential = (GSSContextCredential) credential; final String name = gssCredential.getGssContext().getSrcName().toString(); if (gssApiUsers.contains(name)) { return new Account() { private final Principal principal = new Principal() { @Override public String getName() { return name; } }; @Override public Principal getPrincipal() { return principal; } @Override public Set getRoles() { return Collections.emptySet(); } }; } } catch (GSSException e) { throw new RuntimeException(e); } } return null; } private boolean verifyCredential(Account account, Credential credential) { if (credential instanceof PasswordCredential) { char[] password = ((PasswordCredential) credential).getPassword(); char[] expectedPassword = passwordUsers.get(account.getPrincipal().getName()); return Arrays.equals(password, expectedPassword); } else if (credential instanceof DigestCredential) { DigestCredential digCred = (DigestCredential) credential; MessageDigest digest = null; try { digest = digCred.getAlgorithm().getMessageDigest(); digest.update(account.getPrincipal().getName().getBytes(UTF_8)); digest.update((byte) ':'); digest.update(digCred.getRealm().getBytes(UTF_8)); digest.update((byte) ':'); char[] expectedPassword = passwordUsers.get(account.getPrincipal().getName()); digest.update(new String(expectedPassword).getBytes(UTF_8)); return digCred.verifyHA1(HexConverter.convertToHexBytes(digest.digest())); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Unsupported Algorithm", e); } finally { digest.reset(); } } else { throw new IllegalArgumentException("Invalid Credential Type " + credential.getClass().getName()); } } private Account getAccount(final String id) { if (passwordUsers.containsKey(id)) { return new Account() { private final Principal principal = new Principal() { @Override public String getName() { return id; } }; @Override public Principal getPrincipal() { return principal; } @Override public Set getRoles() { return Collections.emptySet(); } }; } return null; } }; } @Before public void setAuthenticationChain() { List testMechanisms = getTestMechanisms(); if(testMechanisms == null) { return; } HttpHandler current = new ResponseHandler(); current = new AuthenticationCallHandler(current); current = new AuthenticationConstraintHandler(current); current = new AuthenticationMechanismsHandler(current, testMechanisms); auditReceiver.takeNotifications(); // Ensure empty on initialisation. current = new NotificationReceiverHandler(current, Collections. singleton(auditReceiver)); if(cachingRequired()) { current = new CachedAuthenticatedSessionHandler(current); } current = new SecurityInitialHandler(AuthenticationMode.PRO_ACTIVE, identityManager, current); setRootHandler(current); } protected boolean cachingRequired() { return false; } protected void setRootHandler(HttpHandler current) { DefaultServer.setRootHandler(current); } protected abstract List getTestMechanisms(); /** * Basic test to prove detection of the ResponseHandler response. */ @Test public void testNoMechanisms() throws Exception { DefaultServer.setRootHandler(new ResponseHandler()); TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders("ProcessedBy"); assertEquals(1, values.length); assertEquals("ResponseHandler", values[0].getValue()); } protected static void assertSingleNotificationType(final SecurityNotification.EventType eventType) { List notifications = auditReceiver.takeNotifications(); assertEquals("A single notification is expected.", 1, notifications.size()); assertEquals("Expected EventType not matched.", eventType, notifications.get(0).getEventType()); } protected static void assertNotifiactions(final SecurityNotification.EventType ... eventTypes) { List notifications = auditReceiver.takeNotifications(); assertEquals("A single notification is expected.", eventTypes.length, notifications.size()); final List types = new ArrayList<>(); for(SecurityNotification i : notifications) { types.add(i.getEventType()); } assertEquals("Expected EventType not matched.", Arrays.asList(eventTypes), types); } protected static String getAuthenticatedUser(final HttpServerExchange exchange) { SecurityContext context = exchange.getSecurityContext(); if (context != null) { Account account = context.getAuthenticatedAccount(); if (account != null) { // An account must always return a Principal otherwise it is not an Account. return account.getPrincipal().getName(); } } return null; } protected static String getAuthHeader(final HttpString prefix, final Header[] values) { for (Header current : values) { String currentValue = current.getValue(); if (currentValue.startsWith(prefix.toString())) { return currentValue; } } fail("Expected header not found."); return null; // Unreachable } /** * A simple end of chain handler to set a header and cause the call to return. *

* Reaching this handler is a sign the mechanism handlers have allowed the request through. */ protected static class ResponseHandler implements HttpHandler { static final HttpString PROCESSED_BY = new HttpString("ProcessedBy"); static final HttpString AUTHENTICATED_USER = new HttpString("AuthenticatedUser"); @Override public void handleRequest(HttpServerExchange exchange) throws Exception { HeaderMap responseHeader = exchange.getResponseHeaders(); responseHeader.add(PROCESSED_BY, "ResponseHandler"); String user = getAuthenticatedUser(exchange); if (user != null) { responseHeader.add(AUTHENTICATED_USER, user); } if(exchange.getQueryParameters().get("logout") != null) { exchange.getSecurityContext().logout(); } exchange.endExchange(); } } protected static class AuditReceiver implements NotificationReceiver { private final List receivedNotifications = new ArrayList<>(); @Override public void handleNotification(SecurityNotification notification) { receivedNotifications.add(notification); } public List takeNotifications() { try { return new ArrayList<>(receivedNotifications); } finally { receivedNotifications.clear(); } } } } BasicAuthenticationTestCase.java000066400000000000000000000127601420065311100337130ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.security; import java.util.Collections; import java.util.List; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.SecurityNotification.EventType; import io.undertow.security.impl.BasicAuthenticationMechanism; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.FlexBase64; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.junit.Test; import org.junit.runner.RunWith; import static io.undertow.util.Headers.AUTHORIZATION; import static io.undertow.util.Headers.BASIC; import static io.undertow.util.Headers.WWW_AUTHENTICATE; import static org.junit.Assert.assertEquals; /** * A test case to test when the only authentication mechanism is the BASIC mechanism. * * @author Darran Lofthouse */ @RunWith(DefaultServer.class) public class BasicAuthenticationTestCase extends AuthenticationTestBase { static AuthenticationMechanism getTestMechanism() { return new BasicAuthenticationMechanism("Test Realm"); } @Override protected List getTestMechanisms() { AuthenticationMechanism mechanism = getTestMechanism(); return Collections.singletonList(mechanism); } @Test public void testBasicSuccess() throws Exception { _testBasicSuccess(); assertSingleNotificationType(EventType.AUTHENTICATED); } static void _testBasicSuccess() throws Exception { TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); String header = getAuthHeader(BASIC, values); assertEquals(BASIC + " realm=\"Test Realm\"", header); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL()); get.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString("userOne:passwordOne".getBytes(), false)); result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); values = result.getHeaders("ProcessedBy"); assertEquals(1, values.length); assertEquals("ResponseHandler", values[0].getValue()); HttpClientUtils.readResponse(result); } @Test public void testBadUserName() throws Exception { _testBadUserName(); assertSingleNotificationType(EventType.FAILED_AUTHENTICATION); } static void _testBadUserName() throws Exception { TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); String header = getAuthHeader(BASIC, values); assertEquals(BASIC + " realm=\"Test Realm\"", header); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL()); get.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString("badUser:passwordOne".getBytes(), false)); result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } @Test public void testBadPassword() throws Exception { _testBadPassword(); assertSingleNotificationType(EventType.FAILED_AUTHENTICATION); } static void _testBadPassword() throws Exception { TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); String header = getAuthHeader(BASIC, values); assertEquals(BASIC + " realm=\"Test Realm\"", header); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL()); get.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString("userOne:badPassword".getBytes(), false)); result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } } ClientCertRenegotiationTestCase.java000066400000000000000000000136721420065311100345610ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.security; import java.util.Collections; import java.util.List; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.SecurityNotification.EventType; import io.undertow.security.impl.ClientCertAuthenticationMechanism; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.ProxyIgnore; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.OptionMap; import io.undertow.connector.PooledByteBuffer; import javax.net.ssl.SSLContext; import static org.junit.Assert.assertEquals; import static org.xnio.Options.SSL_CLIENT_AUTH_MODE; import static org.xnio.SslClientAuthMode.NOT_REQUESTED; /** * Test case covering the core of Client-Cert * * @author Darran Lofthouse * @author Stuart Douglas */ @RunWith(DefaultServer.class) @ProxyIgnore public class ClientCertRenegotiationTestCase extends AuthenticationTestBase { private static SSLContext clientSSLContext; @Override protected List getTestMechanisms() { AuthenticationMechanism mechanism = new ClientCertAuthenticationMechanism(); return Collections.singletonList(mechanism); } @BeforeClass public static void startSSL() throws Exception { DefaultServer.startSSLServer(OptionMap.create(SSL_CLIENT_AUTH_MODE, NOT_REQUESTED)); clientSSLContext = DefaultServer.getClientSSLContext(); } @AfterClass public static void stopSSL() throws Exception { clientSSLContext = null; DefaultServer.stopSSLServer(); } @Test public void testClientCertSuccess() throws Exception { TestHttpClient client = new TestHttpClient(); client.setSSLContext(clientSSLContext); HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress()); HttpResponse result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders("ProcessedBy"); assertEquals("ProcessedBy Headers", 1, values.length); assertEquals("ResponseHandler", values[0].getValue()); values = result.getHeaders("AuthenticatedUser"); assertEquals("AuthenticatedUser Headers", 1, values.length); assertEquals("CN=Test Client,OU=OU,O=Org,L=City,ST=State,C=GB", values[0].getValue()); HttpClientUtils.readResponse(result); assertSingleNotificationType(EventType.AUTHENTICATED); } @Test public void testClientCertSuccessWithPostBody() throws Exception { TestHttpClient client = new TestHttpClient(); try { client.setSSLContext(clientSSLContext); HttpPost post = new HttpPost(DefaultServer.getDefaultServerSSLAddress()); post.setEntity(new StringEntity("hi")); HttpResponse result = client.execute(post); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders("ProcessedBy"); assertEquals("ProcessedBy Headers", 1, values.length); assertEquals("ResponseHandler", values[0].getValue()); values = result.getHeaders("AuthenticatedUser"); assertEquals("AuthenticatedUser Headers", 1, values.length); assertEquals("CN=Test Client,OU=OU,O=Org,L=City,ST=State,C=GB", values[0].getValue()); HttpClientUtils.readResponse(result); assertSingleNotificationType(EventType.AUTHENTICATED); } finally { client.getConnectionManager().shutdown(); } } @Test public void testClientCertSuccessWithLargePostBody() throws Exception { PooledByteBuffer buf = DefaultServer.getBufferPool().allocate(); int requestSize = buf.getBuffer().limit() - 1; buf.close(); final StringBuilder messageBuilder = new StringBuilder(requestSize); for (int i = 0; i < requestSize; ++i) { messageBuilder.append("*"); } TestHttpClient client = new TestHttpClient(); client.setSSLContext(clientSSLContext); HttpPost post = new HttpPost(DefaultServer.getDefaultServerSSLAddress()); post.setEntity(new StringEntity(messageBuilder.toString())); HttpResponse result = client.execute(post); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders("ProcessedBy"); assertEquals("ProcessedBy Headers", 1, values.length); assertEquals("ResponseHandler", values[0].getValue()); values = result.getHeaders("AuthenticatedUser"); assertEquals("AuthenticatedUser Headers", 1, values.length); assertEquals("CN=Test Client,OU=OU,O=Org,L=City,ST=State,C=GB", values[0].getValue()); HttpClientUtils.readResponse(result); assertSingleNotificationType(EventType.AUTHENTICATED); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/security/ClientCertTestCase.java000066400000000000000000000062161420065311100321040ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.security; import java.util.Collections; import java.util.List; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.SecurityNotification.EventType; import io.undertow.security.impl.ClientCertAuthenticationMechanism; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.net.ssl.SSLContext; import static org.junit.Assert.assertEquals; /** * Test case covering the core of Client-Cert * * @author Darran Lofthouse */ @RunWith(DefaultServer.class) public class ClientCertTestCase extends AuthenticationTestBase { private static SSLContext clientSSLContext; @Override protected List getTestMechanisms() { AuthenticationMechanism mechanism = new ClientCertAuthenticationMechanism(); return Collections.singletonList(mechanism); } @BeforeClass public static void startSSL() throws Exception { DefaultServer.startSSLServer(); clientSSLContext = DefaultServer.getClientSSLContext(); } @AfterClass public static void stopSSL() throws Exception { clientSSLContext = null; DefaultServer.stopSSLServer(); } @Test public void testClientCertSuccess() throws Exception { TestHttpClient client = new TestHttpClient(); client.setSSLContext(clientSSLContext); HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress()); HttpResponse result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders("ProcessedBy"); assertEquals("ProcessedBy Headers", 1, values.length); assertEquals("ResponseHandler", values[0].getValue()); values = result.getHeaders("AuthenticatedUser"); assertEquals("AuthenticatedUser Headers", 1, values.length); assertEquals("CN=Test Client,OU=OU,O=Org,L=City,ST=State,C=GB", values[0].getValue()); HttpClientUtils.readResponse(result); assertSingleNotificationType(EventType.AUTHENTICATED); } } DigestAuthentication2069TestCase.java000066400000000000000000000507031420065311100344310ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.security; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.SecurityNotification.EventType; import io.undertow.security.idm.DigestAlgorithm; import io.undertow.security.impl.AuthenticationInfoToken; import io.undertow.security.impl.DigestAuthenticationMechanism; import io.undertow.security.impl.DigestAuthorizationToken; import io.undertow.security.impl.DigestQop; import io.undertow.security.impl.DigestWWWAuthenticateToken; import io.undertow.security.impl.SimpleNonceManager; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.TestHttpClient; import io.undertow.util.HexConverter; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Test; import org.junit.runner.RunWith; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.Collections; import java.util.List; import java.util.Map; import static io.undertow.util.Headers.AUTHORIZATION; import static io.undertow.util.Headers.DIGEST; import static io.undertow.util.Headers.WWW_AUTHENTICATE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** * For Digest authentication we support RFC2617, however this includes a requirement to allow a fall back to RFC2069, this test * case is to test the RFC2069 form of Digest authentication. * * @author Darran Lofthouse */ @RunWith(DefaultServer.class) public class DigestAuthentication2069TestCase extends AuthenticationTestBase { private static final Charset UTF_8 = StandardCharsets.UTF_8; private static final String REALM_NAME = "Digest_Realm"; @Override protected List getTestMechanisms() { List qopList = Collections.emptyList(); AuthenticationMechanism mechanism = new DigestAuthenticationMechanism(Collections.singletonList(DigestAlgorithm.MD5), qopList, REALM_NAME, "/", new SimpleNonceManager()); return Collections.singletonList(mechanism); } /** * Creates a response value from the supplied parameters. * * @return The generated Hex encoded MD5 digest based response. */ private String createResponse(final String userName, final String realm, final String password, final String method, final String uri, final String nonce) throws Exception { MessageDigest digest = MessageDigest.getInstance("MD5"); digest.update(userName.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(realm.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(password.getBytes(UTF_8)); byte[] ha1 = HexConverter.convertToHexBytes(digest.digest()); digest.update(method.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(uri.getBytes(UTF_8)); byte[] ha2 = HexConverter.convertToHexBytes(digest.digest()); digest.update(ha1); digest.update((byte) ':'); digest.update(nonce.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(ha2); return HexConverter.convertToHexString(digest.digest()); } /** * Test for a successful authentication. */ @Test public void testDigestSuccess() throws Exception { TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); assertEquals(1, values.length); String value = values[0].getValue(); assertTrue(value.startsWith(DIGEST.toString())); Map parsedHeader = DigestWWWAuthenticateToken.parseHeader(value.substring(7)); assertEquals(REALM_NAME, parsedHeader.get(DigestWWWAuthenticateToken.REALM)); assertEquals(DigestAlgorithm.MD5.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.ALGORITHM)); assertFalse(parsedHeader.containsKey(DigestWWWAuthenticateToken.MESSAGE_QOP)); String nonce = parsedHeader.get(DigestWWWAuthenticateToken.NONCE); String response = createResponse("userOne", REALM_NAME, "passwordOne", "GET", "/", nonce); client = new TestHttpClient(); get = new HttpGet(DefaultServer.getDefaultServerURL()); StringBuilder sb = new StringBuilder(DIGEST.toString()); sb.append(" "); sb.append(DigestAuthorizationToken.USERNAME.getName()).append("=").append("\"userOne\"").append(","); sb.append(DigestAuthorizationToken.REALM.getName()).append("=\"").append(REALM_NAME).append("\","); sb.append(DigestAuthorizationToken.NONCE.getName()).append("=\"").append(nonce).append("\","); sb.append(DigestAuthorizationToken.DIGEST_URI.getName()).append("=\"/\","); sb.append(DigestAuthorizationToken.RESPONSE.getName()).append("=\"").append(response).append("\""); get.addHeader(AUTHORIZATION.toString(), sb.toString()); result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); values = result.getHeaders("ProcessedBy"); assertEquals(1, values.length); assertEquals("ResponseHandler", values[0].getValue()); values = result.getHeaders("Authentication-Info"); assertEquals(1, values.length); Map parsedAuthInfo = AuthenticationInfoToken.parseHeader(values[0].getValue()); nonce = parsedAuthInfo.get(AuthenticationInfoToken.NEXT_NONCE); response = createResponse("userOne", REALM_NAME, "passwordOne", "GET", "/", nonce); assertSingleNotificationType(EventType.AUTHENTICATED); client = new TestHttpClient(); get = new HttpGet(DefaultServer.getDefaultServerURL()); sb = new StringBuilder(DIGEST.toString()); sb.append(" "); sb.append(DigestAuthorizationToken.USERNAME.getName()).append("=").append("\"userOne\"").append(","); sb.append(DigestAuthorizationToken.REALM.getName()).append("=\"").append(REALM_NAME).append("\","); sb.append(DigestAuthorizationToken.NONCE.getName()).append("=\"").append(nonce).append("\","); sb.append(DigestAuthorizationToken.DIGEST_URI.getName()).append("=\"/\","); sb.append(DigestAuthorizationToken.RESPONSE.getName()).append("=\"").append(response).append("\""); get.addHeader(AUTHORIZATION.toString(), sb.toString()); result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); values = result.getHeaders("ProcessedBy"); assertEquals(1, values.length); assertEquals("ResponseHandler", values[0].getValue()); assertSingleNotificationType(EventType.AUTHENTICATED); } /** * Test that a request is correctly rejected with a bad user name. * * In this case both the supplied username is wrong and also the generated response can not be valid as there is no * corresponding user. */ @Test public void testBadUserName() throws Exception { TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); assertEquals(1, values.length); String value = values[0].getValue(); assertTrue(value.startsWith(DIGEST.toString())); Map parsedHeader = DigestWWWAuthenticateToken.parseHeader(value.substring(7)); assertEquals(REALM_NAME, parsedHeader.get(DigestWWWAuthenticateToken.REALM)); assertEquals(DigestAlgorithm.MD5.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.ALGORITHM)); String nonce = parsedHeader.get(DigestWWWAuthenticateToken.NONCE); String response = createResponse("badUser", REALM_NAME, "passwordOne", "GET", "/", nonce); client = new TestHttpClient(); get = new HttpGet(DefaultServer.getDefaultServerURL()); StringBuilder sb = new StringBuilder(DIGEST.toString()); sb.append(" "); sb.append(DigestAuthorizationToken.USERNAME.getName()).append("=").append("\"badUser\"").append(","); sb.append(DigestAuthorizationToken.REALM.getName()).append("=\"").append(REALM_NAME).append("\","); sb.append(DigestAuthorizationToken.NONCE.getName()).append("=\"").append(nonce).append("\","); sb.append(DigestAuthorizationToken.DIGEST_URI.getName()).append("=\"/\","); sb.append(DigestAuthorizationToken.RESPONSE.getName()).append("=\"").append(response).append("\""); get.addHeader(AUTHORIZATION.toString(), sb.toString()); result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); assertSingleNotificationType(EventType.FAILED_AUTHENTICATION); } /** * Test that a request is correctly rejected if a bad password is used to generate the response value. */ @Test public void testBadPassword() throws Exception { TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); assertEquals(1, values.length); String value = values[0].getValue(); assertTrue(value.startsWith(DIGEST.toString())); Map parsedHeader = DigestWWWAuthenticateToken.parseHeader(value.substring(7)); assertEquals(REALM_NAME, parsedHeader.get(DigestWWWAuthenticateToken.REALM)); assertEquals(DigestAlgorithm.MD5.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.ALGORITHM)); String nonce = parsedHeader.get(DigestWWWAuthenticateToken.NONCE); String response = createResponse("userOne", REALM_NAME, "badPassword", "GET", "/", nonce); client = new TestHttpClient(); get = new HttpGet(DefaultServer.getDefaultServerURL()); StringBuilder sb = new StringBuilder(DIGEST.toString()); sb.append(" "); sb.append(DigestAuthorizationToken.USERNAME.getName()).append("=").append("\"userOne\"").append(","); sb.append(DigestAuthorizationToken.REALM.getName()).append("=\"").append(REALM_NAME).append("\","); sb.append(DigestAuthorizationToken.NONCE.getName()).append("=\"").append(nonce).append("\","); sb.append(DigestAuthorizationToken.DIGEST_URI.getName()).append("=\"/\","); sb.append(DigestAuthorizationToken.RESPONSE.getName()).append("=\"").append(response).append("\""); get.addHeader(AUTHORIZATION.toString(), sb.toString()); result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); assertSingleNotificationType(EventType.FAILED_AUTHENTICATION); } /** * Test that for a valid username and password if an invalid nonce is used the request should be rejected with the nonce * marked as stale, using the replacement nonce should then work. */ @Test public void testDifferentNonce() throws Exception { TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); assertEquals(1, values.length); String value = values[0].getValue(); assertTrue(value.startsWith(DIGEST.toString())); Map parsedHeader = DigestWWWAuthenticateToken.parseHeader(value.substring(7)); assertEquals(REALM_NAME, parsedHeader.get(DigestWWWAuthenticateToken.REALM)); assertEquals(DigestAlgorithm.MD5.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.ALGORITHM)); String nonce = "AU1aCIiy48ENMTM1MTE3OTUxMDU2OLrHnBlV2GBzzguCWOPET+0="; String response = createResponse("userOne", REALM_NAME, "passwordOne", "GET", "/", nonce); client = new TestHttpClient(); get = new HttpGet(DefaultServer.getDefaultServerURL()); StringBuilder sb = new StringBuilder(DIGEST.toString()); sb.append(" "); sb.append(DigestAuthorizationToken.USERNAME.getName()).append("=").append("\"userOne\"").append(","); sb.append(DigestAuthorizationToken.REALM.getName()).append("=\"").append(REALM_NAME).append("\","); sb.append(DigestAuthorizationToken.NONCE.getName()).append("=\"").append(nonce).append("\","); sb.append(DigestAuthorizationToken.DIGEST_URI.getName()).append("=\"/\","); sb.append(DigestAuthorizationToken.RESPONSE.getName()).append("=\"").append(response).append("\""); get.addHeader(AUTHORIZATION.toString(), sb.toString()); result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); values = result.getHeaders(WWW_AUTHENTICATE.toString()); assertEquals(1, values.length); value = values[0].getValue(); assertTrue(value.startsWith(DIGEST.toString())); parsedHeader = DigestWWWAuthenticateToken.parseHeader(value.substring(7)); assertEquals(REALM_NAME, parsedHeader.get(DigestWWWAuthenticateToken.REALM)); assertEquals(DigestAlgorithm.MD5.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.ALGORITHM)); assertEquals("true", parsedHeader.get(DigestWWWAuthenticateToken.STALE)); nonce = parsedHeader.get(DigestWWWAuthenticateToken.NONCE); response = createResponse("userOne", REALM_NAME, "passwordOne", "GET", "/", nonce); client = new TestHttpClient(); get = new HttpGet(DefaultServer.getDefaultServerURL()); sb = new StringBuilder(DIGEST.toString()); sb.append(" "); sb.append(DigestAuthorizationToken.USERNAME.getName()).append("=").append("\"userOne\"").append(","); sb.append(DigestAuthorizationToken.REALM.getName()).append("=\"").append(REALM_NAME).append("\","); sb.append(DigestAuthorizationToken.NONCE.getName()).append("=\"").append(nonce).append("\","); sb.append(DigestAuthorizationToken.DIGEST_URI.getName()).append("=\"/\","); sb.append(DigestAuthorizationToken.RESPONSE.getName()).append("=\"").append(response).append("\""); get.addHeader(AUTHORIZATION.toString(), sb.toString()); result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); values = result.getHeaders("ProcessedBy"); assertEquals(1, values.length); assertEquals("ResponseHandler", values[0].getValue()); // The additional round trip for the bad nonce should not trigger a security notification. assertSingleNotificationType(EventType.AUTHENTICATED); } /** * Test that in RFC2069 mode nonce re-use is rejected. */ @Test public void testNonceReUse() throws Exception { TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); assertEquals(1, values.length); String value = values[0].getValue(); assertTrue(value.startsWith(DIGEST.toString())); Map parsedHeader = DigestWWWAuthenticateToken.parseHeader(value.substring(7)); assertEquals(REALM_NAME, parsedHeader.get(DigestWWWAuthenticateToken.REALM)); assertEquals(DigestAlgorithm.MD5.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.ALGORITHM)); String nonce = parsedHeader.get(DigestWWWAuthenticateToken.NONCE); String response = createResponse("userOne", REALM_NAME, "passwordOne", "GET", "/", nonce); client = new TestHttpClient(); get = new HttpGet(DefaultServer.getDefaultServerURL()); StringBuilder sb = new StringBuilder(DIGEST.toString()); sb.append(" "); sb.append(DigestAuthorizationToken.USERNAME.getName()).append("=").append("\"userOne\"").append(","); sb.append(DigestAuthorizationToken.REALM.getName()).append("=\"").append(REALM_NAME).append("\","); sb.append(DigestAuthorizationToken.NONCE.getName()).append("=\"").append(nonce).append("\","); sb.append(DigestAuthorizationToken.DIGEST_URI.getName()).append("=\"/\","); sb.append(DigestAuthorizationToken.RESPONSE.getName()).append("=\"").append(response).append("\""); get.addHeader(AUTHORIZATION.toString(), sb.toString()); result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); values = result.getHeaders("ProcessedBy"); assertEquals(1, values.length); assertEquals("ResponseHandler", values[0].getValue()); assertSingleNotificationType(EventType.AUTHENTICATED); client = new TestHttpClient(); get = new HttpGet(DefaultServer.getDefaultServerURL()); get.addHeader(AUTHORIZATION.toString(), sb.toString()); result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); values = result.getHeaders(WWW_AUTHENTICATE.toString()); assertEquals(1, values.length); value = values[0].getValue(); assertTrue(value.startsWith(DIGEST.toString())); parsedHeader = DigestWWWAuthenticateToken.parseHeader(value.substring(7)); assertEquals(REALM_NAME, parsedHeader.get(DigestWWWAuthenticateToken.REALM)); assertEquals(DigestAlgorithm.MD5.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.ALGORITHM)); assertEquals("true", parsedHeader.get(DigestWWWAuthenticateToken.STALE)); nonce = parsedHeader.get(DigestWWWAuthenticateToken.NONCE); response = createResponse("userOne", REALM_NAME, "passwordOne", "GET", "/", nonce); client = new TestHttpClient(); get = new HttpGet(DefaultServer.getDefaultServerURL()); sb = new StringBuilder(DIGEST.toString()); sb.append(" "); sb.append(DigestAuthorizationToken.USERNAME.getName()).append("=").append("\"userOne\"").append(","); sb.append(DigestAuthorizationToken.REALM.getName()).append("=\"").append(REALM_NAME).append("\","); sb.append(DigestAuthorizationToken.NONCE.getName()).append("=\"").append(nonce).append("\","); sb.append(DigestAuthorizationToken.DIGEST_URI.getName()).append("=\"/\","); sb.append(DigestAuthorizationToken.RESPONSE.getName()).append("=\"").append(response).append("\""); get.addHeader(AUTHORIZATION.toString(), sb.toString()); result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); values = result.getHeaders("ProcessedBy"); assertEquals(1, values.length); assertEquals("ResponseHandler", values[0].getValue()); // The additional round trip for the bad nonce should not trigger a security notification. assertSingleNotificationType(EventType.AUTHENTICATED); } // Test choosing different algorithm. // Different URI - Test not matching the request as well. // Different Method } DigestAuthenticationAuthTestCase.java000066400000000000000000000534671420065311100347440ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.security; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.SecurityNotification.EventType; import io.undertow.security.idm.DigestAlgorithm; import io.undertow.security.impl.AuthenticationInfoToken; import io.undertow.security.impl.DigestAuthenticationMechanism; import io.undertow.security.impl.DigestAuthorizationToken; import io.undertow.security.impl.DigestQop; import io.undertow.security.impl.DigestWWWAuthenticateToken; import io.undertow.security.impl.SimpleNonceManager; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.TestHttpClient; import io.undertow.util.HexConverter; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Test; import org.junit.runner.RunWith; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Random; import static io.undertow.util.Headers.AUTHORIZATION; import static io.undertow.util.Headers.DIGEST; import static io.undertow.util.Headers.WWW_AUTHENTICATE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; /** * Test case for Digest authentication based on RFC2617 with QOP of auth. * * @author Darran Lofthouse */ @RunWith(DefaultServer.class) public class DigestAuthenticationAuthTestCase extends AuthenticationTestBase { private static final Charset UTF_8 = StandardCharsets.UTF_8; private static final String REALM_NAME = "Digest_Realm"; private static final String ZERO = "00000000"; static AuthenticationMechanism getTestMechanism() { return new DigestAuthenticationMechanism(Collections.singletonList(DigestAlgorithm.MD5), Collections.singletonList(DigestQop.AUTH), REALM_NAME, "/", new SimpleNonceManager()); } /** * @see io.undertow.server.security.AuthenticationTestBase#getTestMechanisms() */ @Override protected List getTestMechanisms() { AuthenticationMechanism mechanism = getTestMechanism(); return Collections.singletonList(mechanism); } private static String createNonce() { // This if just for testing so we are not concerned with how securely the client side nonce is. Random rand = new Random(); byte[] nonceBytes = new byte[32]; rand.nextBytes(nonceBytes); return HexConverter.convertToHexString(nonceBytes); } /** * Creates a response value from the supplied parameters. * * @return The generated Hex encoded MD5 digest based response. */ private static String createResponse(final String userName, final String realm, final String password, final String method, final String uri, final String nonce, final String nonceCount, final String cnonce) throws Exception { MessageDigest digest = MessageDigest.getInstance("MD5"); digest.update(userName.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(realm.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(password.getBytes(UTF_8)); byte[] ha1 = HexConverter.convertToHexBytes(digest.digest()); digest.update(method.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(uri.getBytes(UTF_8)); byte[] ha2 = HexConverter.convertToHexBytes(digest.digest()); digest.update(ha1); digest.update((byte) ':'); digest.update(nonce.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(nonceCount.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(cnonce.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(DigestQop.AUTH.getToken().getBytes(UTF_8)); digest.update((byte) ':'); digest.update(ha2); return HexConverter.convertToHexString(digest.digest()); } private static String createRspAuth(final String userName, final String realm, final String password, final String uri, final String nonce, final String nonceCount, final String cnonce) throws Exception { MessageDigest digest = MessageDigest.getInstance("MD5"); digest.update(userName.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(realm.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(password.getBytes(UTF_8)); byte[] ha1 = HexConverter.convertToHexBytes(digest.digest()); digest.update((byte) ':'); digest.update(uri.getBytes(UTF_8)); byte[] ha2 = HexConverter.convertToHexBytes(digest.digest()); digest.update(ha1); digest.update((byte) ':'); digest.update(nonce.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(nonceCount.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(cnonce.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(DigestQop.AUTH.getToken().getBytes(UTF_8)); digest.update((byte) ':'); digest.update(ha2); return HexConverter.convertToHexString(digest.digest()); } private static String createAuthorizationLine(final String userName, final String password, final String method, final String uri, final String nonce, final int nonceCount, final String cnonce, final String opaque) throws Exception { StringBuilder sb = new StringBuilder(DIGEST.toString()); sb.append(" "); sb.append(DigestAuthorizationToken.USERNAME.getName()).append("=").append("\"userOne\"").append(","); sb.append(DigestAuthorizationToken.REALM.getName()).append("=\"").append(REALM_NAME).append("\","); sb.append(DigestAuthorizationToken.NONCE.getName()).append("=\"").append(nonce).append("\","); sb.append(DigestAuthorizationToken.DIGEST_URI.getName()).append("=\"" + uri + "\","); String nonceCountHex = toHex(nonceCount); String response = createResponse(userName, REALM_NAME, password, method, uri, nonce, nonceCountHex, cnonce); sb.append(DigestAuthorizationToken.RESPONSE.getName()).append("=\"").append(response).append("\","); sb.append(DigestAuthorizationToken.ALGORITHM.getName()).append("=\"").append(DigestAlgorithm.MD5.getToken()) .append("\","); sb.append(DigestAuthorizationToken.CNONCE.getName()).append("=\"").append(cnonce).append("\","); sb.append(DigestAuthorizationToken.OPAQUE.getName()).append("=\"").append(opaque).append("\","); sb.append(DigestAuthorizationToken.MESSAGE_QOP.getName()).append("=\"").append(DigestQop.AUTH.getToken()).append("\","); sb.append(DigestAuthorizationToken.NONCE_COUNT.getName()).append("=").append(nonceCountHex); return sb.toString(); } private static String toHex(final int number) { String temp = Integer.toHexString(number); return ZERO.substring(temp.length()) + temp; } /** * Test for a successful authentication. * * Also makes two additional calls to demonstrate nonce re-use with an incrementing nonce count. */ @Test public void testDigestSuccess() throws Exception { _testDigestSuccess(); } static void _testDigestSuccess() throws Exception { TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); String value = getAuthHeader(DIGEST, values); Map parsedHeader = DigestWWWAuthenticateToken.parseHeader(value.substring(7)); assertEquals(REALM_NAME, parsedHeader.get(DigestWWWAuthenticateToken.REALM)); assertEquals(DigestAlgorithm.MD5.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.ALGORITHM)); assertEquals(DigestQop.AUTH.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.MESSAGE_QOP)); String clientNonce = createNonce(); int nonceCount = 1; String nonce = parsedHeader.get(DigestWWWAuthenticateToken.NONCE); String opaque = parsedHeader.get(DigestWWWAuthenticateToken.OPAQUE); assertNotNull(opaque); // Send 5 requests with an incrementing nonce count on each call. for (int i = 0; i < 5; i++) { client = new TestHttpClient(); get = new HttpGet(DefaultServer.getDefaultServerURL()); int thisNonceCount = nonceCount++; String authorization = createAuthorizationLine("userOne", "passwordOne", "GET", "/", nonce, thisNonceCount, clientNonce, opaque); get.addHeader(AUTHORIZATION.toString(), authorization); result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); values = result.getHeaders("ProcessedBy"); assertEquals(1, values.length); assertEquals("ResponseHandler", values[0].getValue()); assertSingleNotificationType(EventType.AUTHENTICATED); values = result.getHeaders("Authentication-Info"); assertEquals(1, values.length); Map parsedAuthInfo = AuthenticationInfoToken.parseHeader(values[0].getValue()); assertEquals("Didn't expect a new nonce.", nonce, parsedAuthInfo.get(AuthenticationInfoToken.NEXT_NONCE)); assertEquals(DigestQop.AUTH.getToken(), parsedAuthInfo.get(AuthenticationInfoToken.MESSAGE_QOP)); String nonceCountString = toHex(thisNonceCount); assertEquals(createRspAuth("userOne", REALM_NAME, "passwordOne", "/", nonce, nonceCountString, clientNonce), parsedAuthInfo.get(AuthenticationInfoToken.RESPONSE_AUTH)); assertEquals(clientNonce, parsedAuthInfo.get(AuthenticationInfoToken.CNONCE)); assertEquals(nonceCountString, parsedAuthInfo.get(AuthenticationInfoToken.NONCE_COUNT)); } } /** * Test for a successful authentication. * * Also makes two additional calls to demonstrate nonce re-use with an incrementing nonce count. */ @Test public void testDigestBadUri() throws Exception { _testDigestBadUri(); } static void _testDigestBadUri() throws Exception { TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); String value = getAuthHeader(DIGEST, values); Map parsedHeader = DigestWWWAuthenticateToken.parseHeader(value.substring(7)); assertEquals(REALM_NAME, parsedHeader.get(DigestWWWAuthenticateToken.REALM)); assertEquals(DigestAlgorithm.MD5.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.ALGORITHM)); assertEquals(DigestQop.AUTH.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.MESSAGE_QOP)); String clientNonce = createNonce(); int nonceCount = 1; String nonce = parsedHeader.get(DigestWWWAuthenticateToken.NONCE); String opaque = parsedHeader.get(DigestWWWAuthenticateToken.OPAQUE); assertNotNull(opaque); // Send 5 requests with an incrementing nonce count on each call. for (int i = 0; i < 5; i++) { client = new TestHttpClient(); get = new HttpGet(DefaultServer.getDefaultServerURL()); int thisNonceCount = nonceCount++; String authorization = createAuthorizationLine("userOne", "passwordOne", "GET", "/badUri", nonce, thisNonceCount, clientNonce, opaque); get.addHeader(AUTHORIZATION.toString(), authorization); result = client.execute(get); assertEquals(StatusCodes.BAD_REQUEST, result.getStatusLine().getStatusCode()); } } /** * Test for a failed authentication where a bad username is provided. */ @Test public void testBadUsername() throws Exception { _testBadUsername(); } static void _testBadUsername() throws Exception { TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); String value = getAuthHeader(DIGEST, values); Map parsedHeader = DigestWWWAuthenticateToken.parseHeader(value.substring(7)); assertEquals(REALM_NAME, parsedHeader.get(DigestWWWAuthenticateToken.REALM)); assertEquals(DigestAlgorithm.MD5.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.ALGORITHM)); assertEquals(DigestQop.AUTH.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.MESSAGE_QOP)); String clientNonce = createNonce(); int nonceCount = 1; String nonce = parsedHeader.get(DigestWWWAuthenticateToken.NONCE); String opaque = parsedHeader.get(DigestWWWAuthenticateToken.OPAQUE); assertNotNull(opaque); client = new TestHttpClient(); get = new HttpGet(DefaultServer.getDefaultServerURL()); int thisNonceCount = nonceCount++; String authorization = createAuthorizationLine("noUser", "passwordOne", "GET", "/", nonce, thisNonceCount, clientNonce, opaque); get.addHeader(AUTHORIZATION.toString(), authorization); result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); assertSingleNotificationType(EventType.FAILED_AUTHENTICATION); } /** * Test for a failed authentication where a bad password is provided. */ @Test public void testBadPassword() throws Exception { _testBadPassword(); } static void _testBadPassword() throws Exception { TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); String value = getAuthHeader(DIGEST, values); Map parsedHeader = DigestWWWAuthenticateToken.parseHeader(value.substring(7)); assertEquals(REALM_NAME, parsedHeader.get(DigestWWWAuthenticateToken.REALM)); assertEquals(DigestAlgorithm.MD5.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.ALGORITHM)); assertEquals(DigestQop.AUTH.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.MESSAGE_QOP)); String clientNonce = createNonce(); int nonceCount = 1; String nonce = parsedHeader.get(DigestWWWAuthenticateToken.NONCE); String opaque = parsedHeader.get(DigestWWWAuthenticateToken.OPAQUE); assertNotNull(opaque); client = new TestHttpClient(); get = new HttpGet(DefaultServer.getDefaultServerURL()); int thisNonceCount = nonceCount++; String authorization = createAuthorizationLine("userOne", "badPassword", "GET", "/", nonce, thisNonceCount, clientNonce, opaque); get.addHeader(AUTHORIZATION.toString(), authorization); result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); assertSingleNotificationType(EventType.FAILED_AUTHENTICATION); } /** * Test for a failed authentication where a bad nonce is provided. */ @Test public void testBadNonce() throws Exception { _testBadNonce(); } static void _testBadNonce() throws Exception { TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); String value = getAuthHeader(DIGEST, values); Map parsedHeader = DigestWWWAuthenticateToken.parseHeader(value.substring(7)); assertEquals(REALM_NAME, parsedHeader.get(DigestWWWAuthenticateToken.REALM)); assertEquals(DigestAlgorithm.MD5.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.ALGORITHM)); assertEquals(DigestQop.AUTH.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.MESSAGE_QOP)); String clientNonce = createNonce(); int nonceCount = 1; String nonce = "AU1aCIiy48ENMTM1MTE3OTUxMDU2OLrHnBlV2GBzzguCWOPET+0="; String opaque = parsedHeader.get(DigestWWWAuthenticateToken.OPAQUE); assertNotNull(opaque); client = new TestHttpClient(); get = new HttpGet(DefaultServer.getDefaultServerURL()); int thisNonceCount = nonceCount++; String authorization = createAuthorizationLine("userOne", "badPassword", "GET", "/", nonce, thisNonceCount, clientNonce, opaque); get.addHeader(AUTHORIZATION.toString(), authorization); result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); assertSingleNotificationType(EventType.FAILED_AUTHENTICATION); } /** * Test for a failed authentication where the nonce count is re-used. * * Where a nonce count is used the nonce can now be re-used, however each time the nonce count must be different. */ @Test public void testNonceCountReUse() throws Exception { _testNonceCountReUse(); } static void _testNonceCountReUse() throws Exception { TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); String value = getAuthHeader(DIGEST, values); Map parsedHeader = DigestWWWAuthenticateToken.parseHeader(value.substring(7)); assertEquals(REALM_NAME, parsedHeader.get(DigestWWWAuthenticateToken.REALM)); assertEquals(DigestAlgorithm.MD5.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.ALGORITHM)); assertEquals(DigestQop.AUTH.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.MESSAGE_QOP)); String clientNonce = createNonce(); int nonceCount = 1; String nonce = parsedHeader.get(DigestWWWAuthenticateToken.NONCE); String opaque = parsedHeader.get(DigestWWWAuthenticateToken.OPAQUE); assertNotNull(opaque); // Send 5 requests with an incrementing nonce count on each call. for (int i = 0; i < 2; i++) { client = new TestHttpClient(); get = new HttpGet(DefaultServer.getDefaultServerURL()); int thisNonceCount = nonceCount; // Note - No increment String authorization = createAuthorizationLine("userOne", "passwordOne", "GET", "/", nonce, thisNonceCount, clientNonce, opaque); get.addHeader(AUTHORIZATION.toString(), authorization); result = client.execute(get); if (i == 0) { assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); assertSingleNotificationType(EventType.AUTHENTICATED); values = result.getHeaders("ProcessedBy"); assertEquals(1, values.length); assertEquals("ResponseHandler", values[0].getValue()); values = result.getHeaders("Authentication-Info"); assertEquals(1, values.length); Map parsedAuthInfo = AuthenticationInfoToken.parseHeader(values[0].getValue()); assertEquals("Didn't expect a new nonce.", nonce, parsedAuthInfo.get(AuthenticationInfoToken.NEXT_NONCE)); assertEquals(DigestQop.AUTH.getToken(), parsedAuthInfo.get(AuthenticationInfoToken.MESSAGE_QOP)); String nonceCountString = toHex(thisNonceCount); assertEquals(createRspAuth("userOne", REALM_NAME, "passwordOne", "/", nonce, nonceCountString, clientNonce), parsedAuthInfo.get(AuthenticationInfoToken.RESPONSE_AUTH)); assertEquals(clientNonce, parsedAuthInfo.get(AuthenticationInfoToken.CNONCE)); assertEquals(nonceCountString, parsedAuthInfo.get(AuthenticationInfoToken.NONCE_COUNT)); } else { assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); } } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/security/FormAuthTestCase.java000066400000000000000000000133271420065311100315760ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.security; import io.undertow.predicate.Predicates; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.impl.CachedAuthenticatedSessionMechanism; import io.undertow.security.impl.FormAuthenticationMechanism; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.PredicateHandler; import io.undertow.server.session.InMemorySessionManager; import io.undertow.server.session.SessionAttachmentHandler; import io.undertow.server.session.SessionCookieConfig; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.ProtocolException; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultRedirectStrategy; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HttpContext; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class FormAuthTestCase extends AuthenticationTestBase { public static final String HELLO_WORLD = "Hello World"; @Override protected void setRootHandler(HttpHandler current) { final PredicateHandler handler = new PredicateHandler(Predicates.path("/login"), new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseSender().send("Login Page"); } }, current); super.setRootHandler(new SessionAttachmentHandler(handler, new InMemorySessionManager("test"), new SessionCookieConfig())); } protected boolean cachingRequired() { return true; } @Test public void testFormAuth() throws IOException { TestHttpClient client = new TestHttpClient(); client.setRedirectStrategy(new DefaultRedirectStrategy() { @Override public boolean isRedirected(final HttpRequest request, final HttpResponse response, final HttpContext context) throws ProtocolException { Header[] locationHeaders = response.getHeaders("Location"); if (locationHeaders != null && locationHeaders.length > 0) { for (Header locationHeader : locationHeaders) { assertFalse("Location header incorrectly computed resulting in wrong request URI upon redirect, " + "failed probably due UNDERTOW-884", locationHeader.getValue().startsWith(DefaultServer.getDefaultServerURL() + DefaultServer.getDefaultServerURL())); } } if (response.getStatusLine().getStatusCode() == StatusCodes.FOUND) { return true; } return super.isRedirected(request, response, context); } }); try { final String uri = DefaultServer.getDefaultServerURL() + "/secured/test"; HttpGet get = new HttpGet(uri); HttpResponse result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("Login Page", response); BasicNameValuePair[] pairs = new BasicNameValuePair[]{new BasicNameValuePair("j_username", "userOne"), new BasicNameValuePair("j_password", "passwordOne")}; final List data = new ArrayList<>(); data.addAll(Arrays.asList(pairs)); HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/j_security_check;jsessionid=dsjahfklsahdfjklsa"); post.setEntity(new UrlEncodedFormEntity(data)); result = client.execute(post); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders("ProcessedBy"); assertEquals(1, values.length); assertEquals("ResponseHandler", values[0].getValue()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } @Override protected List getTestMechanisms() { List ret = new ArrayList<>(); ret.add(new CachedAuthenticatedSessionMechanism()); ret.add(new FormAuthenticationMechanism("test", "/login", "/error")); return ret; } } GenericHeaderAuthenticationTestCase.java000066400000000000000000000115651420065311100353610ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.security; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.SecurityNotification.EventType; import io.undertow.security.impl.GenericHeaderAuthenticationMechanism; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.HttpString; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Collections; import java.util.List; import static io.undertow.security.impl.GenericHeaderAuthenticationMechanism.NAME; import static org.junit.Assert.assertEquals; /** * A test case to test when the only authentication mechanism is the GENERIC_HEADER mechanism. * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class GenericHeaderAuthenticationTestCase extends AuthenticationTestBase { static AuthenticationMechanism getTestMechanism() { return new GenericHeaderAuthenticationMechanism(NAME, Collections.singletonList(new HttpString("user")), Collections.singletonList("sessionid"), identityManager); } @Override protected List getTestMechanisms() { AuthenticationMechanism mechanism = getTestMechanism(); return Collections.singletonList(mechanism); } @Test public void testGenericHeaderSucess() throws Exception { _testGenericHeaderSucess(); assertSingleNotificationType(EventType.AUTHENTICATED); } static void _testGenericHeaderSucess() throws Exception { TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); assertEquals(StatusCodes.FORBIDDEN, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL()); get.addHeader("user", "userOne"); get.addHeader("cookie", "sessionid=passwordOne"); result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders("ProcessedBy"); assertEquals(1, values.length); assertEquals("ResponseHandler", values[0].getValue()); HttpClientUtils.readResponse(result); } @Test public void testBadUserName() throws Exception { _testBadUserName(); assertSingleNotificationType(EventType.FAILED_AUTHENTICATION); } static void _testBadUserName() throws Exception { TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); assertEquals(StatusCodes.FORBIDDEN, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL()); get.addHeader("user", "badUser"); get.addHeader("cookie", "sessionid=badPassword"); result = client.execute(get); assertEquals(StatusCodes.FORBIDDEN, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } @Test public void testBadPassword() throws Exception { _testBadPassword(); assertSingleNotificationType(EventType.FAILED_AUTHENTICATION); } static void _testBadPassword() throws Exception { TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); assertEquals(StatusCodes.FORBIDDEN, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL()); get.addHeader("user", "userOne"); get.addHeader("cookie", "sessionid=badPassword"); result = client.execute(get); assertEquals(StatusCodes.FORBIDDEN, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/security/KerberosKDCUtil.java000066400000000000000000000305241420065311100313470ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.security; import static javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag.REQUIRED; import io.undertow.testutils.DefaultServer; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; import java.util.HashMap; import java.util.Map; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.AppConfigurationEntry; import javax.security.auth.login.Configuration; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import org.apache.directory.api.ldap.model.entry.DefaultEntry; import org.apache.directory.api.ldap.model.ldif.LdifEntry; import org.apache.directory.api.ldap.model.ldif.LdifReader; import org.apache.directory.api.ldap.model.schema.SchemaManager; import org.apache.directory.server.core.api.CoreSession; import org.apache.directory.server.core.api.DirectoryService; import org.apache.directory.server.core.api.partition.Partition; import org.apache.directory.server.core.factory.DefaultDirectoryServiceFactory; import org.apache.directory.server.core.factory.DirectoryServiceFactory; import org.apache.directory.server.core.factory.PartitionFactory; import org.apache.directory.server.core.kerberos.KeyDerivationInterceptor; import org.apache.directory.server.kerberos.KerberosConfig; import org.apache.directory.server.kerberos.kdc.KdcServer; import org.apache.directory.server.ldap.LdapServer; import org.apache.directory.server.protocol.shared.transport.TcpTransport; import org.apache.directory.server.protocol.shared.transport.Transport; import org.apache.directory.server.protocol.shared.transport.UdpTransport; /** * Utility class to start up a test KDC backed by a directory server. * * It is better to start the server once instead of once per test but once running * the overhead is minimal. However a better solution may be to use the {@link Suite} * runner but we currently need to use the {@link DefaultServer} runner. * * TODO - May be able to add some lifecycle methods to DefaultServer to allow * for an extension. * * @author Darran Lofthouse */ class KerberosKDCUtil { private static final boolean IS_IBM = System.getProperty("java.vendor").contains("IBM"); static final int LDAP_PORT = 11389; static final int KDC_PORT = 6088; private static final String DIRECTORY_NAME = "Test Service"; private static boolean initialised; private static Path workingDir; /* * LDAP Related */ private static DirectoryService directoryService; private static LdapServer ldapServer; /* * KDC Related */ private static KdcServer kdcServer; public static boolean startServer() throws Exception { if (initialised) { return false; } setupEnvironment(); startLdapServer(); startKDC(); initialised = true; return true; } private static void startLdapServer() throws Exception { createWorkingDir(); DirectoryServiceFactory dsf = new DefaultDirectoryServiceFactory(); dsf.init(DIRECTORY_NAME); directoryService = dsf.getDirectoryService(); directoryService.addLast(new KeyDerivationInterceptor()); // Derives the Kerberos keys for new entries. directoryService.getChangeLog().setEnabled(false); SchemaManager schemaManager = directoryService.getSchemaManager(); createPartition(dsf, schemaManager, "users", "ou=users,dc=undertow,dc=io"); CoreSession adminSession = directoryService.getAdminSession(); Map mappings = Collections.singletonMap("hostname", DefaultServer.getDefaultServerAddress().getHostString()); processLdif(schemaManager, adminSession, "partition.ldif", mappings); processLdif(schemaManager, adminSession, "krbtgt.ldif", mappings); processLdif(schemaManager, adminSession, "user.ldif", mappings); processLdif(schemaManager, adminSession, "server.ldif", mappings); ldapServer = new LdapServer(); ldapServer.setServiceName("DefaultLDAP"); Transport ldap = new TcpTransport( "0.0.0.0", LDAP_PORT, 3, 5 ); ldapServer.addTransports(ldap); ldapServer.setDirectoryService(directoryService); ldapServer.start(); } private static void createPartition(final DirectoryServiceFactory dsf, final SchemaManager schemaManager, final String id, final String suffix) throws Exception { PartitionFactory pf = dsf.getPartitionFactory(); Partition p = pf.createPartition(schemaManager, id, suffix, 1000, workingDir.toFile()); pf.addIndex(p, "krb5PrincipalName", 10); p.initialize(); directoryService.addPartition(p); } private static void processLdif(final SchemaManager schemaManager, final CoreSession adminSession, final String ldifName, final Map mappings) throws Exception { InputStream resourceInput = KerberosKDCUtil.class.getResourceAsStream("/ldif/" + ldifName); ByteArrayOutputStream baos = new ByteArrayOutputStream(resourceInput.available()); int current; while ((current = resourceInput.read()) != -1) { if (current == '$') { // Enter String replacement mode. int second = resourceInput.read(); if (second == '{') { ByteArrayOutputStream substitute = new ByteArrayOutputStream(); while ((current = resourceInput.read()) != -1 && current != '}') { substitute.write(current); } if (current == -1) { baos.write(current); baos.write(second); baos.write(substitute.toByteArray()); // Terminator never found. } String toReplace = new String(substitute.toByteArray(), StandardCharsets.UTF_8); if (mappings.containsKey(toReplace)) { baos.write(mappings.get(toReplace).getBytes()); } else { throw new IllegalArgumentException(String.format("No mapping found for '%s'", toReplace)); } } else { baos.write(current); baos.write(second); } } else { baos.write(current); } } ByteArrayInputStream ldifInput = new ByteArrayInputStream(baos.toByteArray()); LdifReader ldifReader = new LdifReader(ldifInput); for (LdifEntry ldifEntry : ldifReader) { adminSession.add(new DefaultEntry(schemaManager, ldifEntry.getEntry())); } ldifReader.close(); ldifInput.close(); } private static void startKDC() throws Exception { kdcServer = new KdcServer(); kdcServer.setServiceName("Test KDC"); kdcServer.setSearchBaseDn("ou=users,dc=undertow,dc=io"); KerberosConfig config = kdcServer.getConfig(); config.setServicePrincipal("krbtgt/UNDERTOW.IO@UNDERTOW.IO"); config.setPrimaryRealm("UNDERTOW.IO"); config.setPaEncTimestampRequired(false); UdpTransport udp = new UdpTransport("0.0.0.0", KDC_PORT); kdcServer.addTransports(udp); kdcServer.setDirectoryService(directoryService); kdcServer.start(); } private static void setupEnvironment() { final URL configPath = KerberosKDCUtil.class.getResource("/krb5.conf"); try { System.setProperty("java.security.krb5.conf", Paths.get(configPath.toURI()).normalize().toAbsolutePath().toString()); } catch (URISyntaxException e) { throw new RuntimeException(e); } } private static void createWorkingDir() throws IOException { if (workingDir == null) { workingDir = Paths.get(".", "target", "apacheds_working"); if (!Files.exists(workingDir)) { Files.createDirectories(workingDir); } } try(DirectoryStream stream = Files.newDirectoryStream(workingDir)) { for(Path child : stream) { Files.delete(child); } } } static Subject login(final String userName, final char[] password) throws LoginException { Subject theSubject = new Subject(); CallbackHandler cbh = new UsernamePasswordCBH(userName, password); LoginContext lc = new LoginContext("KDC", theSubject, cbh, createJaasConfiguration()); lc.login(); return theSubject; } private static Configuration createJaasConfiguration() { return new Configuration() { @Override public AppConfigurationEntry[] getAppConfigurationEntry(String name) { if (!"KDC".equals(name)) { throw new IllegalArgumentException("Unexpected name '" + name + "'"); } AppConfigurationEntry[] entries = new AppConfigurationEntry[1]; Map options = new HashMap<>(); options.put("debug", "true"); options.put("refreshKrb5Config", "true"); if (IS_IBM) { options.put("noAddress", "true"); options.put("credsType", "both"); entries[0] = new AppConfigurationEntry("com.ibm.security.auth.module.Krb5LoginModule", REQUIRED, options); } else { options.put("storeKey", "true"); options.put("isInitiator", "true"); entries[0] = new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule", REQUIRED, options); } return entries; } }; } private static class UsernamePasswordCBH implements CallbackHandler { /* * Note: We use CallbackHandler implementations like this in test cases as test cases need to run unattended, a true * CallbackHandler implementation should interact directly with the current user to prompt for the username and * password. * * i.e. In a client app NEVER prompt for these values in advance and provide them to a CallbackHandler like this. */ private final String username; private final char[] password; private UsernamePasswordCBH(final String username, final char[] password) { this.username = username; this.password = password; } @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (Callback current : callbacks) { if (current instanceof NameCallback) { NameCallback ncb = (NameCallback) current; ncb.setName(username); } else if (current instanceof PasswordCallback) { PasswordCallback pcb = (PasswordCallback) current; pcb.setPassword(password); } else { throw new UnsupportedCallbackException(current); } } } } } ParseDigestAuthorizationTokenTestCase.java000066400000000000000000000162461420065311100357710ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.security; import io.undertow.testutils.category.UnitTest; import io.undertow.security.idm.DigestAlgorithm; import io.undertow.security.impl.DigestAuthorizationToken; import io.undertow.security.impl.DigestQop; import org.junit.Test; import org.junit.experimental.categories.Category; import java.util.EnumMap; import java.util.Map; import static org.junit.Assert.assertEquals; /** * Test case to test the parsing of the Authorization header for Digest requests. * * The RFC defines which values are quoted and which ones are not, however different implementations interpret this differently. * This test case tests different headers generated by supported browsers. * * Ordering of the values is not important, however the construction of the Map of expected values does match the header being * tested for readability. * * @author Darran Lofthouse */ @Category(UnitTest.class) public class ParseDigestAuthorizationTokenTestCase { private void doTest(final String header, final Map expected) { Map parsedHeader = DigestAuthorizationToken.parseHeader(header); assertEquals(expected, parsedHeader); } @Test public void testChrome_22() { final String header = "username=\"userTwo\", realm=\"Digest_Realm\", nonce=\"Yxmkh5liIOYNMTM1MTUyNjQzMTE4NJziT7YLEOEJ4QEN1py4Yog=\", uri=\"/\", algorithm=MD5, response=\"5b26e00233607e8a714cd1d910692e08\", opaque=\"00000000000000000000000000000000\", qop=auth, nc=00000001, cnonce=\"8c008c8ce43dc0a7\""; Map expected = new EnumMap<>(DigestAuthorizationToken.class); expected.put(DigestAuthorizationToken.USERNAME, "userTwo"); expected.put(DigestAuthorizationToken.REALM, "Digest_Realm"); expected.put(DigestAuthorizationToken.NONCE, "Yxmkh5liIOYNMTM1MTUyNjQzMTE4NJziT7YLEOEJ4QEN1py4Yog="); expected.put(DigestAuthorizationToken.DIGEST_URI, "/"); expected.put(DigestAuthorizationToken.ALGORITHM, DigestAlgorithm.MD5.getToken()); expected.put(DigestAuthorizationToken.RESPONSE, "5b26e00233607e8a714cd1d910692e08"); expected.put(DigestAuthorizationToken.OPAQUE, "00000000000000000000000000000000"); expected.put(DigestAuthorizationToken.MESSAGE_QOP, DigestQop.AUTH.getToken()); expected.put(DigestAuthorizationToken.NONCE_COUNT, "00000001"); expected.put(DigestAuthorizationToken.CNONCE, "8c008c8ce43dc0a7"); doTest(header, expected); } @Test public void testCurl_7() { final String header = "username=\"userTwo\", realm=\"Digest_Realm\", nonce=\"5CgZ39vhie0NMTM1MTUyNDc4ODkwNMwr6sWKVSGfhXB4jBtkupY=\", uri=\"/\", cnonce=\"MTYwOTQ4\", nc=00000001, qop=\"auth\", response=\"c3c1ce9945a0c36d54860eda7846018b\", opaque=\"00000000000000000000000000000000\", algorithm=\"MD5\""; Map expected = new EnumMap<>(DigestAuthorizationToken.class); expected.put(DigestAuthorizationToken.USERNAME, "userTwo"); expected.put(DigestAuthorizationToken.REALM, "Digest_Realm"); expected.put(DigestAuthorizationToken.NONCE, "5CgZ39vhie0NMTM1MTUyNDc4ODkwNMwr6sWKVSGfhXB4jBtkupY="); expected.put(DigestAuthorizationToken.DIGEST_URI, "/"); expected.put(DigestAuthorizationToken.CNONCE, "MTYwOTQ4"); expected.put(DigestAuthorizationToken.NONCE_COUNT, "00000001"); expected.put(DigestAuthorizationToken.MESSAGE_QOP, DigestQop.AUTH.getToken()); expected.put(DigestAuthorizationToken.RESPONSE, "c3c1ce9945a0c36d54860eda7846018b"); expected.put(DigestAuthorizationToken.OPAQUE, "00000000000000000000000000000000"); expected.put(DigestAuthorizationToken.ALGORITHM, DigestAlgorithm.MD5.getToken()); doTest(header, expected); } @Test public void testFirefox_16() { final String header = "username=\"userOne\", realm=\"Digest_Realm\", nonce=\"nBhFxtSS6rkNMTM1MTUyNjE2MjgyNWA/xW/LOH53vhXGq/2B/yQ=\", uri=\"/\", algorithm=MD5, response=\"b0adb1025da2de0d16f44131858bad6f\", opaque=\"00000000000000000000000000000000\", qop=auth, nc=00000001, cnonce=\"8127726535363b07\""; Map expected = new EnumMap<>(DigestAuthorizationToken.class); expected.put(DigestAuthorizationToken.USERNAME, "userOne"); expected.put(DigestAuthorizationToken.REALM, "Digest_Realm"); expected.put(DigestAuthorizationToken.NONCE, "nBhFxtSS6rkNMTM1MTUyNjE2MjgyNWA/xW/LOH53vhXGq/2B/yQ="); expected.put(DigestAuthorizationToken.DIGEST_URI, "/"); expected.put(DigestAuthorizationToken.ALGORITHM, DigestAlgorithm.MD5.getToken()); expected.put(DigestAuthorizationToken.RESPONSE, "b0adb1025da2de0d16f44131858bad6f"); expected.put(DigestAuthorizationToken.OPAQUE, "00000000000000000000000000000000"); expected.put(DigestAuthorizationToken.MESSAGE_QOP, DigestQop.AUTH.getToken()); expected.put(DigestAuthorizationToken.NONCE_COUNT, "00000001"); expected.put(DigestAuthorizationToken.CNONCE, "8127726535363b07"); doTest(header, expected); } @Test public void testOpera_12() { final String header = "username=\"userOne\", realm=\"Digest_Realm\", uri=\"/\", algorithm=MD5, nonce=\"D2floAc+FhkNMTM1MTUyMzY2ODc4Mhbi2Zrcuv1lvdgEaPXa+bg=\", cnonce=\"v722VYJEeG28C3SoXS8BEWThGHPDOlXgUCCts70i7Fc=\", opaque=\"00000000000000000000000000000000\", qop=auth, nc=00000001, response=\"8106a5d19bc67982527cbb576658f9d6\""; Map expected = new EnumMap<>(DigestAuthorizationToken.class); expected.put(DigestAuthorizationToken.USERNAME, "userOne"); expected.put(DigestAuthorizationToken.REALM, "Digest_Realm"); expected.put(DigestAuthorizationToken.DIGEST_URI, "/"); expected.put(DigestAuthorizationToken.ALGORITHM, DigestAlgorithm.MD5.getToken()); expected.put(DigestAuthorizationToken.NONCE, "D2floAc+FhkNMTM1MTUyMzY2ODc4Mhbi2Zrcuv1lvdgEaPXa+bg="); expected.put(DigestAuthorizationToken.CNONCE, "v722VYJEeG28C3SoXS8BEWThGHPDOlXgUCCts70i7Fc="); expected.put(DigestAuthorizationToken.OPAQUE, "00000000000000000000000000000000"); expected.put(DigestAuthorizationToken.MESSAGE_QOP, DigestQop.AUTH.getToken()); expected.put(DigestAuthorizationToken.NONCE_COUNT, "00000001"); expected.put(DigestAuthorizationToken.RESPONSE, "8106a5d19bc67982527cbb576658f9d6"); doTest(header, expected); } } SimpleConfidentialRedirectTestCase.java000066400000000000000000000145351420065311100352270ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.security; import java.io.IOException; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import io.undertow.security.handlers.SinglePortConfidentialityHandler; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.ProxyIgnore; import io.undertow.testutils.TestHttpClient; import io.undertow.util.FileUtils; import io.undertow.util.HttpString; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpGet; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * A simple test case to verify a redirect works. * * @author Darran Lofthouse * @author Flavia Rainone */ @RunWith(DefaultServer.class) public class SimpleConfidentialRedirectTestCase { private static int redirectPort = -1; @BeforeClass public static void setup() throws IOException { DefaultServer.startSSLServer(); HttpHandler current = new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) { exchange.getResponseHeaders().put(HttpString.tryFromString("scheme"), exchange.getRequestScheme()); exchange.getResponseHeaders().put(HttpString.tryFromString("uri"), exchange.getRequestURI()); exchange.getResponseHeaders().put(HttpString.tryFromString("queryString"), exchange.getQueryString()); exchange.getResponseHeaders().put(HttpString.tryFromString("redirectedToPort"), exchange.getHostPort()); exchange.endExchange(); } }; redirectPort = DefaultServer.getHostSSLPort("default"); current = new SinglePortConfidentialityHandler(current, redirectPort); DefaultServer.setRootHandler(current); } @AfterClass public static void stop() throws IOException { DefaultServer.stopSSLServer(); } @Test public void simpleRedirectTestCase() throws IOException, GeneralSecurityException { TestHttpClient client = new TestHttpClient(); // create our own context to force http-request.config // notice that, if we just create http context, the config is ovewritten before request is sent // if we add the config to the HttpClient instead, it is ignored HttpContext httpContext = new BasicHttpContext() { private final RequestConfig config = RequestConfig.copy(RequestConfig.DEFAULT).setNormalizeUri(false).build(); @Override public void setAttribute(final String id, final Object obj) { if ("http.request-config".equals(id)) return; super.setAttribute(id, obj); } @Override public Object getAttribute(final String id) { if ("http.request-config".equals(id)) return config; return super.getAttribute(id); } }; client.setSSLContext(DefaultServer.getClientSSLContext()); try { sendRequest(client, httpContext,"/foo", null); sendRequest(client, httpContext,"/foo+bar", null); sendRequest(client, httpContext,"/foo+bar;aa", null); sendRequest(client, httpContext,"/foo+bar;aa", "x=y"); sendRequest(client, httpContext,"/foo+bar%3Aaa", "x=%3Ablah"); } finally { client.getConnectionManager().shutdown(); } } @ProxyIgnore public void testRedirectWithFullURLInPath() throws IOException { DefaultServer.isProxy(); //now we need to test what happens if the client send a full URI //see UNDERTOW-874 try (Socket socket = new Socket(DefaultServer.getHostAddress(), DefaultServer.getHostPort())) { socket.getOutputStream().write(("GET " + DefaultServer.getDefaultServerURL() + "/foo HTTP/1.0\r\n\r\n").getBytes(StandardCharsets.UTF_8)); String result = FileUtils.readFile(socket.getInputStream()); Assert.assertTrue(result.contains("Location: " + DefaultServer.getDefaultServerSSLAddress() + "/foo")); } } private void sendRequest(final TestHttpClient client, HttpContext httpContext, final String uri, final String queryString) throws IOException { String targetURL = DefaultServer.getDefaultServerURL() + uri; if (queryString != null) { targetURL = targetURL + "?" + queryString; } final HttpGet get = new HttpGet(targetURL); HttpResponse result = client.execute(get, httpContext); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("Unexpected scheme in redirected URI", "https", result.getFirstHeader("scheme").getValue()); Assert.assertEquals("Unexpected port in redirected URI", String.valueOf(redirectPort), result.getFirstHeader("redirectedToPort").getValue()); Assert.assertEquals("Unexpected path in redirected URI", uri, result.getFirstHeader("uri").getValue()); if (queryString != null) { Assert.assertEquals("Unexpected query string in redirected URI", queryString, result.getFirstHeader("queryString").getValue()); } HttpClientUtils.readResponse(result); } } SpnegoAuthenticationTestCase.java000066400000000000000000000156761420065311100341360ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.security; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.GSSAPIServerSubjectFactory; import io.undertow.security.api.SecurityNotification.EventType; import io.undertow.security.impl.GSSAPIAuthenticationMechanism; import io.undertow.testutils.AjpIgnore; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.FlexBase64; import io.undertow.util.StatusCodes; import org.apache.commons.lang.ArrayUtils; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSManager; import org.ietf.jgss.GSSName; import org.ietf.jgss.Oid; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.PrivilegedExceptionAction; import java.util.Base64; import java.util.Collections; import java.util.List; import javax.security.auth.Subject; import static io.undertow.server.security.KerberosKDCUtil.login; import static io.undertow.util.Headers.AUTHORIZATION; import static io.undertow.util.Headers.NEGOTIATE; import static io.undertow.util.Headers.WWW_AUTHENTICATE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** * A test case to test the SPNEGO authentication mechanism. * * @author Darran Lofthouse */ @RunWith(DefaultServer.class) @AjpIgnore(apacheOnly = true, value = "SPNEGO requires a single connection to the server, and apache cannot guarantee that") public class SpnegoAuthenticationTestCase extends AuthenticationTestBase { private static Oid SPNEGO; @Override protected List getTestMechanisms() { AuthenticationMechanism mechanism = new GSSAPIAuthenticationMechanism(new SubjectFactory()); return Collections.singletonList(mechanism); } @BeforeClass public static void startServers() throws Exception { KerberosKDCUtil.startServer(); SPNEGO = new Oid("1.3.6.1.5.5.2"); } @AfterClass public static void stopServers() { } @Test public void testSpnegoSuccess() throws Exception { final TestHttpClient client = new TestHttpClient(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); HttpResponse result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); String header = getAuthHeader(NEGOTIATE, values); assertEquals(NEGOTIATE.toString(), header); HttpClientUtils.readResponse(result); Subject clientSubject = login("jduke", "theduke".toCharArray()); Subject.doAs(clientSubject, new PrivilegedExceptionAction() { @Override public Void run() throws Exception { GSSManager gssManager = GSSManager.getInstance(); GSSName serverName = gssManager.createName("HTTP/" + DefaultServer.getDefaultServerAddress().getHostString(), null); GSSContext context = gssManager.createContext(serverName, SPNEGO, null, GSSContext.DEFAULT_LIFETIME); byte[] token = new byte[0]; boolean gotOur200 = false; while (!context.isEstablished()) { token = context.initSecContext(token, 0, token.length); if (token != null && token.length > 0) { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); get.addHeader(AUTHORIZATION.toString(), NEGOTIATE + " " + FlexBase64.encodeString(token, false)); HttpResponse result = client.execute(get); Header[] headers = result.getHeaders(WWW_AUTHENTICATE.toString()); if (headers.length > 0) { String header = getAuthHeader(NEGOTIATE, headers); byte[] headerBytes = header.getBytes(StandardCharsets.US_ASCII); // FlexBase64.decode() returns byte buffer, which can contain backend array of greater size. // when on such ByteBuffer is called array(), it returns the underlying byte array including the 0 bytes // at the end, which makes the token invalid. => using Base64 mime decoder, which returnes directly properly sized byte[]. token = Base64.getMimeDecoder().decode(ArrayUtils.subarray(headerBytes, NEGOTIATE.toString().length() + 1, headerBytes.length)); } if (result.getStatusLine().getStatusCode() == StatusCodes.OK) { Header[] values = result.getHeaders("ProcessedBy"); assertEquals(1, values.length); assertEquals("ResponseHandler", values[0].getValue()); HttpClientUtils.readResponse(result); assertSingleNotificationType(EventType.AUTHENTICATED); gotOur200 = true; } else if (result.getStatusLine().getStatusCode() == StatusCodes.UNAUTHORIZED) { assertTrue("We did get a header.", headers.length > 0); HttpClientUtils.readResponse(result); } else { fail(String.format("Unexpected status code %d", result.getStatusLine().getStatusCode())); } } } assertTrue(gotOur200); assertTrue(context.isEstablished()); return null; } }); } private class SubjectFactory implements GSSAPIServerSubjectFactory { @Override public Subject getSubjectForHost(String hostName) throws GeneralSecurityException { return login("HTTP/" + hostName, "servicepwd".toCharArray()); } } } SpnegoBasicAuthenticationTestCase.java000066400000000000000000000045201420065311100350620ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.security; import static io.undertow.server.security.BasicAuthenticationTestCase._testBadPassword; import static io.undertow.server.security.BasicAuthenticationTestCase._testBadUserName; import static io.undertow.server.security.BasicAuthenticationTestCase._testBasicSuccess; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.SecurityNotification.EventType; import io.undertow.testutils.DefaultServer; import java.util.ArrayList; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; /** * A test case to test the SPNEGO authentication mechanism with a fallback to BASIC authentication. * * @author Darran Lofthouse */ @RunWith(DefaultServer.class) public class SpnegoBasicAuthenticationTestCase extends SpnegoAuthenticationTestCase { @Override protected List getTestMechanisms() { ArrayList mechanisms = new ArrayList<>(super.getTestMechanisms()); mechanisms.add(BasicAuthenticationTestCase.getTestMechanism()); return mechanisms; } @Test public void testBasicSuccess() throws Exception { _testBasicSuccess(); assertSingleNotificationType(EventType.AUTHENTICATED); } @Test public void testBadUserName() throws Exception { _testBadUserName(); assertSingleNotificationType(EventType.FAILED_AUTHENTICATION); } @Test public void testBadPassword() throws Exception { _testBadPassword(); assertSingleNotificationType(EventType.FAILED_AUTHENTICATION); } } SpnegoDigestAuthenticationTestCase.java000066400000000000000000000047351420065311100352700ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.security; import static io.undertow.server.security.DigestAuthenticationAuthTestCase._testBadNonce; import static io.undertow.server.security.DigestAuthenticationAuthTestCase._testBadPassword; import static io.undertow.server.security.DigestAuthenticationAuthTestCase._testBadUsername; import static io.undertow.server.security.DigestAuthenticationAuthTestCase._testDigestSuccess; import static io.undertow.server.security.DigestAuthenticationAuthTestCase._testNonceCountReUse; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.testutils.DefaultServer; import java.util.ArrayList; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; /** * A test case to test the SPNEGO authentication mechanism with a fallback to DIGEST authentication. * * @author Darran Lofthouse */ @RunWith(DefaultServer.class) public class SpnegoDigestAuthenticationTestCase extends SpnegoAuthenticationTestCase { @Override protected List getTestMechanisms() { ArrayList mechanisms = new ArrayList<>(super.getTestMechanisms()); mechanisms.add(DigestAuthenticationAuthTestCase.getTestMechanism()); return mechanisms; } @Test public void testDigestSuccess() throws Exception { _testDigestSuccess(); } @Test public void testBadUsername() throws Exception { _testBadUsername(); } @Test public void testBadPassword() throws Exception { _testBadPassword(); } @Test public void testBadNonce() throws Exception { _testBadNonce(); } @Test public void testNonceCountReUse() throws Exception { _testNonceCountReUse(); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/security/SsoTestCase.java000066400000000000000000000162321420065311100306130ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.security; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.AuthenticationMode; import io.undertow.security.api.NotificationReceiver; import io.undertow.security.api.SecurityNotification; import io.undertow.security.handlers.AuthenticationCallHandler; import io.undertow.security.handlers.AuthenticationConstraintHandler; import io.undertow.security.handlers.AuthenticationMechanismsHandler; import io.undertow.security.handlers.NotificationReceiverHandler; import io.undertow.security.handlers.SecurityInitialHandler; import io.undertow.security.impl.BasicAuthenticationMechanism; import io.undertow.security.impl.FormAuthenticationMechanism; import io.undertow.security.impl.InMemorySingleSignOnManager; import io.undertow.security.impl.SingleSignOnAuthenticationMechanism; import io.undertow.server.HttpHandler; import io.undertow.server.handlers.PathHandler; import io.undertow.server.handlers.ResponseCodeHandler; import io.undertow.server.session.InMemorySessionManager; import io.undertow.server.session.SessionAttachmentHandler; import io.undertow.server.session.SessionCookieConfig; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.FlexBase64; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.BasicCookieStore; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import static io.undertow.util.Headers.AUTHORIZATION; import static io.undertow.util.Headers.BASIC; import static io.undertow.util.Headers.WWW_AUTHENTICATE; import static org.junit.Assert.assertEquals; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class SsoTestCase extends AuthenticationTestBase { @BeforeClass public static void setup() { final SingleSignOnAuthenticationMechanism sso = new SingleSignOnAuthenticationMechanism(new InMemorySingleSignOnManager()); final PathHandler path = new PathHandler(); HttpHandler current = new ResponseHandler(); current = new AuthenticationCallHandler(current); current = new AuthenticationConstraintHandler(current); List mechs = new ArrayList<>(); mechs.add(sso); mechs.add(new BasicAuthenticationMechanism("Test Realm")); current = new AuthenticationMechanismsHandler(current, mechs); current = new NotificationReceiverHandler(current, Collections.singleton(auditReceiver)); current = new SecurityInitialHandler(AuthenticationMode.PRO_ACTIVE, identityManager, current); path.addPrefixPath("/test1", current); current = new ResponseHandler(); current = new AuthenticationCallHandler(current); current = new AuthenticationConstraintHandler(current); mechs = new ArrayList<>(); mechs.add(sso); mechs.add(new FormAuthenticationMechanism("form", "/login", "/error")); current = new AuthenticationMechanismsHandler(current, mechs); current = new NotificationReceiverHandler(current, Collections.singleton(auditReceiver)); current = new SecurityInitialHandler(AuthenticationMode.PRO_ACTIVE, identityManager, current); path.addPrefixPath("/test2", current); path.addPrefixPath("/login", new ResponseCodeHandler(StatusCodes.UNAUTHORIZED)); DefaultServer.setRootHandler(new SessionAttachmentHandler(path, new InMemorySessionManager(""), new SessionCookieConfig())); } @Override protected List getTestMechanisms() { return null;//not used } @Test public void testSsoSuccess() throws IOException { TestHttpClient client = new TestHttpClient(); client.setCookieStore(new BasicCookieStore()); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/test1"); HttpResponse result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); String header = getAuthHeader(BASIC, values); assertEquals(BASIC + " realm=\"Test Realm\"", header); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/test1"); get.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString("userOne:passwordOne".getBytes(), false)); result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); values = result.getHeaders("ProcessedBy"); assertEquals(1, values.length); assertEquals("ResponseHandler", values[0].getValue()); HttpClientUtils.readResponse(result); assertSingleNotificationType(SecurityNotification.EventType.AUTHENTICATED); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/test2"); result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); values = result.getHeaders("ProcessedBy"); assertEquals(1, values.length); assertEquals("ResponseHandler", values[0].getValue()); HttpClientUtils.readResponse(result); assertSingleNotificationType(SecurityNotification.EventType.AUTHENTICATED); //now test that logout will invalidate the SSO session get = new HttpGet(DefaultServer.getDefaultServerURL() + "/test1?logout=true"); get.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString("userOne:passwordOne".getBytes(), false)); result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); values = result.getHeaders("ProcessedBy"); assertEquals(1, values.length); assertEquals("ResponseHandler", values[0].getValue()); HttpClientUtils.readResponse(result); assertNotifiactions(SecurityNotification.EventType.AUTHENTICATED, SecurityNotification.EventType.LOGGED_OUT); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/test2"); result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/ssl/000077500000000000000000000000001420065311100244765ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/server/ssl/ComplexSSLTestCase.java000066400000000000000000000157641420065311100310030ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.ssl; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Path; import java.nio.file.Paths; import java.security.GeneralSecurityException; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.CanonicalPathHandler; import io.undertow.server.handlers.NameVirtualHostHandler; import io.undertow.server.handlers.PathHandler; import io.undertow.server.handlers.error.SimpleErrorPageHandler; import io.undertow.server.handlers.resource.PathResourceManager; import io.undertow.server.handlers.resource.ResourceHandler; import io.undertow.server.handlers.file.FileHandlerTestCase; import io.undertow.testutils.AjpIgnore; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @AjpIgnore @RunWith(DefaultServer.class) public class ComplexSSLTestCase { private static final String MESSAGE = "My HTTP Request!"; private static volatile String message; @Test public void complexSSLTestCase() throws IOException, GeneralSecurityException, URISyntaxException, InterruptedException { final PathHandler pathHandler = new PathHandler(); Path rootPath = Paths.get(FileHandlerTestCase.class.getResource("page.html").toURI()).getParent(); final NameVirtualHostHandler virtualHostHandler = new NameVirtualHostHandler(); HttpHandler root = virtualHostHandler; root = new SimpleErrorPageHandler(root); root = new CanonicalPathHandler(root); virtualHostHandler.addHost("default-host", pathHandler); virtualHostHandler.setDefaultHandler(pathHandler); pathHandler.addPrefixPath("/", new ResourceHandler(new PathResourceManager(rootPath, 10485760)) .setDirectoryListingEnabled(true)); DefaultServer.setRootHandler(root); DefaultServer.startSSLServer(); TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); try { //get file list, this works HttpGet getFileList = new HttpGet(DefaultServer.getDefaultServerSSLAddress()); HttpResponse resultList = client.execute(getFileList); Assert.assertEquals(StatusCodes.OK, resultList.getStatusLine().getStatusCode()); String responseList = HttpClientUtils.readResponse(resultList); Assert.assertTrue(responseList, responseList.contains("page.html")); Header[] headersList = resultList.getHeaders("Content-Type"); Assert.assertEquals("text/html; charset=UTF-8", headersList[0].getValue()); //get file itself, breaks HttpGet getFile = new HttpGet(DefaultServer.getDefaultServerSSLAddress() + "/page.html"); HttpResponse result = client.execute(getFile); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Header[] headers = result.getHeaders("Content-Type"); Assert.assertEquals("text/html", headers[0].getValue()); Assert.assertTrue(response, response.contains("A web page")); } finally { client.getConnectionManager().shutdown(); DefaultServer.stopSSLServer(); } } @Test @Ignore // FIXME UNDERTOW-1918 public void testSslLotsOfData() throws IOException, GeneralSecurityException, URISyntaxException { DefaultServer.setRootHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if(exchange.isInIoThread()) { exchange.dispatch(this); return; } exchange.startBlocking(); ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] buf = new byte[100]; int res = 0; while ((res = exchange.getInputStream().read(buf)) > 0) { out.write(buf, 0, res); } System.out.println("WRITE " + out.size()); exchange.getOutputStream().write(out.toByteArray()); System.out.println("DONE " + out.size()); } }); DefaultServer.startSSLServer(); TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); try { generateMessage(1000000); HttpPost post = new HttpPost(DefaultServer.getDefaultServerSSLAddress()); post.setEntity(new StringEntity(message)); HttpResponse resultList = client.execute(post); Assert.assertEquals(StatusCodes.OK, resultList.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(resultList); Assert.assertEquals(message.length(), response.length()); Assert.assertEquals(message, response); generateMessage(100000); post = new HttpPost(DefaultServer.getDefaultServerSSLAddress()); post.setEntity(new StringEntity(message)); resultList = client.execute(post); Assert.assertEquals(StatusCodes.OK, resultList.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(resultList); Assert.assertEquals(message.length(), response.length()); Assert.assertEquals(message, response); } finally { client.getConnectionManager().shutdown(); DefaultServer.stopSSLServer(); } } private static void generateMessage(int repetitions) { final StringBuilder builder = new StringBuilder(repetitions * MESSAGE.length()); for (int i = 0; i < repetitions; ++i) { builder.append(MESSAGE); } message = builder.toString(); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/ssl/DelegatedTaskExecutorTestCase.java000066400000000000000000000123371420065311100332230ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.ssl; import io.undertow.Undertow; import io.undertow.server.handlers.ResponseCodeHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.TestHttpClient; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Test; import javax.net.ssl.SSLHandshakeException; import java.io.IOException; import java.net.InetSocketAddress; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** * @author Carter Kozak */ public class DelegatedTaskExecutorTestCase { @Test public void testDelegatedTaskExecutorIsUsed() throws Exception { ExecutorService delegatedTaskExecutor = Executors.newSingleThreadExecutor(); AtomicInteger counter = new AtomicInteger(); Undertow undertow = Undertow.builder() .addHttpsListener(0, null, DefaultServer.getServerSslContext()) .setSslEngineDelegatedTaskExecutor(task -> { counter.getAndIncrement(); delegatedTaskExecutor.execute(task); }) .setHandler(ResponseCodeHandler.HANDLE_200) .build(); TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); undertow.start(); int port = port(undertow); try(CloseableHttpResponse response = client.execute(new HttpGet("https://localhost:" + port))) { assertEquals(200, response.getStatusLine().getStatusCode()); assertTrue("expected interactions with the delegated task executor", counter.get() > 0); } finally { undertow.stop(); client.getConnectionManager().shutdown(); List tasks = delegatedTaskExecutor.shutdownNow(); for (Runnable task: tasks) { task.run(); } assertTrue( "ExecutorService did not shut down in time", delegatedTaskExecutor.awaitTermination(1, TimeUnit.SECONDS)); } } @Test public void testRejection() { Undertow undertow = Undertow.builder() .addHttpsListener(0, null, DefaultServer.getServerSslContext()) .setSslEngineDelegatedTaskExecutor(ignoredTask -> { throw new RejectedExecutionException(); }) .setHandler(ResponseCodeHandler.HANDLE_200) .build(); TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); undertow.start(); try { int port = port(undertow); HttpGet request = new HttpGet("https://localhost:" + port); try { client.execute(request); fail("Expected an exception"); } catch (SSLHandshakeException handshakeException) { // expected one of: // - Remote host closed connection during handshake // - Remote host terminated the handshake // This exception comes from the jvm and may change in future // releases so we don't verify an exact match. String message = handshakeException.getMessage(); System.out.println(message); assertTrue( "message was: " + message, message != null && (message.contains("closed") || message.contains("terminated"))); } catch (IOException e) { throw new AssertionError(e); } } finally { undertow.stop(); client.getConnectionManager().shutdown(); // sleep 1 s to prevent BindException (Address already in use) when running the CI try { Thread.sleep(1000); } catch (InterruptedException ignore) {} } } private static int port(Undertow undertow) { if (undertow.getListenerInfo().size() != 1) { throw new IllegalStateException("Expected exactly one listener"); } InetSocketAddress address = (InetSocketAddress) undertow.getListenerInfo().get(0).getAddress(); return address.getPort(); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/ssl/SimpleSSLTestCase.java000066400000000000000000000177211420065311100306200ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.server.ssl; import java.io.IOException; import java.security.GeneralSecurityException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.config.SocketConfig; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.junit.Assert; import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class SimpleSSLTestCase { @Test public void simpleSSLTestCase() throws IOException, GeneralSecurityException { DefaultServer.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getResponseHeaders().put(HttpString.tryFromString("scheme"), exchange.getRequestScheme()); exchange.endExchange(); } }); DefaultServer.startSSLServer(); TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress()); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] header = result.getHeaders("scheme"); Assert.assertEquals("https", header[0].getValue()); } finally { client.getConnectionManager().shutdown(); DefaultServer.stopSSLServer(); } } @Test public void testNonPersistentConnections() throws IOException, GeneralSecurityException { DefaultServer.setRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getResponseHeaders().put(HttpString.tryFromString("scheme"), exchange.getRequestScheme()); exchange.getResponseHeaders().put(Headers.CONNECTION, "close"); exchange.endExchange(); } }); DefaultServer.startSSLServer(); TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); try { for(int i = 0; i <5; ++ i) { HttpGet get = new HttpGet(DefaultServer.getDefaultServerSSLAddress()); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] header = result.getHeaders("scheme"); Assert.assertEquals("https", header[0].getValue()); HttpClientUtils.readResponse(result); } } finally { client.getConnectionManager().shutdown(); DefaultServer.stopSSLServer(); } } @Test public void parallel() throws Exception { // FIXME UNDERTOW-1928 Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows") && DefaultServer.isProxy()); runTest(32, new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getResponseHeaders().put(HttpString.tryFromString("scheme"), exchange.getRequestScheme()); exchange.endExchange(); } }); } @Test public void parallelWithDispatch() throws Exception { // FIXME UNDERTOW-1928 Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows")); runTest(32, new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.dispatch(() -> { exchange.getResponseHeaders().put(HttpString.tryFromString("scheme"), exchange.getRequestScheme()); exchange.endExchange(); }); } }); } @Test public void parallelWithBlockingDispatch() throws Exception { // FIXME UNDERTOW-1928 Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows")); runTest(32, new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { if (exchange.isInIoThread()) { exchange.dispatch(this); return; } exchange.startBlocking(); exchange.getResponseHeaders().put(HttpString.tryFromString("scheme"), exchange.getRequestScheme()); exchange.endExchange(); } }); } private void runTest(int concurrency, HttpHandler handler) throws IOException, InterruptedException { DefaultServer.setRootHandler(handler); DefaultServer.startSSLServer(); try (CloseableHttpClient client = HttpClients.custom().disableConnectionState() .setSSLContext(DefaultServer.getClientSSLContext()) .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(5000).build()) .setMaxConnPerRoute(1000) .build()) { ExecutorService executorService = Executors.newFixedThreadPool(concurrency); AtomicBoolean failed = new AtomicBoolean(); Runnable task = new Runnable() { @Override public void run() { if (failed.get()) { return; } try (CloseableHttpResponse result = client.execute(new HttpGet(DefaultServer.getDefaultServerSSLAddress()))) { Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Header[] header = result.getHeaders("scheme"); Assert.assertEquals("https", header[0].getValue()); EntityUtils.consumeQuietly(result.getEntity()); } catch (Throwable t) { if (failed.compareAndSet(false, true)) { t.printStackTrace(); executorService.shutdownNow(); } } } }; for (int i = 0; i < concurrency * 300; i++) { executorService.submit(task); } executorService.shutdown(); Assert.assertTrue(executorService.awaitTermination(70, TimeUnit.SECONDS)); Assert.assertFalse(failed.get()); } finally { DefaultServer.stopSSLServer(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/server/ssl/TLS13HalfCloseHangTestCase.java000066400000000000000000000071601420065311100321660ustar00rootroot00000000000000package io.undertow.server.ssl; import io.undertow.Undertow; import io.undertow.testutils.DefaultServer; import io.undertow.util.Headers; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.net.InetSocketAddress; import java.net.Socket; import java.security.GeneralSecurityException; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import org.junit.Assume; import org.junit.Test; import org.xnio.Options; import org.xnio.Sequence; public class TLS13HalfCloseHangTestCase { private static final Pattern CONTENT_LENGTH_PATTERN = Pattern .compile("Content-Length: ([0-9]+)", Pattern.CASE_INSENSITIVE); @Test public void testHang() throws IOException, GeneralSecurityException, InterruptedException { SSLContext clientSslContext = null; try { clientSslContext = DefaultServer.createClientSslContext("TLSv1.3"); } catch (Throwable e) { // Don't try to run test if TLS 1.3 is not supported Assume.assumeNoException(e); } Undertow server = Undertow.builder() // This relies on TLSv1.2 context actually supporting TLS 1.3 which works fine with JDK11 .addHttpsListener(0, "localhost", DefaultServer.getServerSslContext()) .setHandler((exchange) -> { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain"); exchange.getResponseSender().send("Hello World!\n"); }) .setSocketOption(Options.SSL_ENABLED_PROTOCOLS, Sequence.of("TLSv1.3")) // These make the issue easier to detect .setIoThreads(1) .setWorkerThreads(1) .build(); server.start(); InetSocketAddress address = (InetSocketAddress) server.getListenerInfo().get(0).getAddress(); String uri = "https://localhost:" + address.getPort() + "/foo"; doRequest(clientSslContext, address); doRequest(clientSslContext, address); server.stop(); // sleep 1 s to prevent BindException (Address already in use) when running the CI try { Thread.sleep(1000); } catch (InterruptedException ignore) {} } private void doRequest(SSLContext clientSslContext, InetSocketAddress address) throws IOException { Socket rawSocket = new Socket(); rawSocket.connect(address); SSLSocket sslSocket = (SSLSocket) clientSslContext.getSocketFactory() .createSocket(rawSocket, "localhost", address.getPort(), false); PrintWriter writer = new PrintWriter(sslSocket.getOutputStream()); writer.println("GET / HTTP/1.1"); writer.println("Host: localhost"); writer.println("Connection: keep-alive"); writer.println(); writer.flush(); readResponse(sslSocket); sslSocket.shutdownOutput(); rawSocket.close(); } private String readLine(InputStream is) throws IOException { StringBuilder line = new StringBuilder(); while (true) { int c = is.read(); switch (c) { case -1: throw new RuntimeException("Unexpected EOF"); case '\r': continue; case '\n': return line.toString(); default: line.append((char) c); } } } private void readResponse(SSLSocket sslSocket) throws IOException { String line; int contentLength = 0; do { line = readLine(sslSocket.getInputStream()); Matcher matcher = CONTENT_LENGTH_PATTERN.matcher(line); if (matcher.matches()) { contentLength = Integer.parseInt(matcher.group(1), 10); } } while (!line.isEmpty()); for (int i = 0; i < contentLength; i++) { sslSocket.getInputStream().read(); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/testutils/000077500000000000000000000000001420065311100244275ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/testutils/AjpIgnore.java000066400000000000000000000020361420065311100271510ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.testutils; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * @author Stuart Douglas */ @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface AjpIgnore { boolean apacheOnly() default false; String value() default ""; } undertow-2.2.16.Final/core/src/test/java/io/undertow/testutils/DebuggingSlicePool.java000066400000000000000000000070321420065311100310010ustar00rootroot00000000000000package io.undertow.testutils; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import java.nio.ByteBuffer; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; /** * @author Stuart Douglas */ public class DebuggingSlicePool implements ByteBufferPool{ /** * context that can be added to allocations to give more information about buffer leaks, useful when debugging buffer leaks */ private static final ThreadLocal ALLOCATION_CONTEXT = new ThreadLocal<>(); static final Set BUFFERS = Collections.newSetFromMap(new ConcurrentHashMap()); static volatile String currentLabel; private final ByteBufferPool delegate; private final ByteBufferPool arrayBacked; public DebuggingSlicePool(ByteBufferPool delegate) { this.delegate = delegate; if(delegate.isDirect()) { this.arrayBacked = new DebuggingSlicePool(delegate.getArrayBackedPool()); } else { this.arrayBacked = this; } } public static void addContext(String context) { ALLOCATION_CONTEXT.set(context); } @Override public PooledByteBuffer allocate() { final PooledByteBuffer delegate = this.delegate.allocate(); return new DebuggingBuffer(delegate, currentLabel); } @Override public ByteBufferPool getArrayBackedPool() { return arrayBacked; } @Override public void close() { delegate.close(); } @Override public int getBufferSize() { return delegate.getBufferSize(); } @Override public boolean isDirect() { return delegate.isDirect(); } static class DebuggingBuffer implements PooledByteBuffer { private static final AtomicInteger allocationCount = new AtomicInteger(); private final RuntimeException allocationPoint; private final PooledByteBuffer delegate; private final String label; private final int no; private volatile boolean free = false; private RuntimeException freePoint; DebuggingBuffer(PooledByteBuffer delegate, String label) { this.delegate = delegate; this.label = label; this.no = allocationCount.getAndIncrement(); String ctx = ALLOCATION_CONTEXT.get(); ALLOCATION_CONTEXT.remove(); allocationPoint = new RuntimeException(delegate.getBuffer() + " NO: " + no + " " + (ctx == null ? "[NO_CONTEXT]" : ctx)); BUFFERS.add(this); } @Override public void close() { if(free) { return; } freePoint = new RuntimeException("FREE POINT"); free = true; BUFFERS.remove(this); delegate.close(); } @Override public boolean isOpen() { return !free; } @Override public ByteBuffer getBuffer() throws IllegalStateException { if(free) { throw new IllegalStateException("Buffer already freed, free point: ", freePoint); } return delegate.getBuffer(); } RuntimeException getAllocationPoint() { return allocationPoint; } String getLabel() { return label; } @Override public String toString() { return "[debug:"+no+"]" + delegate.toString() ; } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/testutils/DefaultServer.java000066400000000000000000001400611420065311100300470ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.testutils; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.net.Inet4Address; import java.net.InetSocketAddress; import java.net.URI; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import io.undertow.UndertowLogger; import io.undertow.UndertowOptions; import io.undertow.connector.ByteBufferPool; import io.undertow.protocols.alpn.ALPNManager; import io.undertow.protocols.alpn.ALPNProvider; import io.undertow.protocols.alpn.JettyAlpnProvider; import io.undertow.protocols.ssl.SNIContextMatcher; import io.undertow.protocols.ssl.SNISSLContext; import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.security.impl.GSSAPIAuthenticationMechanism; import io.undertow.server.DefaultByteBufferPool; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.OpenListener; import io.undertow.server.handlers.ProxyPeerAddressHandler; import io.undertow.server.handlers.RequestDumpingHandler; import io.undertow.server.handlers.SSLHeaderHandler; import io.undertow.server.handlers.proxy.LoadBalancingProxyClient; import io.undertow.server.handlers.proxy.ProxyHandler; import io.undertow.server.protocol.ajp.AjpOpenListener; import io.undertow.server.protocol.http.AlpnOpenListener; import io.undertow.server.protocol.http.HttpOpenListener; import io.undertow.server.protocol.http2.Http2OpenListener; import io.undertow.server.protocol.http2.Http2UpgradeHandler; import io.undertow.util.Headers; import io.undertow.util.NetworkUtils; import io.undertow.util.SingleByteStreamSinkConduit; import io.undertow.util.SingleByteStreamSourceConduit; import org.junit.Assume; import org.junit.Ignore; import org.junit.internal.runners.statements.RunAfters; import org.junit.internal.runners.statements.RunBefores; import org.junit.runner.Description; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunListener; import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; import org.junit.runners.model.TestClass; import org.wildfly.openssl.OpenSSLProvider; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.Sequence; import org.xnio.StreamConnection; import org.xnio.Xnio; import org.xnio.XnioWorker; import org.xnio.channels.AcceptingChannel; import org.xnio.ssl.XnioSsl; import static io.undertow.server.handlers.ResponseCodeHandler.HANDLE_404; import static io.undertow.testutils.StopServerWithExternalWorkerUtils.stopWorker; import static io.undertow.testutils.StopServerWithExternalWorkerUtils.waitWorkerRunnableCycle; import static org.xnio.Options.SSL_CLIENT_AUTH_MODE; import static org.xnio.SslClientAuthMode.REQUESTED; /** * A class that starts a server before the test suite. By swapping out the root handler * tests can test various server functionality without continually starting and stopping the server. *

* This runner adds two Annotations for specifying invocation points: {@link DefaultServer.BeforeServerStarts} * and {@link DefaultServer.AfterServerStops}. *

When any of those annotated methods are defined in the test class, tests are run in the following order:

*
  • {@link DefaultServer.BeforeServerStarts @BeforeServerStarts} static methods are invoked
  • *
  • the server is {@link #startServer() started}, before any test method is invoked
  • *
  • {@code @BeforeClass} methods are invoked, meaning subclasses can rely on getter methods * returning values that were initialized in the previous step
  • *
  • all test methods are invoked, with @Before and @After methods being invoked before and after them as in * any normal JUnit test case
  • *
  • {@code @AfterClass} methods are invoked, so subclasses can still read values initialized when the server * started via getter methods in this class
  • *
  • server is finally {@link #stopServer() stopped}
  • *
  • {@link DefaultServer.AfterServerStops @AfterServerStops} methods are invoked
  • *

    *

    {@code @ClassRule}s are applied to the whole block above, meaning that the {@code Statement} passed to the * corresponding {@code TestRule} contains the steps as described in the above list.

    *

    * If no {@link DefaultServer.BeforeServerStarts @BeforeServerStarts}/{@link DefaultServer.AfterServerStops @AfterServerStops} * methods are specified, all methods in the test case are guaranteed to run in the context of a running {@code DefaultServer}. * Notice, however, that in this particular case the server might not be started just before a test case is run, * and will not be shutdown when the same test case is finished. The reason for this is that this runner prevents the * extra cost of starting and stopping the server several times when doing a test run if that step is not needed. * Usually, this runner will start the server only once for a whole test run. For the same reason, the server will be * stopped only after all test classes in the test run have finished. The only exception is when there is a test case that * contains a {@link DefaultServer.BeforeServerStarts @BeforeServerStarts} method. In that case, the server started by * previous test cases will be stopped before anything so that the {@code @BeforeServerStarts} method sticks to the * contract and can be run before the server starts. Likewise, the server is stopped before invoking {@code @AfterServerStops} * methods. Before proceeding to running the next test cases in the test run, this runner will restart the server once * more, but it will refrain from doing so again for the following test cases, unless it faces anoter test with the * {@code @BeforeServerStarts}/{@code @AfterServerStops} methods. *

    * @author Stuart Douglas * @author Flavia Rainone */ public class DefaultServer extends BlockJUnit4ClassRunner { /** * Static methods marked with this annotation will be invoked before the server is started. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface BeforeServerStarts {} /** * Static methods marked with this annotation will be invoked after the server is stopped. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface AfterServerStops {} private static final Throwable OPENSSL_FAILURE; static { Throwable failure = null; try { OpenSSLProvider.register(); } catch (Throwable t) { failure = t; } OPENSSL_FAILURE = failure; } static final String DEFAULT = "default"; private static final int PROXY_OFFSET = 1111; public static final int APACHE_PORT = 9080; public static final int APACHE_SSL_PORT = 9443; public static final int BUFFER_SIZE = Integer.getInteger("test.bufferSize", 1024 * 16 - 20); public static final DebuggingSlicePool SSL_BUFFER_POOL = new DebuggingSlicePool(new DefaultByteBufferPool(true, 17 * 1024)); private static OptionMap serverOptions; private static OptionMap.Builder serverOptionMapBuilder = OptionMap.builder(); private static OpenListener openListener; private static ChannelListener acceptListener; private static OpenListener proxyOpenListener; private static ChannelListener proxyAcceptListener; private static XnioWorker worker; private static AcceptingChannel server; private static AcceptingChannel proxyServer; private static AcceptingChannel sslServer; private static SSLContext clientSslContext; private static final String SERVER_KEY_STORE = "server.keystore"; private static final String SERVER_TRUST_STORE = "server.truststore"; private static final String CLIENT_KEY_STORE = "client.keystore"; private static final String CLIENT_TRUST_STORE = "client.truststore"; private static final char[] STORE_PASSWORD = "password".toCharArray(); private static final boolean ajp = Boolean.getBoolean("test.ajp"); private static final boolean h2 = Boolean.getBoolean("test.h2"); private static final boolean h2c = Boolean.getBoolean("test.h2c"); private static final boolean h2cUpgrade = Boolean.getBoolean("test.h2c-upgrade"); private static final boolean https = Boolean.getBoolean("test.https"); private static final boolean proxy = Boolean.getBoolean("test.proxy"); private static final boolean apache = Boolean.getBoolean("test.apache"); private static final boolean dump = Boolean.getBoolean("test.dump"); private static final boolean single = Boolean.getBoolean("test.single"); private static final boolean openssl = Boolean.getBoolean("test.openssl"); private static final boolean ipv6 = Boolean.getBoolean("test.ipv6"); private static final int runs = Integer.getInteger("test.runs", 1); private static final DelegatingHandler rootHandler = new DelegatingHandler(); private static final DebuggingSlicePool pool = new DebuggingSlicePool(new DefaultByteBufferPool(true, BUFFER_SIZE, 1000, 10, 100)); private static LoadBalancingProxyClient loadBalancingProxyClient; /** A link to the {@link #startServer()} method using JUnit framework method API. */ private static final List startServerMethod; /** A link to the {@link #stopServer()} method using JUnit framework method API. */ private static final List stopServerMethod; static { startServerMethod = new ArrayList<>(1); try { startServerMethod.add(new FrameworkMethod(DefaultServer.class.getDeclaredMethod("startServer"))); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } stopServerMethod = new ArrayList<>(1); try { stopServerMethod.add(new FrameworkMethod(DefaultServer.class.getDeclaredMethod("stopServer"))); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } private static KeyStore loadKeyStore(final String name) throws IOException { final InputStream stream = DefaultServer.class.getClassLoader().getResourceAsStream(name); if (stream == null) { throw new RuntimeException("Could not load keystore"); } try { KeyStore loadedKeystore = KeyStore.getInstance("JKS"); loadedKeystore.load(stream, STORE_PASSWORD); return loadedKeystore; } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException e) { throw new IOException(String.format("Unable to load KeyStore %s", name), e); } finally { IoUtils.safeClose(stream); } } private static SSLContext createSSLContext(final KeyStore keyStore, final KeyStore trustStore, boolean client) throws IOException { return createSSLContext(keyStore, trustStore, "TLSv1.2", client); } private static SSLContext createSSLContext(final KeyStore keyStore, final KeyStore trustStore, String protocol, boolean client) throws IOException { final KeyManager[] keyManagers; try { KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, STORE_PASSWORD); keyManagers = keyManagerFactory.getKeyManagers(); } catch (NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException e) { throw new IOException("Unable to initialise KeyManager[]", e); } final TrustManager[] trustManagers; try { TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(trustStore); trustManagers = trustManagerFactory.getTrustManagers(); } catch (NoSuchAlgorithmException | KeyStoreException e) { throw new IOException("Unable to initialise TrustManager[]", e); } final SSLContext sslContext; try { if (openssl && !client) { sslContext = SSLContext.getInstance("openssl.TLS"); } else { sslContext = SSLContext.getInstance(protocol); } sslContext.init(keyManagers, trustManagers, null); } catch (NoSuchAlgorithmException | KeyManagementException e) { throw new IOException("Unable to create and initialise the SSLContext", e); } if (!client) { SNIContextMatcher matcher = new SNIContextMatcher.Builder().setDefaultContext(sslContext) .addMatch("localhost", sslContext) .build(); return new SNISSLContext(matcher); } else { return sslContext; } } /** * @return The base URL that can be used to make connections to this server */ public static String getDefaultServerURL() { return "http://" + NetworkUtils.formatPossibleIpv6Address(getHostAddress(DEFAULT)) + ":" + getHostPort(DEFAULT); } public static InetSocketAddress getDefaultServerAddress() { return new InetSocketAddress(DefaultServer.getHostAddress("default"), DefaultServer.getHostPort("default")); } public static String getDefaultServerSSLAddress() { if (sslServer == null && !isApacheTest()) { throw new IllegalStateException("SSL Server not started."); } return "https://" + NetworkUtils.formatPossibleIpv6Address(getHostAddress(DEFAULT)) + ":" + getHostSSLPort(DEFAULT); } public DefaultServer(Class klass) throws InitializationError { super(klass); } @SuppressWarnings("deprecation") public static void setupProxyHandlerForSSL(ProxyHandler proxyHandler) { proxyHandler.addRequestHeader(Headers.SSL_CLIENT_CERT, "%{SSL_CLIENT_CERT}", DefaultServer.class.getClassLoader()); proxyHandler.addRequestHeader(Headers.SSL_CIPHER, "%{SSL_CIPHER}", DefaultServer.class.getClassLoader()); proxyHandler.addRequestHeader(Headers.SSL_SESSION_ID, "%{SSL_SESSION_ID}", DefaultServer.class.getClassLoader()); } public static ByteBufferPool getBufferPool() { return pool; } public static Supplier getWorkerSupplier() { return DefaultServer::getWorker; } @Override public Description getDescription() { return super.getDescription(); } /** * Returns a {@link Statement} that runs the test methods from within a * {@link RunDefaultServer} statement. If any {@link BeforeServerStarts @BeforeServerStarts} * methods are found in the test class, server is stopped if it is already running, and the * returned statement will invoke those methods before {@code RunDefaultServer} statement. * In the same way, if {@link AfterServerStops @AfterServerStops} methods are found in the * test class, the returned statement will cause the server to stop after this test case runs, * and will cause those methods to be invoked after that. */ protected Statement classBlock(final RunNotifier notifier) { return createClassStatement(getTestClass(), notifier, super.classBlock(notifier)); } private static Statement createClassStatement(final TestClass testClass, final RunNotifier notifier, Statement classBlock) { final RunDefaultServer defaultServerStatement = new RunDefaultServer(classBlock, notifier); Statement statement = defaultServerStatement; final List beforeServerStarts = testClass.getAnnotatedMethods(BeforeServerStarts.class); if (!beforeServerStarts.isEmpty()) { // stopServer that might be already up of we're running the full test suite instead of a single test case stopServer(); statement = new RunBefores(statement, beforeServerStarts, null); } final List afterServerStops = testClass.getAnnotatedMethods(AfterServerStops.class); if (!afterServerStops.isEmpty()) { defaultServerStatement.stopTheServerWhenDone(); statement = new RunAfters(statement, afterServerStops, null); } return statement; } public static AcceptingChannel getProxyServer() { return proxyServer; } @Override public void run(final RunNotifier notifier) { addRunNotifierListener(notifier); super.run(notifier); } private static void addRunNotifierListener(final RunNotifier notifier) { notifier.addListener(new RunListener() { @Override public void testStarted(Description description) throws Exception { DebuggingSlicePool.currentLabel = description.getClassName() + "." + description.getMethodName(); super.testStarted(description); } @Override public void testFinished(Description description) throws Exception { if (!DebuggingSlicePool.BUFFERS.isEmpty()) { try { Thread.sleep(200); long end = System.currentTimeMillis() + 20000; while (!DebuggingSlicePool.BUFFERS.isEmpty() && System.currentTimeMillis() < end) { Thread.sleep(200); } } catch (InterruptedException e) { throw new RuntimeException(e); } for (DebuggingSlicePool.DebuggingBuffer b : DebuggingSlicePool.BUFFERS) { b.getAllocationPoint().printStackTrace(); notifier.fireTestFailure(new Failure(description, new RuntimeException("Buffer Leak " + b.getLabel(), b.getAllocationPoint()))); } DebuggingSlicePool.BUFFERS.clear(); } super.testFinished(description); } }); } /** * Starts the server if it is not up, and initiates the static fields in this class. This method is invoked * automatically once before your tests are triggered when using {@code DefaultServer} as a {@code Runner}. * After this method executes, getter methods can be invoked safely. *

    * To perform an action in your test before the server starts, such as {@link #setServerOptions(OptionMap)}, * use {@link BeforeServerStarts @BeforeServerStarts} methods. To perform an action precisely after the server * starts and before the test method runs, use {@code @BeforeClass} or {@code @Before} methods. */ public static boolean startServer() { if (openssl && OPENSSL_FAILURE != null) { throw new RuntimeException(OPENSSL_FAILURE); } if (server != null) { return false; } Xnio xnio = Xnio.getInstance("nio", DefaultServer.class.getClassLoader()); try { worker = xnio.createWorker( OptionMap.builder().set(Options.WORKER_IO_THREADS, 8).set(Options.CONNECTION_HIGH_WATER, 1000000).set(Options.CONNECTION_LOW_WATER, 1000000).set(Options.WORKER_TASK_CORE_THREADS, 30).set(Options.WORKER_TASK_MAX_THREADS, 30).set(Options.TCP_NODELAY, true).set(Options.CORK, true).getMap()); serverOptions = serverOptionMapBuilder.set(Options.TCP_NODELAY, true).set(Options.BACKLOG, 1000).set(Options.REUSE_ADDRESSES, true).set(Options.BALANCING_TOKENS, 1).set(Options.BALANCING_CONNECTIONS, 2).getMap(); final SSLContext serverContext = createSSLContext(loadKeyStore(SERVER_KEY_STORE), loadKeyStore(SERVER_TRUST_STORE), false); UndertowXnioSsl ssl = new UndertowXnioSsl(worker.getXnio(), OptionMap.EMPTY, SSL_BUFFER_POOL, serverContext); if (ajp) { openListener = new AjpOpenListener(pool); acceptListener = ChannelListeners.openListenerAdapter(wrapOpenListener(openListener)); if (apache) { int port = 8888; server = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(getHostAddress(DEFAULT)), port), acceptListener, serverOptions); } else { server = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(getHostAddress(DEFAULT)), 7777 + PROXY_OFFSET), acceptListener, serverOptions); proxyOpenListener = new HttpOpenListener(pool, OptionMap.create(UndertowOptions.BUFFER_PIPELINED_DATA, true)); proxyAcceptListener = ChannelListeners.openListenerAdapter(wrapOpenListener(proxyOpenListener)); proxyServer = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(getHostAddress(DEFAULT)), getHostPort(DEFAULT)), proxyAcceptListener, serverOptions); loadBalancingProxyClient = new LoadBalancingProxyClient(GSSAPIAuthenticationMechanism.EXCLUSIVITY_CHECKER) .addHost(new URI("ajp", null, getHostAddress(DEFAULT), getHostPort(DEFAULT) + PROXY_OFFSET, "/", null, null)); ProxyHandler proxyHandler = ProxyHandler.builder() .setProxyClient(loadBalancingProxyClient) .setMaxRequestTime(120000) .setNext(HANDLE_404) .setReuseXForwarded(true) .build(); proxyOpenListener.setRootHandler(proxyHandler); proxyServer.resumeAccepts(); } } else if (h2 && isAlpnEnabled()) { openListener = new Http2OpenListener(pool, OptionMap.create(UndertowOptions.ENABLE_HTTP2, true, UndertowOptions.HTTP2_PADDING_SIZE, 10)); acceptListener = ChannelListeners.openListenerAdapter(wrapOpenListener(new AlpnOpenListener(pool).addProtocol(Http2OpenListener.HTTP2, (io.undertow.server.DelegateOpenListener) openListener, 10))); SSLContext clientContext = createSSLContext(loadKeyStore(CLIENT_KEY_STORE), loadKeyStore(CLIENT_TRUST_STORE), true); server = ssl.createSslConnectionServer(worker, new InetSocketAddress(getHostAddress("default"), 7777 + PROXY_OFFSET), acceptListener, serverOptions); server.resumeAccepts(); proxyOpenListener = new HttpOpenListener(pool, OptionMap.create(UndertowOptions.BUFFER_PIPELINED_DATA, true)); proxyAcceptListener = ChannelListeners.openListenerAdapter(wrapOpenListener(proxyOpenListener)); proxyServer = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(getHostAddress(DEFAULT)), getHostPort(DEFAULT)), proxyAcceptListener, serverOptions); loadBalancingProxyClient = new LoadBalancingProxyClient(GSSAPIAuthenticationMechanism.EXCLUSIVITY_CHECKER) .addHost(new URI("h2", null, getHostAddress(DEFAULT), getHostPort(DEFAULT) + PROXY_OFFSET, "/", null, null), null, new UndertowXnioSsl(xnio, OptionMap.EMPTY, SSL_BUFFER_POOL, clientContext), OptionMap.create(UndertowOptions.ENABLE_HTTP2, true)); ProxyHandler proxyHandler = ProxyHandler.builder() .setProxyClient(loadBalancingProxyClient) .setMaxRequestTime(120000) .setNext(HANDLE_404) .setReuseXForwarded(true) .build(); setupProxyHandlerForSSL(proxyHandler); proxyOpenListener.setRootHandler(proxyHandler); proxyServer.resumeAccepts(); } else if (h2c || h2cUpgrade) { openListener = new HttpOpenListener(pool, OptionMap.create(UndertowOptions.ENABLE_HTTP2, true, UndertowOptions.HTTP2_PADDING_SIZE, 10)); acceptListener = ChannelListeners.openListenerAdapter(wrapOpenListener(openListener)); InetSocketAddress targetAddress = new InetSocketAddress(Inet4Address.getByName(getHostAddress(DEFAULT)), getHostPort(DEFAULT) + PROXY_OFFSET); server = worker.createStreamConnectionServer(targetAddress, acceptListener, serverOptions); proxyOpenListener = new HttpOpenListener(pool, OptionMap.create(UndertowOptions.BUFFER_PIPELINED_DATA, true)); proxyAcceptListener = ChannelListeners.openListenerAdapter(wrapOpenListener(proxyOpenListener)); proxyServer = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(getHostAddress(DEFAULT)), getHostPort(DEFAULT)), proxyAcceptListener, serverOptions); loadBalancingProxyClient = new LoadBalancingProxyClient(GSSAPIAuthenticationMechanism.EXCLUSIVITY_CHECKER) .addHost(new URI(h2cUpgrade ? "http" : "h2c-prior", null, getHostAddress(DEFAULT), getHostPort(DEFAULT) + PROXY_OFFSET, "/", null, null), null, null, OptionMap.create(UndertowOptions.ENABLE_HTTP2, true)); ProxyHandler proxyHandler = ProxyHandler.builder() .setProxyClient(loadBalancingProxyClient) .setMaxRequestTime(30000) .setNext(HANDLE_404) .setReuseXForwarded(true) .build(); setupProxyHandlerForSSL(proxyHandler); proxyOpenListener.setRootHandler(proxyHandler); proxyServer.resumeAccepts(); } else if (https) { XnioSsl clientSsl = new UndertowXnioSsl(xnio, OptionMap.EMPTY, SSL_BUFFER_POOL, createClientSslContext()); openListener = new HttpOpenListener(pool, OptionMap.create(UndertowOptions.BUFFER_PIPELINED_DATA, true)); acceptListener = ChannelListeners.openListenerAdapter(wrapOpenListener(openListener)); server = ssl.createSslConnectionServer(worker, new InetSocketAddress(getHostAddress("default"), 7777 + PROXY_OFFSET), acceptListener, serverOptions); server.getAcceptSetter().set(acceptListener); server.resumeAccepts(); proxyOpenListener = new HttpOpenListener(pool, OptionMap.create(UndertowOptions.BUFFER_PIPELINED_DATA, true)); proxyAcceptListener = ChannelListeners.openListenerAdapter(wrapOpenListener(proxyOpenListener)); proxyServer = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(getHostAddress(DEFAULT)), getHostPort(DEFAULT)), proxyAcceptListener, serverOptions); loadBalancingProxyClient = new LoadBalancingProxyClient(GSSAPIAuthenticationMechanism.EXCLUSIVITY_CHECKER) .addHost(new URI("https", null, getHostAddress(DEFAULT), getHostPort(DEFAULT) + PROXY_OFFSET, "/", null, null), clientSsl); ProxyHandler proxyHandler = ProxyHandler.builder() .setProxyClient(loadBalancingProxyClient) .setMaxRequestTime(30000) .setNext(HANDLE_404) .setReuseXForwarded(true) .build(); setupProxyHandlerForSSL(proxyHandler); proxyOpenListener.setRootHandler(proxyHandler); proxyServer.resumeAccepts(); } else { if (h2) { UndertowLogger.ROOT_LOGGER.error("HTTP2 selected but Netty ALPN was not on the boot class path"); } openListener = new HttpOpenListener(pool, OptionMap.builder().set(UndertowOptions.BUFFER_PIPELINED_DATA, true).set(UndertowOptions.ENABLE_CONNECTOR_STATISTICS, true).set(UndertowOptions.REQUIRE_HOST_HTTP11, true).getMap()); acceptListener = ChannelListeners.openListenerAdapter(wrapOpenListener(openListener)); if (!proxy) { server = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(getHostAddress(DEFAULT)), getHostPort(DEFAULT)), acceptListener, serverOptions); } else { InetSocketAddress targetAddress = new InetSocketAddress(Inet4Address.getByName(getHostAddress(DEFAULT)), getHostPort(DEFAULT) + PROXY_OFFSET); server = worker.createStreamConnectionServer(targetAddress, acceptListener, serverOptions); proxyOpenListener = new HttpOpenListener(pool, OptionMap.create(UndertowOptions.BUFFER_PIPELINED_DATA, true)); proxyAcceptListener = ChannelListeners.openListenerAdapter(wrapOpenListener(proxyOpenListener)); proxyServer = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(getHostAddress(DEFAULT)), getHostPort(DEFAULT)), proxyAcceptListener, serverOptions); loadBalancingProxyClient = new LoadBalancingProxyClient(GSSAPIAuthenticationMechanism.EXCLUSIVITY_CHECKER) .addHost(new URI("http", null, getHostAddress(DEFAULT), getHostPort(DEFAULT) + PROXY_OFFSET, "/", null, null)); ProxyHandler proxyHandler = ProxyHandler.builder() .setProxyClient(loadBalancingProxyClient) .setMaxRequestTime(30000) .setNext(HANDLE_404) .setReuseXForwarded(true) .build(); setupProxyHandlerForSSL(proxyHandler); proxyOpenListener.setRootHandler(proxyHandler); proxyServer.resumeAccepts(); } } if (h2cUpgrade) { openListener.setRootHandler(new Http2UpgradeHandler(rootHandler)); } else { openListener.setRootHandler(rootHandler); } server.resumeAccepts(); } catch (Exception e) { throw new RuntimeException(e); } return true; } /** * Stops the server and resets the fields. This method is invoked automatically after all method tests have * run when using {@code DefaultServer} as a {@code Runner}. After this method executes, getter methods will * return {@code null}. *

    * To do any after test cleanup before server actually stops, use either {@code @After} or {@code @AfterClass} * methods. To perform an action after the server stops, use {@link AfterServerStops @AfterServerStops} methods. */ public static final void stopServer() { try { if (server != null) { server.close(); } stopSSLServer(); if (worker != null) { stopWorker(worker); } } catch (Exception e) { throw new RuntimeException(e); } finally { worker = null; serverOptions = null; openListener = null; acceptListener = null; server = null; proxyOpenListener = null; proxyAcceptListener = null; proxyServer = null; } } @Override protected Description describeChild(FrameworkMethod method) { if (runs > 1 && method.getAnnotation(Ignore.class) == null) { return describeRepeatTest(method); } return super.describeChild(method); } private Description describeRepeatTest(FrameworkMethod method) { Description description = Description.createSuiteDescription( testName(method) + " [" + runs + " times]", method.getAnnotations()); for (int i = 1; i <= runs; i++) { description.addChild(Description.createTestDescription( getTestClass().getJavaClass(), "[" + i + "] " + testName(method))); } return description; } private static ChannelListener wrapOpenListener(final ChannelListener listener) { if (!single) { return listener; } return (StreamConnection channel) -> { channel.getSinkChannel().setConduit(new SingleByteStreamSinkConduit(channel.getSinkChannel().getConduit(), 10000)); channel.getSourceChannel().setConduit(new SingleByteStreamSourceConduit(channel.getSourceChannel().getConduit(), 10000)); listener.handleEvent(channel); }; } @Override protected void runChild(FrameworkMethod method, RunNotifier notifier) { AjpIgnore ajpIgnore = method.getAnnotation(AjpIgnore.class); if (ajpIgnore == null) { ajpIgnore = method.getMethod().getDeclaringClass().getAnnotation(AjpIgnore.class); } if (ajp && ajpIgnore != null) { if (apache || !ajpIgnore.apacheOnly()) { notifier.fireTestIgnored(describeChild(method)); return; } } if (h2 || h2c || ajp || h2cUpgrade) { //h2c-upgrade we still allow HTTP1 HttpOneOnly httpOneOnly = method.getAnnotation(HttpOneOnly.class); if (httpOneOnly == null) { httpOneOnly = method.getMethod().getDeclaringClass().getAnnotation(HttpOneOnly.class); } if (httpOneOnly != null) { notifier.fireTestIgnored(describeChild(method)); return; } if (h2) { assumeAlpnEnabled(); } } if (https) { HttpsIgnore httpsIgnore = method.getAnnotation(HttpsIgnore.class); if (httpsIgnore == null) { httpsIgnore = method.getMethod().getDeclaringClass().getAnnotation(HttpsIgnore.class); } if (httpsIgnore != null) { notifier.fireTestIgnored(describeChild(method)); return; } } if (isProxy()) { if (method.getAnnotation(ProxyIgnore.class) != null || method.getMethod().getDeclaringClass().isAnnotationPresent(ProxyIgnore.class) || getTestClass().getJavaClass().isAnnotationPresent(ProxyIgnore.class)) { notifier.fireTestIgnored(describeChild(method)); return; } } if (ipv6) { if (method.getAnnotation(IPv6Ignore.class) != null || method.getMethod().getDeclaringClass().isAnnotationPresent(IPv6Ignore.class) || getTestClass().getJavaClass().isAnnotationPresent(IPv6Ignore.class)) { notifier.fireTestIgnored(describeChild(method)); return; } } else { if (method.getAnnotation(IPv6Only.class) != null || method.getMethod().getDeclaringClass().isAnnotationPresent(IPv6Only.class) || getTestClass().getJavaClass().isAnnotationPresent(IPv6Only.class)) { notifier.fireTestIgnored(describeChild(method)); return; } } try { if (runs > 1) { Statement statement = methodBlock(method); Description description = describeChild(method); for (Description desc : description.getChildren()) { runLeaf(statement, desc, notifier); } } else { super.runChild(method, notifier); } } finally { TestHttpClient.afterTest(); } } @Override protected String testName(FrameworkMethod method) { if (!isProxy()) { return super.testName(method); } else { StringBuilder sb = new StringBuilder(super.testName(method)); if (isProxy()) { sb.append("[proxy]"); } if (ajp) { sb.append("[ajp]"); } if (https) { sb.append("[https]"); } if (h2) { sb.append("[http2]"); } if (h2c) { sb.append("[http2-clear]"); } if (h2cUpgrade) { sb.append("[http2-clear-upgrade]"); } if (ipv6) { sb.append("[ipv6]"); } return sb.toString(); } } /** * Sets the root handler for the default web server * * @param handler The handler to use */ public static void setRootHandler(HttpHandler handler) { if ((isProxy()) && !ajp) { //if we are testing HTTP proxy we always add the SSLHeaderHandler //this allows the SSL information to be propagated to the backend handler = new SSLHeaderHandler(new ProxyPeerAddressHandler(handler)); } if (dump) { rootHandler.next = new RequestDumpingHandler(handler); } else { rootHandler.next = handler; } } /** * When using the default SSL settings returns the corresponding client context. *

    * If a test case is initialising a custom server side SSLContext then the test case will be responsible for creating it's * own client side. * * @return The client side SSLContext. */ public static SSLContext getClientSSLContext() { if (clientSslContext == null) { clientSslContext = createClientSslContext(); } return clientSslContext; } /** * Start the SSL server using the default settings. *

    * The default settings initialise a server with a key for 'localhost' and a trust store containing the certificate of a * single client, the client authentication mode is set to 'REQUESTED' to optionally allow progression to CLIENT-CERT * authentication. */ public static void startSSLServer() throws IOException { SSLContext serverContext = getServerSslContext(); getClientSSLContext(); startSSLServer(serverContext, OptionMap.create(SSL_CLIENT_AUTH_MODE, REQUESTED, Options.SSL_ENABLED_PROTOCOLS, Sequence.of("TLSv1.2"))); } public static SSLContext createClientSslContext() { return createClientSslContext("TLSv1.2"); } public static SSLContext createClientSslContext(String protocol) { try { return createSSLContext(loadKeyStore(CLIENT_KEY_STORE), loadKeyStore(CLIENT_TRUST_STORE), protocol, true); } catch (IOException e) { throw new RuntimeException(e); } } public static SSLContext getServerSslContext() { try { return createSSLContext(loadKeyStore(SERVER_KEY_STORE), loadKeyStore(SERVER_TRUST_STORE), false); } catch (IOException e) { throw new RuntimeException(e); } } /** * Start the SSL server using the default ssl context and the provided option map *

    * The default settings initialise a server with a key for 'localhost' and a trust store containing the certificate of a * single client. Client cert mode is not set by default */ public static void startSSLServer(OptionMap optionMap) throws IOException { clientSslContext = createClientSslContext(); startSSLServer(optionMap, proxyAcceptListener != null ? proxyAcceptListener : acceptListener); } /** * Start the SSL server using the default ssl context and the provided option map *

    * The default settings initialise a server with a key for 'localhost' and a trust store containing the certificate of a * single client. Client cert mode is not set by default */ public static void startSSLServer(OptionMap optionMap, ChannelListener openListener) throws IOException { SSLContext serverContext = createSSLContext(loadKeyStore(SERVER_KEY_STORE), loadKeyStore(SERVER_TRUST_STORE), false); clientSslContext = createSSLContext(loadKeyStore(CLIENT_KEY_STORE), loadKeyStore(CLIENT_TRUST_STORE), true); startSSLServer(serverContext, optionMap, openListener); } /** * Start the SSL server using a custom SSLContext with additional options to pass to the JsseXnioSsl instance. * * @param context - The SSLContext to use for JsseXnioSsl initialisation. * @param options - Additional options to be passed to the JsseXnioSsl, this will be merged with the default options where * applicable. */ public static void startSSLServer(final SSLContext context, final OptionMap options) throws IOException { startSSLServer(context, options, proxyAcceptListener != null ? proxyAcceptListener : acceptListener); } /** * Start the SSL server using a custom SSLContext with additional options to pass to the JsseXnioSsl instance. * * @param context - The SSLContext to use for JsseXnioSsl initialisation. * @param options - Additional options to be passed to the JsseXnioSsl, this will be merged with the default options where * applicable. */ public static void startSSLServer(final SSLContext context, final OptionMap options, ChannelListener openListener) throws IOException { startSSLServer(context, options, openListener, getHostSSLPort(DEFAULT)); } /** * Start the SSL server using a custom SSLContext with additional options to pass to the JsseXnioSsl instance. * * @param context - The SSLContext to use for JsseXnioSsl initialisation. * @param options - Additional options to be passed to the JsseXnioSsl, this will be merged with the default options where * applicable. */ public static void startSSLServer(final SSLContext context, final OptionMap options, ChannelListener openListener, int port) throws IOException { if (isApacheTest()) { return; } OptionMap combined = OptionMap.builder().addAll(serverOptions).addAll(options) .set(Options.USE_DIRECT_BUFFERS, true) .getMap(); UndertowXnioSsl ssl = new UndertowXnioSsl(worker.getXnio(), OptionMap.EMPTY, SSL_BUFFER_POOL, context); sslServer = ssl.createSslConnectionServer(worker, new InetSocketAddress(getHostAddress("default"), port), openListener, combined); sslServer.getAcceptSetter().set(openListener); sslServer.resumeAccepts(); } private static boolean isApacheTest() { return apache; } /** * Stop any previously created SSL server - as this is for test clean up calling when no SSL server is running will not * cause an error. */ public static void stopSSLServer() throws IOException { boolean shuttingDown = false; if (sslServer != null) { sslServer.close(); sslServer = null; shuttingDown = true; } clientSslContext = null; if (proxyOpenListener != null) { proxyOpenListener.closeConnections(); shuttingDown = true; } else if (openListener != null) { openListener.closeConnections(); shuttingDown = true; } if (shuttingDown) { // TODO replace this by the mechanism described in UNDERTOW-1648 once it is implemented waitWorkerRunnableCycle(worker); } } public static String getHostAddress(String serverName) { return System.getProperty(serverName + ".server.address", "localhost"); } public static String getHostAddress() { return getHostAddress(DEFAULT); } public static int getHostPort(String serverName) { if (isApacheTest()) { return APACHE_PORT; } return Integer.getInteger(serverName + ".server.port", 7777); } public static int getHostPort() { return getHostPort(DEFAULT); } public static int getHostSSLPort(String serverName) { if (isApacheTest()) { return APACHE_SSL_PORT; } return Integer.getInteger(serverName + ".server.sslPort", 7778); } public static OptionMap getUndertowOptions() { return openListener.getUndertowOptions(); } public static void setUndertowOptions(final OptionMap options) { OptionMap.Builder builder = OptionMap.builder().addAll(options); if (h2c) { builder.set(UndertowOptions.ENABLE_HTTP2, true); } if (openListener != null) { openListener.setUndertowOptions(builder.getMap()); openListener.closeConnections(); if (proxyOpenListener != null) { proxyOpenListener.closeConnections(); } if (loadBalancingProxyClient != null) { loadBalancingProxyClient.closeCurrentConnections(); } } } public static void setServerOptions(final OptionMap options) { serverOptionMapBuilder = OptionMap.builder().addAll(options); } public static XnioWorker getWorker() { return worker; } /** * Runner that works in the same way as {@link DefaultServer} with added support to * parameterized tests. */ public static class Parameterized extends org.junit.runners.Parameterized { public Parameterized(Class klass) throws Throwable { super(klass); } @Override public void run(final RunNotifier notifier) { addRunNotifierListener(notifier); super.run(notifier); } @Override protected Statement classBlock(RunNotifier notifier) { return createClassStatement(getTestClass(), notifier, super.classBlock(notifier)); } } public static boolean isAjp() { return ajp; } public static boolean isProxy() { return proxy || https || h2 || h2c || ajp || h2cUpgrade; } public static boolean isHttps() { return https; } public static boolean isH2() { return h2 || h2c || h2cUpgrade; } public static boolean isH2upgrade() { return h2cUpgrade; } public static boolean isIpv6() { return ipv6; } /** * The root handler is tied to the connection, and AJP can re-use connections for different tests, so we * use a delegating handler to chance the next handler after the root. *

    * TODO: should we re-read the root handler for every request? */ private static final class DelegatingHandler implements HttpHandler { volatile HttpHandler next; @Override public void handleRequest(HttpServerExchange exchange) throws Exception { next.handleRequest(exchange); } } private static Boolean alpnEnabled; private static boolean isAlpnEnabled() { if (alpnEnabled == null) { //we use the client context, as the server one is wrapped by a SNISSLEngine //so we can't tell that ALPN is enabled or now SSLEngine engine = getClientSSLContext().createSSLEngine(); ALPNProvider provider = ALPNManager.INSTANCE.getProvider(engine); if (provider instanceof JettyAlpnProvider) { alpnEnabled = System.getProperty("alpn-boot-string") != null; } else { alpnEnabled = provider != null; } } return alpnEnabled; } public static void assumeAlpnEnabled() { Assume.assumeTrue(isAlpnEnabled()); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/testutils/HttpClientUtils.java000066400000000000000000000053771420065311100304050ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.testutils; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; /** * @author Stuart Douglas */ public class HttpClientUtils { private HttpClientUtils() { } public static String readResponse(final HttpResponse response) throws IOException { return readResponse(response, StandardCharsets.UTF_8); } public static String readResponse(final HttpResponse response, final Charset charset) throws IOException { HttpEntity entity = response.getEntity(); if(entity == null) { return ""; } return readResponse(entity.getContent(), charset); } public static String readResponse(InputStream stream) throws IOException { byte[] data = new byte[100]; int read; ByteArrayOutputStream out = new ByteArrayOutputStream(); while ((read = stream.read(data)) != -1) { out.write(data, 0, read); } return new String(out.toByteArray(), StandardCharsets.UTF_8); } public static String readResponse(final InputStream stream, final Charset charset) throws IOException { byte[] data = new byte[100]; int read; ByteArrayOutputStream out = new ByteArrayOutputStream(); while ((read = stream.read(data)) != -1) { out.write(data, 0, read); } return new String(out.toByteArray(), charset); } public static byte[] readRawResponse(final HttpResponse response) throws IOException { return readRawResponse(response.getEntity().getContent()); } public static byte[] readRawResponse(InputStream stream) throws IOException { final ByteArrayOutputStream b = new ByteArrayOutputStream(); byte[] data = new byte[100]; int read; while ((read = stream.read(data)) != -1) { b.write(data, 0, read); } return b.toByteArray(); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/testutils/HttpOneOnly.java000066400000000000000000000021241420065311100275140ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.testutils; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Marks a test as only applicable to HTTP 1, so it will be ignored for other transports * * @author Stuart Douglas */ @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface HttpOneOnly { String value() default ""; } undertow-2.2.16.Final/core/src/test/java/io/undertow/testutils/HttpsIgnore.java000066400000000000000000000017701420065311100275450ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.testutils; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * @author Stuart Douglas */ @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface HttpsIgnore { String value() default ""; } undertow-2.2.16.Final/core/src/test/java/io/undertow/testutils/IPv6Ignore.java000066400000000000000000000017261420065311100272300ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.testutils; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Retention(RUNTIME) @Inherited public @interface IPv6Ignore { String value() default ""; } undertow-2.2.16.Final/core/src/test/java/io/undertow/testutils/IPv6Only.java000066400000000000000000000017241420065311100267240ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.testutils; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Retention(RUNTIME) @Inherited public @interface IPv6Only { String value() default ""; } undertow-2.2.16.Final/core/src/test/java/io/undertow/testutils/ProxyIgnore.java000066400000000000000000000017741420065311100275700ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.testutils; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * * @author Stuart Douglas */ @Retention(RUNTIME) @Inherited public @interface ProxyIgnore { String value() default ""; } undertow-2.2.16.Final/core/src/test/java/io/undertow/testutils/RunDefaultServer.java000066400000000000000000000055161420065311100305410ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.testutils; import org.junit.runner.Result; import org.junit.runner.notification.RunListener; import org.junit.runner.notification.RunNotifier; import org.junit.runners.model.Statement; /** * This statement wraps another statement and works like an around-type hook on top * of the wrapped statement, starting the server before the wrapped statement runs * and stopping the server at some point afterwords. Notice the server is started * only if it not already up. * * @author Flavia Rainone */ public class RunDefaultServer extends Statement { private final Statement next; private final RunNotifier runNotifier; private boolean stopTheServer; /** * Constructor. * * @param next wrapped statement * @param runNotifier run notifier for current test run */ RunDefaultServer(final Statement next, final RunNotifier runNotifier) { this.next = next; this.runNotifier = runNotifier; } /** * If invoked, causes the server to be stopped immediately after the wrapped statement * is executed. *
    * If not invoked, the server will be programmed to be stopped only after the whole * test run is finished, via a {@code RunListener}. */ public void stopTheServerWhenDone() { this.stopTheServer = true; } @Override public void evaluate() throws Throwable { if (DefaultServer.startServer() && !stopTheServer) { runNotifier.addListener(new RunListener() { @Override public void testRunFinished(final Result result) { // TODO if need arises in the future, add a @StopServerAfterClass class annotation to DefaultServer // (this annotation will cause the server to shutdown after class runs regardless of whether // there is an @AfterServerStops method in the class DefaultServer.stopServer(); } }); } try { next.evaluate(); } finally { if (stopTheServer) { DefaultServer.stopServer(); } } } }StopServerWithExternalWorkerUtils.java000066400000000000000000000124071420065311100341050ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/testutils/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.testutils; import java.net.BindException; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import io.undertow.Undertow; import org.xnio.XnioWorker; import static org.junit.Assert.fail; /** *

    * When an Undertow Server has an internal worker (i.e. a {@link org.xnio.XnioWorker} * that is created upon start if no worker is {@link io.undertow.Undertow.Builder#setWorker(XnioWorker) * provided} to the builder), the worker is automatically closed with the server when it * {@link Undertow#stop() stops}. This operation blocks until the worker is finished, and this is * usually enough to guarantee all ports are freed when the operation returns, making it possible * to start another server associated to the same address. *

    *

    * If that is not the case, we say the worker is external to the server, provided to the Builder via * {@link io.undertow.Undertow.Builder#setWorker(XnioWorker)}. In this case, there is no way to * wait on the sockets closing when stopping the server, and in a test environment this means that * a series of opening/closing servers could lead to a {@link BindException} if a starting server tries * to bind to the ports used by a closing server before its address is released. *

    *

    To prevent that error, we need to perform some extra actions, such as {@link * StopServerWithExternalWorkerUtils#stopServerAndWorker(Undertow) closing } both the server * and the worker whenever possible, or {@link * StopServerWithExternalWorkerUtils#waitWorkerRunnableCycle(XnioWorker) waiting} for a full cycle * of tasks to be executed in the {@link XnioWorker} plus a short period of sleep to avoid the time * window in which the closing server is still bound to the socket address. *

    * * @see io.undertow.Undertow.Builder#setWorker(XnioWorker) * @see #stopServerAndWorker(Undertow) * @see #stopServer(Undertow) * * @author Flavia Rainone */ public class StopServerWithExternalWorkerUtils { private StopServerWithExternalWorkerUtils() {} /** * Stops the server and the external worker associated with it. Blocks until * the worker has fully stopped. * * @param server the Undertow server */ public static void stopServerAndWorker(Undertow server) { final XnioWorker worker = server.getWorker(); server.stop(); stopWorker(worker); } /** * Stops the worker and waits until it is shutdown. This operation is not * asynchronous and will block until the worker has fully stopped. * * @param worker the XnioWorker */ public static void stopWorker(XnioWorker worker) { worker.shutdown(); try { if (!worker.awaitTermination(10, TimeUnit.SECONDS)) { List tasks = worker.shutdownNow(); for (Runnable task: tasks) task.run(); if (!worker.awaitTermination(10, TimeUnit.SECONDS)) throw new IllegalStateException("Worker failed to shutdown within ten seconds"); } } catch (InterruptedException e) { e.printStackTrace(); fail(e.getMessage()); } } /** * Stops only the server, keeping its external worker up and unchanged. After the server * is stopped, waits for a full worker runnable cycle to complete, plus a sleep time, to * prevent the time window in which the closing server is still bound to the associated * address. * This operation blocks until the worker finishes executing an empty Runnable task. * * @param server the Undertow server */ public static void stopServer(Undertow server) { final XnioWorker worker = server.getWorker(); server.stop(); waitWorkerRunnableCycle(worker); } /** * Waits for a full worker runnable cycle to complete, plus a sleep time, to prevent the * time window in which any closing server could be still bound to its associated address. * This operation blocks until the worker finishes executing an empty Runnable task. * * @param worker the XnioWorker */ public static void waitWorkerRunnableCycle(XnioWorker worker) { CountDownLatch serverShutdownLatch = new CountDownLatch(1); worker.getIoThread().execute(serverShutdownLatch::countDown); //some environments seem to need a small delay to re-bind the socket try { serverShutdownLatch.await(); Thread.sleep(1000); } catch (InterruptedException e) { //ignore } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/testutils/TestHttpClient.java000066400000000000000000000112211420065311100302050ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.testutils; import org.apache.http.client.HttpRequestRetryHandler; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.conn.ssl.X509HostnameVerifier; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.apache.http.params.SyncBasicHttpParams; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import java.io.IOException; import java.security.cert.X509Certificate; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** * @author Stuart Douglas */ public class TestHttpClient extends DefaultHttpClient { private static final X509HostnameVerifier NO_OP_VERIFIER = new X509HostnameVerifier() { @Override public void verify(String host, SSLSocket ssl) throws IOException { } @Override public void verify(String host, X509Certificate cert) throws SSLException { } @Override public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException { } @Override public boolean verify(String s, SSLSession sslSession) { return true; } }; private static final List instances = new CopyOnWriteArrayList<>(); public TestHttpClient() { super(preventSocketTimeoutException(null)); instances.add(this); } public TestHttpClient(HttpParams params) { super(preventSocketTimeoutException(params)); instances.add(this); } public TestHttpClient(ClientConnectionManager conman) { super(conman, preventSocketTimeoutException(null)); instances.add(this); } public TestHttpClient(ClientConnectionManager conman, HttpParams params) { super(conman, preventSocketTimeoutException(params)); instances.add(this); } private static HttpParams preventSocketTimeoutException(HttpParams params) { // UNDERTOW-1929 prevent the SocketTimeoutException that we see recurring // in CI when running tests on proxy mode if (DefaultServer.isProxy()) { if (params == null) { params = new SyncBasicHttpParams(); setDefaultHttpParams(params); } HttpConnectionParams.setSoTimeout(params, 300000); return params; } return params; } @Override protected HttpRequestRetryHandler createHttpRequestRetryHandler() { return new DefaultHttpRequestRetryHandler(0, false); } @Override protected HttpParams createHttpParams() { HttpParams params = super.createHttpParams(); HttpConnectionParams.setSoTimeout(params, 300000); return params; } public void setSSLContext(final SSLContext sslContext) { SchemeRegistry registry = getConnectionManager().getSchemeRegistry(); registry.unregister("https"); if (DefaultServer.getHostAddress(DefaultServer.DEFAULT).equals("localhost")) { registry.register(new Scheme("https", 443, new SSLSocketFactory(sslContext))); registry.register(new Scheme("https", DefaultServer.getHostSSLPort("default"), new SSLSocketFactory(sslContext))); } else { registry.register(new Scheme("https", 443, new SSLSocketFactory(sslContext, NO_OP_VERIFIER))); registry.register(new Scheme("https", DefaultServer.getHostSSLPort("default"), new SSLSocketFactory(sslContext, NO_OP_VERIFIER))); } } public static void afterTest() { for(TestHttpClient i : instances) { i.getConnectionManager().shutdown(); } instances.clear(); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/testutils/category/000077500000000000000000000000001420065311100262445ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/testutils/category/FunctionalTest.java000066400000000000000000000002261420065311100320510ustar00rootroot00000000000000package io.undertow.testutils.category; /** * Marker class used by JUnit categories representing unit tests */ public interface FunctionalTest { } undertow-2.2.16.Final/core/src/test/java/io/undertow/testutils/category/UnitTest.java000066400000000000000000000002201420065311100306600ustar00rootroot00000000000000package io.undertow.testutils.category; /** * Marker class used by JUnit categories representing unit tests */ public interface UnitTest { } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/000077500000000000000000000000001420065311100233445ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/util/ByteRangeTestCase.java000066400000000000000000000257641420065311100275410ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import org.junit.Assert; import org.junit.Test; public class ByteRangeTestCase { @Test public void testGetRanges() { ByteRange byteRange = new ByteRange( new ArrayList<>(Arrays.asList( new ByteRange.Range(3, 5), new ByteRange.Range(4, 8), new ByteRange.Range(3, 9)))); Assert.assertEquals(3, byteRange.getRanges()); } @Test public void testGetStart() { ByteRange byteRange = new ByteRange( new ArrayList<>(Arrays.asList( new ByteRange.Range(3, 5), new ByteRange.Range(4, 8), new ByteRange.Range(3, 9)))); Assert.assertEquals(3, byteRange.getStart(0)); Assert.assertEquals(4, byteRange.getStart(1)); Assert.assertEquals(3, byteRange.getStart(2)); } @Test public void testGetEnd() { ByteRange byteRange = new ByteRange( new ArrayList<>(Arrays.asList( new ByteRange.Range(3, 5), new ByteRange.Range(4, 8), new ByteRange.Range(3, 9)))); Assert.assertEquals(5, byteRange.getEnd(0)); Assert.assertEquals(8, byteRange.getEnd(1)); Assert.assertEquals(9, byteRange.getEnd(2)); } @Test public void testParse() { Assert.assertNull(ByteRange.parse(null)); Assert.assertNull(ByteRange.parse("foo")); Assert.assertNull(ByteRange.parse("bytes=1")); Assert.assertNull(ByteRange.parse("bytes=a-")); Assert.assertNull(ByteRange.parse("foobarbaz")); Assert.assertNull(ByteRange.parse("bytes=--1")); Assert.assertEquals(1, ByteRange.parse("bytes=2-").getRanges()); Assert.assertEquals(1, ByteRange.parse("bytes=-20").getRanges()); } @Test public void testGetResponseResult1() { ByteRange byteRange = new ByteRange( new ArrayList<>(Arrays.asList( new ByteRange.Range(3, 5), new ByteRange.Range(4, 8), new ByteRange.Range(3, 9)))); Assert.assertNull(byteRange.getResponseResult(0, "\"1\"", new Date(1559820153000L), "foo")); Assert.assertNull(byteRange.getResponseResult(0, "Mon, 31 Mar 2014 09:24:49 GMT", new Date(1559820153000L), "foo")); } @Test public void testGetResponseResult2() { ByteRange byteRange = new ByteRange( new ArrayList<>(Arrays.asList(new ByteRange.Range(-1, -1)))); Assert.assertEquals(0, byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getStart()); Assert.assertEquals(0, byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getEnd()); Assert.assertEquals(0, byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getContentLength()); Assert.assertEquals("bytes */0", byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getContentRange()); Assert.assertEquals(416, byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getStatusCode()); Assert.assertEquals(0, byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getStart()); Assert.assertEquals(0, byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getEnd()); Assert.assertEquals(0, byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getContentLength()); Assert.assertEquals("bytes */6", byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getContentRange()); Assert.assertEquals(416, byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getStatusCode()); } @Test public void testGetResponseResult3() { ByteRange byteRange = new ByteRange( new ArrayList<>(Arrays.asList(new ByteRange.Range(5, -1)))); Assert.assertEquals(0, byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getStart()); Assert.assertEquals(0, byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getEnd()); Assert.assertEquals(0, byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getContentLength()); Assert.assertEquals("bytes */0", byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getContentRange()); Assert.assertEquals(416, byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getStatusCode()); Assert.assertEquals(5, byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getStart()); Assert.assertEquals(5, byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getEnd()); Assert.assertEquals(1, byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getContentLength()); Assert.assertEquals("bytes 5-5/6", byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getContentRange()); Assert.assertEquals(206, byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getStatusCode()); } @Test public void testGetResponseResult4() { ByteRange byteRange = new ByteRange( new ArrayList<>(Arrays.asList(new ByteRange.Range(0, -1)))); Assert.assertEquals(0, byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getStart()); Assert.assertEquals(0, byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getEnd()); Assert.assertEquals(0, byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getContentLength()); Assert.assertEquals("bytes */0", byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getContentRange()); Assert.assertEquals(416, byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getStatusCode()); Assert.assertEquals(0, byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getStart()); Assert.assertEquals(5, byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getEnd()); Assert.assertEquals(6, byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getContentLength()); Assert.assertEquals("bytes 0-5/6", byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getContentRange()); Assert.assertEquals(206, byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getStatusCode()); } @Test public void testGetResponseResult5() { ByteRange byteRange = new ByteRange( new ArrayList<>(Arrays.asList(new ByteRange.Range(3, 5)))); Assert.assertEquals(0, byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getStart()); Assert.assertEquals(0, byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getEnd()); Assert.assertEquals(0, byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getContentLength()); Assert.assertEquals("bytes */0", byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getContentRange()); Assert.assertEquals(416, byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getStatusCode()); Assert.assertEquals(3, byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getStart()); Assert.assertEquals(5, byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getEnd()); Assert.assertEquals(3, byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getContentLength()); Assert.assertEquals("bytes 3-5/6", byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getContentRange()); Assert.assertEquals(206, byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getStatusCode()); } @Test public void testGetResponseResult6() { ByteRange byteRange = new ByteRange( new ArrayList<>(Arrays.asList(new ByteRange.Range(-1, 5)))); Assert.assertEquals(0, byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getStart()); Assert.assertEquals(-1, byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getEnd()); Assert.assertEquals(0, byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getContentLength()); Assert.assertEquals("bytes 0--1/0", byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getContentRange()); Assert.assertEquals(206, byteRange.getResponseResult(0, null, new Date(1559820153000L), "foo").getStatusCode()); Assert.assertEquals(1, byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getStart()); Assert.assertEquals(5, byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getEnd()); Assert.assertEquals(5, byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getContentLength()); Assert.assertEquals("bytes 1-5/6", byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getContentRange()); Assert.assertEquals(206, byteRange.getResponseResult(6, null, new Date(1559820153000L), "foo").getStatusCode()); } @Test public void testGetResponseResultNull() { ByteRange byteRange = new ByteRange(new ArrayList<>()); Assert.assertNull(byteRange.getResponseResult(0, "1", new Date(1559820153000L), "foo")); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/CanonicalPathUtilsTestCase.java000066400000000000000000000145261420065311100314000ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.testutils.category.UnitTest; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; /** * Tests canonicalization of the path * * @author Stuart Douglas */ @Category(UnitTest.class) public class CanonicalPathUtilsTestCase { @Test public void testCanonicalization() { //these strings should not be touched Assert.assertSame("a/b/c", CanonicalPathUtils.canonicalize("a/b/c")); Assert.assertSame("a/b/c/", CanonicalPathUtils.canonicalize("a/b/c/")); Assert.assertSame("aaaaa", CanonicalPathUtils.canonicalize("aaaaa")); //these strings should result in the same string being output Assert.assertEquals("a./b", CanonicalPathUtils.canonicalize("a./b")); Assert.assertEquals("a./.b", CanonicalPathUtils.canonicalize("a./.b")); //removing double slash Assert.assertEquals("a/b", CanonicalPathUtils.canonicalize("a//b")); Assert.assertEquals("a/b", CanonicalPathUtils.canonicalize("a///b")); Assert.assertEquals("a/b", CanonicalPathUtils.canonicalize("a////b")); //removing /./ Assert.assertEquals("a/b", CanonicalPathUtils.canonicalize("a/./b")); Assert.assertEquals("a/b", CanonicalPathUtils.canonicalize("a/././b")); Assert.assertEquals("a/b/c", CanonicalPathUtils.canonicalize("a/./b/./c")); Assert.assertEquals("a/b", CanonicalPathUtils.canonicalize("a/./././b")); Assert.assertEquals("a/b/", CanonicalPathUtils.canonicalize("a/./././b/./")); Assert.assertEquals("a/b", CanonicalPathUtils.canonicalize("a/./././b/.")); //dealing with /../ Assert.assertEquals("/b", CanonicalPathUtils.canonicalize("/a/../b")); Assert.assertEquals("/b", CanonicalPathUtils.canonicalize("/a/../c/../e/../b")); Assert.assertEquals("/b", CanonicalPathUtils.canonicalize("/a/c/../../b")); // out of servlet context Assert.assertNull(CanonicalPathUtils.canonicalize("/a/../..", true)); Assert.assertNull(CanonicalPathUtils.canonicalize("/a/../../foo", true)); Assert.assertNull(CanonicalPathUtils.canonicalize("/../../a/b/bar", true)); Assert.assertEquals("/", CanonicalPathUtils.canonicalize("/a/../..")); Assert.assertEquals("/foo", CanonicalPathUtils.canonicalize("/a/../../foo")); //preserve (single) trailing / Assert.assertEquals("/a/", CanonicalPathUtils.canonicalize("/a/")); Assert.assertEquals("/", CanonicalPathUtils.canonicalize("/")); Assert.assertEquals("/bbb/a", CanonicalPathUtils.canonicalize("/cc/../bbb/a/.")); Assert.assertEquals("/aaa/bbb/", CanonicalPathUtils.canonicalize("/aaa/bbb//////")); } @Test public void testCanonicalizationBackslash() { //these strings should not be touched Assert.assertSame("a\\b\\c", CanonicalPathUtils.canonicalize("a\\b\\c")); Assert.assertSame("a\\b\\c\\", CanonicalPathUtils.canonicalize("a\\b\\c\\")); Assert.assertSame("aaaaa", CanonicalPathUtils.canonicalize("aaaaa")); //these strings should result in the same string being output Assert.assertEquals("a.\\b", CanonicalPathUtils.canonicalize("a.\\b")); Assert.assertEquals("a.\\.b", CanonicalPathUtils.canonicalize("a.\\.b")); //removing double slash Assert.assertEquals("a\\b", CanonicalPathUtils.canonicalize("a\\\\b")); Assert.assertEquals("a\\b", CanonicalPathUtils.canonicalize("a\\\\\\b")); Assert.assertEquals("a\\b", CanonicalPathUtils.canonicalize("a\\\\\\\\b")); //removing \.\ Assert.assertEquals("a\\b", CanonicalPathUtils.canonicalize("a\\.\\b")); Assert.assertEquals("a\\b", CanonicalPathUtils.canonicalize("a\\.\\.\\b")); Assert.assertEquals("a\\b\\c", CanonicalPathUtils.canonicalize("a\\.\\b\\.\\c")); Assert.assertEquals("a\\b", CanonicalPathUtils.canonicalize("a\\.\\.\\.\\b")); Assert.assertEquals("a\\b\\", CanonicalPathUtils.canonicalize("a\\.\\.\\.\\b\\.\\")); Assert.assertEquals("a\\b", CanonicalPathUtils.canonicalize("a\\.\\.\\.\\b\\.")); //dealing with \..\ Assert.assertEquals("\\b", CanonicalPathUtils.canonicalize("\\a\\..\\b")); Assert.assertEquals("\\b", CanonicalPathUtils.canonicalize("\\a\\..\\c\\..\\e\\..\\b")); Assert.assertEquals("\\b", CanonicalPathUtils.canonicalize("\\a\\c\\..\\..\\b")); // out of servlet context Assert.assertNull(CanonicalPathUtils.canonicalize("\\a\\..\\..", true)); Assert.assertNull(CanonicalPathUtils.canonicalize("\\a\\..\\..\\foo", true)); Assert.assertNull(CanonicalPathUtils.canonicalize("\\..\\..\\a\\b\\bar", true)); Assert.assertEquals("/", CanonicalPathUtils.canonicalize("\\a\\..\\..")); Assert.assertEquals("\\foo", CanonicalPathUtils.canonicalize("\\a\\..\\..\\foo")); //preserve (single) trailing \ Assert.assertEquals("\\a\\", CanonicalPathUtils.canonicalize("\\a\\")); Assert.assertEquals("\\", CanonicalPathUtils.canonicalize("\\")); Assert.assertEquals("\\bbb\\a", CanonicalPathUtils.canonicalize("\\cc\\..\\bbb\\a\\.")); Assert.assertEquals("\\aaa\\bbb\\", CanonicalPathUtils.canonicalize("\\aaa\\bbb\\\\\\\\\\\\")); //test mixtures of both forward and back slash Assert.assertEquals("/", CanonicalPathUtils.canonicalize("\\a/..\\./")); Assert.assertEquals("\\a/", CanonicalPathUtils.canonicalize("\\a\\b\\..\\./")); Assert.assertEquals("/a/b/c../d..\\", CanonicalPathUtils.canonicalize("/a/b/c../d..\\")); Assert.assertEquals("/a/d\\", CanonicalPathUtils.canonicalize("/a/b/c/..\\../d\\.\\")); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/CompletionLatchHandler.java000066400000000000000000000042221420065311100305720ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * @author Stuart Douglas */ public class CompletionLatchHandler implements HttpHandler { private final HttpHandler next; private volatile CountDownLatch latch; public CompletionLatchHandler(HttpHandler next) { this.next = next; latch = new CountDownLatch(1); } public CompletionLatchHandler(int size, HttpHandler next) { this.next = next; latch = new CountDownLatch(size); } public void await() { try { latch.await(10, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); } } public void reset() { this.latch = new CountDownLatch(1); } public void reset(int size) { this.latch = new CountDownLatch(size); } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.addExchangeCompleteListener(new ExchangeCompletionListener() { @Override public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { latch.countDown(); nextListener.proceed(); } }); next.handleRequest(exchange); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/ContentTypeParsingTestCase.java000066400000000000000000000041171420065311100314460ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.testutils.category.UnitTest; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; /** * @author Stuart Douglas */ @Category(UnitTest.class) public class ContentTypeParsingTestCase { @Test public void testCharsetParsing() { Assert.assertEquals(null, Headers.extractQuotedValueFromHeader("text/html; other-data=\"charset=UTF-8\"", "charset")); Assert.assertEquals(null, Headers.extractQuotedValueFromHeader("text/html;", "charset")); Assert.assertEquals("UTF-8", Headers.extractQuotedValueFromHeader("text/html; charset=\"UTF-8\"", "charset")); Assert.assertEquals("UTF-8", Headers.extractQuotedValueFromHeader("text/html; charset=UTF-8", "charset")); Assert.assertEquals("UTF-8", Headers.extractQuotedValueFromHeader("text/html; charset=\"UTF-8\"; foo=bar", "charset")); Assert.assertEquals("UTF-8", Headers.extractQuotedValueFromHeader("text/html; charset=UTF-8 foo=bar", "charset")); Assert.assertEquals("UTF-8", Headers.extractQuotedValueFromHeader("text/html; badcharset=bad charset=UTF-8 foo=bar", "charset")); Assert.assertEquals("UTF-8", Headers.extractQuotedValueFromHeader("text/html;charset=UTF-8", "charset")); Assert.assertEquals("UTF-8", Headers.extractQuotedValueFromHeader("text/html;\tcharset=UTF-8", "charset")); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/CookiesTestCase.java000066400000000000000000000614371420065311100272520ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.server.handlers.Cookie; import io.undertow.testutils.category.UnitTest; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.Map; import java.util.TimeZone; /** * @author Stuart Douglas */ @Category(UnitTest.class) public class CookiesTestCase { @Test public void testParsingSetCookieHeaderV0() { Cookie cookie = Cookies.parseSetCookieHeader("CUSTOMER=WILE_E_COYOTE; path=/; expires=Wednesday, 09-Nov-99 23:12:40 GMT"); Assert.assertEquals("CUSTOMER", cookie.getName()); Assert.assertEquals("WILE_E_COYOTE", cookie.getValue()); Assert.assertEquals("/", cookie.getPath()); Assert.assertEquals(date(1999, 11, 9, 23, 12, 40), cookie.getExpires()); cookie = Cookies.parseSetCookieHeader("SHIPPING=FEDEX; path=/foo; secure"); Assert.assertEquals("SHIPPING", cookie.getName()); Assert.assertEquals("FEDEX", cookie.getValue()); Assert.assertEquals("/foo", cookie.getPath()); Assert.assertTrue(cookie.isSecure()); cookie = Cookies.parseSetCookieHeader("SHIPPING=FEDEX"); Assert.assertEquals("SHIPPING", cookie.getName()); Assert.assertEquals("FEDEX", cookie.getValue()); } @Test public void testParsingSetCookieHeaderV1() { Cookie cookie = Cookies.parseSetCookieHeader("Customer=\"WILE_E_COYOTE\"; Version=\"1\"; Path=\"/acme\""); Assert.assertEquals("Customer", cookie.getName()); Assert.assertEquals("WILE_E_COYOTE", cookie.getValue()); Assert.assertEquals("/acme", cookie.getPath()); Assert.assertEquals(1, cookie.getVersion()); cookie = Cookies.parseSetCookieHeader("SHIPPING=\"FEDEX\"; path=\"/foo\"; secure; Version=\"1\";"); Assert.assertEquals("SHIPPING", cookie.getName()); Assert.assertEquals("FEDEX", cookie.getValue()); Assert.assertEquals("/foo", cookie.getPath()); Assert.assertTrue(cookie.isSecure()); Assert.assertEquals(1, cookie.getVersion()); } private static Date date(int year, int month, int day, int hour, int minute, int second) { Calendar c = Calendar.getInstance(); c.set(Calendar.MILLISECOND, 0); c.setTimeZone(TimeZone.getTimeZone("GMT")); c.set(year, month-1, day, hour, minute, second); return c.getTime(); } @Test public void testInvalidCookie() { Map cookies = Cookies.parseRequestCookies(1, false, Arrays.asList("\"; CUSTOMER=WILE_E_COYOTE")); Assert.assertFalse(cookies.containsKey("$Domain")); Assert.assertFalse(cookies.containsKey("$Version")); Assert.assertFalse(cookies.containsKey("$Path")); Cookie cookie = cookies.get("CUSTOMER"); Assert.assertEquals("CUSTOMER", cookie.getName()); Assert.assertEquals("WILE_E_COYOTE", cookie.getValue()); cookies = Cookies.parseRequestCookies(1, false, Arrays.asList("; CUSTOMER=WILE_E_COYOTE")); cookie = cookies.get("CUSTOMER"); Assert.assertEquals("CUSTOMER", cookie.getName()); Assert.assertEquals("WILE_E_COYOTE", cookie.getValue()); cookies = Cookies.parseRequestCookies(1, false, Arrays.asList("foobar; CUSTOMER=WILE_E_COYOTE")); cookie = cookies.get("CUSTOMER"); Assert.assertEquals("CUSTOMER", cookie.getName()); Assert.assertEquals("WILE_E_COYOTE", cookie.getValue()); } @Test public void testRequestCookieDomainPathVersion() { Map cookies = Cookies.parseRequestCookies(1, false, Arrays.asList( "CUSTOMER=WILE_E_COYOTE; $Domain=LOONEY_TUNES; $Version=1; $Path=/")); Assert.assertFalse(cookies.containsKey("$Domain")); Assert.assertFalse(cookies.containsKey("$Version")); Assert.assertFalse(cookies.containsKey("$Path")); Cookie cookie = cookies.get("CUSTOMER"); Assert.assertEquals("CUSTOMER", cookie.getName()); Assert.assertEquals("WILE_E_COYOTE", cookie.getValue()); Assert.assertEquals("LOONEY_TUNES", cookie.getDomain()); Assert.assertEquals(1, cookie.getVersion()); Assert.assertEquals("/", cookie.getPath()); } @Test public void testMultipleRequestCookies() { Map cookies = Cookies.parseRequestCookies(2, false, Arrays.asList( "CUSTOMER=WILE_E_COYOTE; $Domain=LOONEY_TUNES; $Version=1; $Path=/; SHIPPING=FEDEX")); Cookie cookie = cookies.get("CUSTOMER"); Assert.assertEquals("CUSTOMER", cookie.getName()); Assert.assertEquals("WILE_E_COYOTE", cookie.getValue()); Assert.assertEquals("LOONEY_TUNES", cookie.getDomain()); Assert.assertEquals(1, cookie.getVersion()); Assert.assertEquals("/", cookie.getPath()); cookie = cookies.get("SHIPPING"); Assert.assertEquals("SHIPPING", cookie.getName()); Assert.assertEquals("FEDEX", cookie.getValue()); Assert.assertEquals("LOONEY_TUNES", cookie.getDomain()); Assert.assertEquals(1, cookie.getVersion()); Assert.assertEquals("/", cookie.getPath()); } @Test public void testEqualsInValueNotAllowed() { Map cookies = Cookies.parseRequestCookies(2, false, Arrays.asList("CUSTOMER=WILE_E_COYOTE=THE_COYOTE; SHIPPING=FEDEX")); Cookie cookie = cookies.get("CUSTOMER"); Assert.assertNotNull(cookie); Assert.assertEquals("WILE_E_COYOTE", cookie.getValue()); cookie = cookies.get("SHIPPING"); Assert.assertNotNull(cookie); Assert.assertEquals("FEDEX", cookie.getValue()); } @Test public void testEmptyCookieNames() { Map cookies = Cookies.parseRequestCookies(4, false, Arrays.asList("=foo; CUSTOMER=WILE_E_COYOTE=THE_COYOTE; =foobar; SHIPPING=FEDEX; =bar")); Cookie cookie = cookies.get("CUSTOMER"); Assert.assertNotNull(cookie); Assert.assertEquals("WILE_E_COYOTE", cookie.getValue()); cookie = cookies.get("SHIPPING"); Assert.assertNotNull(cookie); Assert.assertEquals("FEDEX", cookie.getValue()); cookie = cookies.get(""); Assert.assertNotNull(cookie); Assert.assertEquals("foo", cookie.getValue()); } @Test public void testEqualsInValueAllowed() { Map cookies = Cookies.parseRequestCookies(1, true, Arrays.asList("CUSTOMER=WILE_E_COYOTE=THE_COYOTE")); Cookie cookie = cookies.get("CUSTOMER"); Assert.assertNotNull(cookie); Assert.assertEquals("WILE_E_COYOTE=THE_COYOTE", cookie.getValue()); } @Test public void testEqualsInValueAllowedInQuotedValue() { Map cookies = Cookies.parseRequestCookies(2, true, Arrays.asList("CUSTOMER=\"WILE_E_COYOTE=THE_COYOTE\"; SHIPPING=FEDEX" )); Assert.assertEquals(2, cookies.size()); Cookie cookie = cookies.get("CUSTOMER"); Assert.assertNotNull(cookie); Assert.assertEquals("WILE_E_COYOTE=THE_COYOTE", cookie.getValue()); cookie = cookies.get("SHIPPING"); Assert.assertNotNull(cookie); Assert.assertEquals("FEDEX", cookie.getValue()); } @Test public void testEqualsInValueNotAllowedInQuotedValue() { Map cookies = Cookies.parseRequestCookies(2, false, Arrays.asList("CUSTOMER=\"WILE_E_COYOTE=THE_COYOTE\"; SHIPPING=FEDEX" )); Assert.assertEquals(2, cookies.size()); Cookie cookie = cookies.get("CUSTOMER"); Assert.assertNotNull(cookie); Assert.assertEquals("WILE_E_COYOTE=THE_COYOTE", cookie.getValue()); cookie = cookies.get("SHIPPING"); Assert.assertNotNull(cookie); Assert.assertEquals("FEDEX", cookie.getValue()); } @Test public void testCommaSeparatedCookies() { Map cookies = Cookies.parseRequestCookies(2, false, Arrays.asList("CUSTOMER=\"WILE_E_COYOTE\", SHIPPING=FEDEX" ), true); Assert.assertEquals(2, cookies.size()); Cookie cookie = cookies.get("CUSTOMER"); Assert.assertNotNull(cookie); Assert.assertEquals("WILE_E_COYOTE", cookie.getValue()); cookie = cookies.get("SHIPPING"); Assert.assertNotNull(cookie); Assert.assertEquals("FEDEX", cookie.getValue()); //also make sure semi colon works as normal cookies = Cookies.parseRequestCookies(2, false, Arrays.asList("CUSTOMER=\"WILE_E_COYOTE\"; SHIPPING=FEDEX" ), true); Assert.assertEquals(2, cookies.size()); cookie = cookies.get("CUSTOMER"); Assert.assertNotNull(cookie); Assert.assertEquals("WILE_E_COYOTE", cookie.getValue()); cookie = cookies.get("SHIPPING"); Assert.assertNotNull(cookie); Assert.assertEquals("FEDEX", cookie.getValue()); } @Test public void testHttpSeparaterInV0CookieValue() { Map cookies = Cookies.parseRequestCookies(2, false, Arrays.asList("CUSTOMER=WILE_E COYOTE; SHIPPING=FEDEX" ), true, false); Assert.assertEquals(2, cookies.size()); Cookie cookie = cookies.get("CUSTOMER"); Assert.assertNotNull(cookie); Assert.assertEquals("WILE_E", cookie.getValue()); cookie = cookies.get("SHIPPING"); Assert.assertNotNull(cookie); Assert.assertEquals("FEDEX", cookie.getValue()); cookies = Cookies.parseRequestCookies(2, false, Arrays.asList("CUSTOMER=WILE_E COYOTE; SHIPPING=FEDEX" ), true, true); Assert.assertEquals(2, cookies.size()); cookie = cookies.get("CUSTOMER"); Assert.assertNotNull(cookie); Assert.assertEquals("WILE_E COYOTE", cookie.getValue()); cookie = cookies.get("SHIPPING"); Assert.assertNotNull(cookie); Assert.assertEquals("FEDEX", cookie.getValue()); cookies = Cookies.parseRequestCookies(2, false, Arrays.asList("CUSTOMER=WILE_E_COYOTE\"; SHIPPING=FEDEX" ), true, false); Assert.assertEquals(2, cookies.size()); cookie = cookies.get("CUSTOMER"); Assert.assertNotNull(cookie); Assert.assertEquals("WILE_E_COYOTE", cookie.getValue()); cookie = cookies.get("SHIPPING"); Assert.assertNotNull(cookie); Assert.assertEquals("FEDEX", cookie.getValue()); cookies = Cookies.parseRequestCookies(2, false, Arrays.asList("CUSTOMER=WILE_E_COYOTE\"; SHIPPING=FEDEX" ), true, true); Assert.assertEquals(2, cookies.size()); cookie = cookies.get("CUSTOMER"); Assert.assertNotNull(cookie); Assert.assertEquals("WILE_E_COYOTE\"", cookie.getValue()); cookie = cookies.get("SHIPPING"); Assert.assertNotNull(cookie); Assert.assertEquals("FEDEX", cookie.getValue()); } @Test public void testCookieContainsColonInJvmRoute() { // ":" (e.g. master:node1) is added as jvmRoute (instance-id) by default in WildFly domain mode. // ":" is http separator, so it's not allowed in V0 cookie value. // However, we need to allow it exceptionally by default. Because, when Undertow runs as a proxy server (like mod_cluster), // we need to handle jvmRoute containing ":" in the request cookie value correctly to maintain the sticky session. Map cookies = Cookies.parseRequestCookies(3, false, Arrays.asList("JSESSIONID=WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1; CUSTOMER=WILE_E COYOTE; SHIPPING=FEDEX" ), true, false); Assert.assertEquals(3, cookies.size()); Cookie cookie = cookies.get("CUSTOMER"); Assert.assertNotNull(cookie); Assert.assertEquals("WILE_E", cookie.getValue()); cookie = cookies.get("SHIPPING"); Assert.assertNotNull(cookie); Assert.assertEquals("FEDEX", cookie.getValue()); cookie = cookies.get("JSESSIONID"); Assert.assertNotNull(cookie); Assert.assertEquals("WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1", cookie.getValue()); cookies = Cookies.parseRequestCookies(3, false, Arrays.asList("JSESSIONID=WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1; CUSTOMER=WILE_E COYOTE; SHIPPING=FEDEX" ), true, true); Assert.assertEquals(3, cookies.size()); cookie = cookies.get("CUSTOMER"); Assert.assertNotNull(cookie); Assert.assertEquals("WILE_E COYOTE", cookie.getValue()); cookie = cookies.get("SHIPPING"); Assert.assertNotNull(cookie); Assert.assertEquals("FEDEX", cookie.getValue()); cookie = cookies.get("JSESSIONID"); Assert.assertNotNull(cookie); Assert.assertEquals("WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1", cookie.getValue()); cookies = Cookies.parseRequestCookies(3, false, Arrays.asList("JSESSIONID=WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1; CUSTOMER=WILE_E_COYOTE\"; SHIPPING=FEDEX" ), true, false); Assert.assertEquals(3, cookies.size()); cookie = cookies.get("CUSTOMER"); Assert.assertNotNull(cookie); Assert.assertEquals("WILE_E_COYOTE", cookie.getValue()); cookie = cookies.get("SHIPPING"); Assert.assertNotNull(cookie); Assert.assertEquals("FEDEX", cookie.getValue()); cookie = cookies.get("JSESSIONID"); Assert.assertNotNull(cookie); Assert.assertEquals("WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1", cookie.getValue()); cookies = Cookies.parseRequestCookies(3, false, Arrays.asList("JSESSIONID=WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1; CUSTOMER=WILE_E_COYOTE\"; SHIPPING=FEDEX" ), true, true); Assert.assertEquals(3, cookies.size()); cookie = cookies.get("CUSTOMER"); Assert.assertNotNull(cookie); Assert.assertEquals("WILE_E_COYOTE\"", cookie.getValue()); cookie = cookies.get("SHIPPING"); Assert.assertNotNull(cookie); Assert.assertEquals("FEDEX", cookie.getValue()); cookie = cookies.get("JSESSIONID"); Assert.assertNotNull(cookie); Assert.assertEquals("WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1", cookie.getValue()); } @Test public void testQuotedEscapedStringInRequestCookie() { Map cookies = Cookies.parseRequestCookies(3, false, Arrays.asList( "Customer=\"WILE_\\\"E_\\\"COYOTE\"; $Version=\"1\"; $Path=\"/acme\";" + " SHIPPING=\"FEDEX\\\\\"; foo=\"\\\"")); Cookie cookie = cookies.get("Customer"); Assert.assertEquals("Customer", cookie.getName()); Assert.assertEquals("WILE_\"E_\"COYOTE", cookie.getValue()); // backslash escapled double quotes in the value Assert.assertEquals("/acme", cookie.getPath()); Assert.assertEquals(1, cookie.getVersion()); cookie = cookies.get("SHIPPING"); Assert.assertEquals("SHIPPING", cookie.getName()); Assert.assertEquals("FEDEX\\\\", cookie.getValue()); // backslash escapled backslash in the value cookie = cookies.get("foo"); Assert.assertEquals("foo", cookie.getName()); Assert.assertEquals("\\", cookie.getValue()); // unescaped backslash exists at the last of the value } @Test public void testSimpleJSONObjectInRequestCookies() { // allowEqualInValue and allowHttpSepartorsV0 needs to be enabled to handle this cookie // Also, commaIsSeperator needs to be set to false Map cookies = Cookies.parseRequestCookies(2, true, Arrays.asList( "CUSTOMER={\"v1\":1, \"id\":\"some_unique_id\", \"c\":\"http://www.google.com?q=love me\"};" + " $Domain=LOONEY_TUNES; $Version=1; $Path=/; SHIPPING=FEDEX"), false, true); Cookie cookie = cookies.get("CUSTOMER"); Assert.assertEquals("CUSTOMER", cookie.getName()); Assert.assertEquals("{\"v1\":1, \"id\":\"some_unique_id\", \"c\":\"http://www.google.com?q=love me\"}", cookie.getValue()); Assert.assertEquals("LOONEY_TUNES", cookie.getDomain()); Assert.assertEquals(1, cookie.getVersion()); Assert.assertEquals("/", cookie.getPath()); cookie = cookies.get("SHIPPING"); Assert.assertEquals("SHIPPING", cookie.getName()); Assert.assertEquals("FEDEX", cookie.getValue()); Assert.assertEquals("LOONEY_TUNES", cookie.getDomain()); Assert.assertEquals(1, cookie.getVersion()); Assert.assertEquals("/", cookie.getPath()); } @Test public void testQuotedJSONObjectInRequestCookies() { // allowEqualInValue and allowHttpSepartorsV0 needs to be enabled to handle this cookie // Also, commaIsSeperator needs to be set to false Map cookies = Cookies.parseRequestCookies(2, true, Arrays.asList( "CUSTOMER=\"{\\\"v1\\\":1, \\\"id\\\":\\\"some_unique_id\\\", \\\"c\\\":\\\"http://www.google.com?q=love me\\\"}\";" + " $Domain=LOONEY_TUNES; $Version=1; $Path=/; SHIPPING=FEDEX"), false, true); Cookie cookie = cookies.get("CUSTOMER"); Assert.assertEquals("CUSTOMER", cookie.getName()); Assert.assertEquals("{\"v1\":1, \"id\":\"some_unique_id\", \"c\":\"http://www.google.com?q=love me\"}", cookie.getValue()); Assert.assertEquals("LOONEY_TUNES", cookie.getDomain()); Assert.assertEquals(1, cookie.getVersion()); Assert.assertEquals("/", cookie.getPath()); cookie = cookies.get("SHIPPING"); Assert.assertEquals("SHIPPING", cookie.getName()); Assert.assertEquals("FEDEX", cookie.getValue()); Assert.assertEquals("LOONEY_TUNES", cookie.getDomain()); Assert.assertEquals(1, cookie.getVersion()); Assert.assertEquals("/", cookie.getPath()); } @Test public void testComplexJSONObjectInRequestCookies() { // allowHttpSepartorsV0 needs to be enabled to handle this cookie // Also, commaIsSeperator needs to be set to false Map cookies = Cookies.parseRequestCookies(2, false, Arrays.asList( "CUSTOMER={ \"accounting\" : [ { \"firstName\" : \"John\", \"lastName\" : \"Doe\", \"age\" : 23 }," + " { \"firstName\" : \"Mary\", \"lastName\" : \"Smith\", \"age\" : 32 }], " + "\"sales\" : [ { \"firstName\" : \"Sally\", \"lastName\" : \"Green\", \"age\" : 27 }, " + "{ \"firstName\" : \"Jim\", \"lastName\" : \"Galley\", \"age\" : 41 } ] };" + " $Domain=LOONEY_TUNES; $Version=1; $Path=/; SHIPPING=FEDEX"), false, true); Cookie cookie = cookies.get("CUSTOMER"); Assert.assertEquals("CUSTOMER", cookie.getName()); Assert.assertEquals("{ \"accounting\" : [ { \"firstName\" : \"John\", \"lastName\" : \"Doe\", \"age\" : 23 }," + " { \"firstName\" : \"Mary\", \"lastName\" : \"Smith\", \"age\" : 32 }], " + "\"sales\" : [ { \"firstName\" : \"Sally\", \"lastName\" : \"Green\", \"age\" : 27 }, " + "{ \"firstName\" : \"Jim\", \"lastName\" : \"Galley\", \"age\" : 41 } ] }", cookie.getValue()); Assert.assertEquals("LOONEY_TUNES", cookie.getDomain()); Assert.assertEquals(1, cookie.getVersion()); Assert.assertEquals("/", cookie.getPath()); cookie = cookies.get("SHIPPING"); Assert.assertEquals("SHIPPING", cookie.getName()); Assert.assertEquals("FEDEX", cookie.getValue()); Assert.assertEquals("LOONEY_TUNES", cookie.getDomain()); Assert.assertEquals(1, cookie.getVersion()); Assert.assertEquals("/", cookie.getPath()); } @Test public void testSameSiteCookie() { Cookie cookie = Cookies.parseSetCookieHeader("CUSTOMER=WILE_E_COYOTE; path=/"); Assert.assertEquals("CUSTOMER", cookie.getName()); Assert.assertEquals("WILE_E_COYOTE", cookie.getValue()); Assert.assertEquals("/", cookie.getPath()); Assert.assertNull(cookie.getSameSiteMode()); cookie = Cookies.parseSetCookieHeader("CUSTOMER=WILE_E_COYOTE; path=/; SameSite=None"); Assert.assertEquals("CUSTOMER", cookie.getName()); Assert.assertEquals("WILE_E_COYOTE", cookie.getValue()); Assert.assertEquals("/", cookie.getPath()); Assert.assertEquals("None", cookie.getSameSiteMode()); cookie = Cookies.parseSetCookieHeader("SHIPPING=FEDEX; path=/foo; SameSite=Strict"); Assert.assertEquals("SHIPPING", cookie.getName()); Assert.assertEquals("FEDEX", cookie.getValue()); Assert.assertEquals("/foo", cookie.getPath()); Assert.assertEquals("Strict", cookie.getSameSiteMode()); cookie = Cookies.parseSetCookieHeader("SHIPPING=FEDEX; path=/acme; SameSite=Lax"); Assert.assertEquals("SHIPPING", cookie.getName()); Assert.assertEquals("FEDEX", cookie.getValue()); Assert.assertEquals("/acme", cookie.getPath()); Assert.assertEquals("Lax", cookie.getSameSiteMode()); cookie = Cookies.parseSetCookieHeader("CUSTOMER=WILE_E_COYOTE; path=/; SameSite=test"); // invalid SameSite mode Assert.assertEquals("CUSTOMER", cookie.getName()); Assert.assertEquals("WILE_E_COYOTE", cookie.getValue()); Assert.assertEquals("/", cookie.getPath()); Assert.assertNull(cookie.getSameSiteMode()); } // RFC6265 allows US-ASCII characters excluding CTLs, whitespace, // double quote, comma, semicolon and backslash as cookie value. // This does not change even if value is quoted. @Test(expected = IllegalArgumentException.class) public void testInvalidRfc6265CookieInValue() { // whitespace is not allowed Cookie cookie = Cookies.parseSetCookieHeader("CUSTOMER=WILE_ E_COYOTE; path=/example; domain=example.com"); Rfc6265CookieSupport.validateCookieValue(cookie.getValue()); } @Test(expected = IllegalArgumentException.class) public void testInvalidRfc6265CookieInValue1() { // whitespace is not allowed Cookie cookie = Cookies.parseSetCookieHeader("CUSTOMER=\"WILE_ E_COYOTE\"; path=/example; domain=example.com"); Rfc6265CookieSupport.validateCookieValue(cookie.getValue()); } @Test(expected = IllegalArgumentException.class) public void testInvalidRfc6265CookieInValue2() { // double quote si not allowed Cookie cookie = Cookies.parseSetCookieHeader("CUSTOMER=\"WILE_\\\"E_COYOTE\"; path=/example; domain=example.com"); Rfc6265CookieSupport.validateCookieValue(cookie.getValue()); } @Test(expected = IllegalArgumentException.class) public void testInvalidRfc6265CookieInValue3() { // comma is not allowed Cookie cookie = Cookies.parseSetCookieHeader("CUSTOMER=\"WILE_,E_COYOTE\"; path=/example; domain=example.com"); Rfc6265CookieSupport.validateCookieValue(cookie.getValue()); } @Test(expected = IllegalArgumentException.class) public void testInvalidRfc6265CookieInValue4() { // semicolon is not allowed Cookie cookie = Cookies.parseSetCookieHeader("CUSTOMER=\"WILE_;E_COYOTE\"; path=/example; domain=example.com"); Rfc6265CookieSupport.validateCookieValue(cookie.getValue()); } @Test(expected = IllegalArgumentException.class) public void testInvalidRfc6265CookieInValue5() { /// backslash is not allowed Cookie cookie = Cookies.parseSetCookieHeader("CUSTOMER=\"WILE_\\E_COYOTE\"; path=/example; domain=example.com"); Rfc6265CookieSupport.validateCookieValue(cookie.getValue()); } // RFC6265 allows any CHAR except CTLs or ";" as cookie path @Test(expected = IllegalArgumentException.class) public void testInvalidRfc6265CookieInPath() { Cookie cookie = Cookies.parseSetCookieHeader("CUSTOMER=WILE_E_COYOTE; path=\"/ex;ample\"; domain=example.com"); Rfc6265CookieSupport.validateCookieValue(cookie.getValue()); Rfc6265CookieSupport.validatePath(cookie.getPath()); Rfc6265CookieSupport.validateDomain(cookie.getDomain()); } @Test(expected = IllegalArgumentException.class) public void testInvalidRfc6265CookieInDomain() { Cookie cookie = Cookies.parseSetCookieHeader("CUSTOMER=WILE_E_COYOTE; path=/example; domain=\"ex;ample.com\""); Rfc6265CookieSupport.validateCookieValue(cookie.getValue()); Rfc6265CookieSupport.validatePath(cookie.getPath()); Rfc6265CookieSupport.validateDomain(cookie.getDomain()); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/DateUtilsTestCase.java000066400000000000000000000063121420065311100275430ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.testutils.category.UnitTest; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import org.junit.experimental.categories.Category; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; /** * @author Tomasz Knyziak */ @Category(UnitTest.class) public class DateUtilsTestCase { @Test public void testParseFirefoxDate() { String firefoxHeader = "Mon, 31 Mar 2014 09:24:49 GMT"; Date firefoxDate = DateUtils.parseDate(firefoxHeader); Assert.assertNotNull(firefoxDate); Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")); calendar.set(2014, Calendar.MARCH, 31, 9, 24, 49); calendar.set(Calendar.MILLISECOND, 0); Assert.assertEquals(calendar.getTime(), firefoxDate); } @Test public void testParseChromeDate() { String chromeHeader = "Mon, 31 Mar 2014 09:44:00 GMT"; Date chromeDate = DateUtils.parseDate(chromeHeader); Assert.assertNotNull(chromeDate); Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")); calendar.set(2014, Calendar.MARCH, 31, 9, 44, 00); calendar.set(Calendar.MILLISECOND, 0); Assert.assertEquals(calendar.getTime(), chromeDate); } @Test public void testParseIE9Date() { String ie9Header = "Wed, 12 Feb 2014 04:43:29 GMT; length=142951"; Date ie9Date = DateUtils.parseDate(ie9Header); Assert.assertNotNull(ie9Date); Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")); calendar.set(2014, Calendar.FEBRUARY, 12, 4, 43, 29); calendar.set(Calendar.MILLISECOND, 0); Assert.assertEquals(calendar.getTime(), ie9Date); } @Test @Ignore("This test can fail if the machine pauses/swaps at the wrong time") public void testPerformance() { String ie9Header = "Wed, 12 Feb 2014 04:43:29 GMT; length=142951"; long timestamp = System.currentTimeMillis(); for (int i=0; i < 1000; i++) { ie9Header.replaceAll(";.*$", ""); } long ts1 = System.currentTimeMillis() - timestamp; timestamp = System.currentTimeMillis(); for (int i=0; i < 1000; i++) { int index = ie9Header.indexOf(';'); final String trimmedDate = index >=0 ? ie9Header.substring(0, index) : ie9Header; } long ts2 = System.currentTimeMillis() - timestamp; Assert.assertTrue(ts2 < ts1); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/ETagUtilsTestCase.java000066400000000000000000000033701420065311100275070ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.testutils.category.UnitTest; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; /** * @author Stuart Douglas */ @Category(UnitTest.class) public class ETagUtilsTestCase { @Test public void testParseHeaderList() { Assert.assertArrayEquals(new ETag[] { new ETag(false, "1"), new ETag(false, "2"), new ETag(false, "3")}, ETagUtils.parseETagList("\"1\",\"2\" , \"3 ").toArray()); Assert.assertArrayEquals(new ETag[] { new ETag(true, "111"), new ETag(false, "222"), new ETag(true, "333")}, ETagUtils.parseETagList("W/\"111\",\"222\" , W/\"333 ").toArray()); Assert.assertArrayEquals(new ETag[] { new ETag(true, "1,1"), new ETag(false, "222"), new ETag(true, "3 3")}, ETagUtils.parseETagList("W/\"1,1\",\"222\" , W/\"3 3 ").toArray()); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/FlexBase64TestCase.java000066400000000000000000000034711420065311100275130ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.testutils.category.UnitTest; import java.nio.ByteBuffer; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; @Category(UnitTest.class) public class FlexBase64TestCase { @Test public void testReadStopsAtTerminator() throws Exception { String source = "ZWxsbw==="; byte[] target = new byte[1024]; final FlexBase64.Decoder decoder = FlexBase64.createDecoder(); int read = decoder.decode(source, 0, source.length(), target, 0, target.length); Assert.assertEquals(4, read); Assert.assertEquals("ello", new String(target, 0, read)); Assert.assertEquals(8, decoder.getLastInputPosition()); } @Test public void testEncodeURLWithByteBufferUsesUrlTable() { ByteBuffer source = ByteBuffer.wrap(new byte[]{0, 0x01, 0, 0, 0x10, 0, 0, 2, 0, 0, 0, 0x01, 0, 0x04, 0, 0, (byte) 0xff, (byte) 0xff, 0, 0x05, 0, 0, 0x40, 0}); String target = FlexBase64.encodeStringURL(source, false); Assert.assertEquals("AAEAABAAAAIAAAABAAQAAP__AAUAAEAA", target); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/HeaderMapTestCase.java000066400000000000000000000112121420065311100274660ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.testutils.category.UnitTest; import org.junit.Test; import org.junit.experimental.categories.Category; import java.util.Arrays; import java.util.List; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; /** * @author David M. Lloyd */ @Category(UnitTest.class) public final class HeaderMapTestCase { private static final List HTTP_STRING_LIST = Arrays.asList(Headers.CONNECTION, Headers.HOST, Headers.UPGRADE, Headers.CONTENT_MD5, Headers.KEEP_ALIVE, Headers.RESPONSE_AUTH, Headers.CONTENT_DISPOSITION, Headers.DEFLATE, Headers.NEGOTIATE, Headers.USER_AGENT, Headers.REFERER, Headers.TRANSFER_ENCODING, Headers.FROM); @Test public void testInitial() { final HeaderMap headerMap = new HeaderMap(); assertEquals(0, headerMap.size()); assertEquals(-1L, headerMap.fastIterate()); assertFalse(headerMap.iterator().hasNext()); } @Test public void testMixedCase() { final HeaderMap headerMap = new HeaderMap(); headerMap.add(new HttpString("Aa"), "A"); headerMap.add(new HttpString("aa"), "a"); assertArrayEquals(headerMap.get(new HttpString("aa")).toArray(), new String[]{"A", "a"}); assertArrayEquals(headerMap.get(new HttpString("Aa")).toArray(), new String[]{"A", "a"}); assertArrayEquals(headerMap.get(new HttpString("AA")).toArray(), new String[]{"A", "a"}); } @Test public void testSimple() { final HeaderMap headerMap = new HeaderMap(); headerMap.add(Headers.HOST, "yay.undertow.io"); assertTrue(headerMap.contains(Headers.HOST)); assertTrue(headerMap.contains("host")); assertEquals(1, headerMap.size()); assertNotEquals(-1L, headerMap.fastIterate()); assertEquals(-1L, headerMap.fiNext(headerMap.fastIterate())); assertEquals(Headers.HOST, headerMap.fiCurrent(headerMap.fastIterate()).getHeaderName()); assertEquals("yay.undertow.io", headerMap.getFirst(Headers.HOST)); assertEquals("yay.undertow.io", headerMap.getLast(Headers.HOST)); assertEquals("yay.undertow.io", headerMap.get(Headers.HOST, 0)); headerMap.remove("host"); assertEquals(0, headerMap.size()); } @Test public void testGrowing() { final HeaderMap headerMap = new HeaderMap(); for (HttpString item : HTTP_STRING_LIST) { for (int i = 0; i < (item.hashCode() & 7) + 1; i++) headerMap.add(item, "Test value"); } for (HttpString item : HTTP_STRING_LIST) { assertTrue(String.format("Missing %s (hash %08x)", item, Integer.valueOf(item.hashCode())), headerMap.contains(item)); assertNotNull(headerMap.get(item)); assertEquals((item.hashCode() & 7) + 1, headerMap.get(item).size()); assertEquals("Test value", headerMap.getFirst(item)); assertEquals("Test value", headerMap.getLast(item)); } assertEquals(HTTP_STRING_LIST.size(), headerMap.size()); for (HttpString item : HTTP_STRING_LIST) { assertTrue(headerMap.contains(item)); assertNotNull(headerMap.remove(item)); assertFalse(headerMap.contains(item)); } assertEquals(0, headerMap.size()); } @Test public void testCollision() { HeaderMap headerMap = new HeaderMap(); headerMap.put(new HttpString("Link"), "a"); headerMap.put(new HttpString("Rest"), "b"); assertEquals("a", headerMap.getFirst(new HttpString("Link"))); assertEquals("b", headerMap.getFirst(new HttpString("Rest"))); assertEquals("a", headerMap.getFirst("Link")); assertEquals("b", headerMap.getFirst("Rest")); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/HeaderOrderTestCase.java000066400000000000000000000050501420065311100300270ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.testutils.category.UnitTest; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; /** * Tests that the headers in the Headers class have the correct order. The headers * are assigned an explicit ordering integer to allow for super fast comparisons. * * @author Stuart Douglas */ @Category(UnitTest.class) public class HeaderOrderTestCase { @Test public void testHeadersOrder() throws Exception { final Field orderIntField = HttpString.class.getDeclaredField("orderInt"); orderIntField.setAccessible(true); Field[] fields = Headers.class.getDeclaredFields(); final List headers = new ArrayList<>(); for(final Field field : fields) { // skip transient field for jacoco if(Modifier.isTransient(field.getModifiers()) || !Modifier.isPublic(field.getModifiers())) { continue; } Object value = field.get(null); if(!(value instanceof HttpString)) { continue; } HttpString header = (HttpString) value; if((Integer)orderIntField.get(header) != 0) { headers.add(header); } } Collections.sort(headers, new Comparator() { @Override public int compare(final HttpString o1, final HttpString o2) { return o1.toString().compareToIgnoreCase(o2.toString()); } }); int val = 1; for(final HttpString header : headers) { Assert.assertEquals(val++, orderIntField.get(header)); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/HeaderTokenParserTestCase.java000066400000000000000000000026401420065311100312130ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.security.impl.DigestAuthorizationToken; import io.undertow.testutils.category.UnitTest; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import java.util.Collections; /** * @author Stuart Douglas */ @Category(UnitTest.class) public class HeaderTokenParserTestCase { @Test public void testHeaderTokenParser() { HeaderTokenParser h = new HeaderTokenParser(Collections.singletonMap("username", DigestAuthorizationToken.USERNAME)); Assert.assertEquals("a\"b", h.parseHeader("username=\"a\\\"b\"").get(DigestAuthorizationToken.USERNAME)); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/HeaderValuesTestCase.java000066400000000000000000000063531420065311100302220ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.testutils.category.UnitTest; import org.junit.Test; import org.junit.experimental.categories.Category; import static org.junit.Assert.*; /** * @author David M. Lloyd */ @Category(UnitTest.class) public final class HeaderValuesTestCase { @Test public void testBasic() { final HeaderValues headerValues = new HeaderValues(Headers.DEFLATE); assertEquals(0, headerValues.size()); assertTrue(headerValues.isEmpty()); assertFalse(headerValues.iterator().hasNext()); assertFalse(headerValues.descendingIterator().hasNext()); assertFalse(headerValues.listIterator().hasNext()); assertFalse(headerValues.listIterator(0).hasNext()); assertNull(headerValues.peek()); assertNull(headerValues.peekFirst()); assertNull(headerValues.peekLast()); } @Test public void testAdd() { HeaderValues headerValues = new HeaderValues(Headers.HOST); assertTrue(headerValues.add("Foo")); assertTrue(headerValues.contains("Foo")); assertTrue(headerValues.contains(new String("Foo"))); assertFalse(headerValues.contains("Bar")); assertFalse(headerValues.isEmpty()); assertEquals(1, headerValues.size()); assertEquals("Foo", headerValues.peek()); assertEquals("Foo", headerValues.peekFirst()); assertEquals("Foo", headerValues.peekLast()); assertEquals("Foo", headerValues.get(0)); assertTrue(headerValues.offerFirst("First!")); assertTrue(headerValues.contains("First!")); assertTrue(headerValues.contains("Foo")); assertEquals(2, headerValues.size()); assertEquals("First!", headerValues.peek()); assertEquals("First!", headerValues.peekFirst()); assertEquals("First!", headerValues.get(0)); assertEquals("Foo", headerValues.peekLast()); assertEquals("Foo", headerValues.get(1)); assertTrue(headerValues.offerLast("Last!")); assertTrue(headerValues.contains("Last!")); assertTrue(headerValues.contains("Foo")); assertTrue(headerValues.contains("First!")); assertEquals(3, headerValues.size()); assertEquals("First!", headerValues.peek()); assertEquals("First!", headerValues.peekFirst()); assertEquals("First!", headerValues.get(0)); assertEquals("Foo", headerValues.get(1)); assertEquals("Last!", headerValues.peekLast()); assertEquals("Last!", headerValues.get(2)); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/HeadersUtilsTestCase.java000066400000000000000000000023021420065311100302340ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.testutils.category.UnitTest; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; /** * Tests param extraction of a header * * @author Tim Terlegård */ @Category(UnitTest.class) public class HeadersUtilsTestCase { @Test public void testTokenExtraction() { Assert.assertEquals("--xyz", Headers.extractTokenFromHeader("multipart/form-data; boundary=--xyz; param=abc", "boundary")); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/HttpStringTestCase.java000066400000000000000000000056211420065311100277550ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.testutils.category.UnitTest; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; /** * @author Matej Lazar */ @Category(UnitTest.class) public class HttpStringTestCase { @Test public void testOrderShorterFirst() { HttpString a = new HttpString("a"); HttpString aa = new HttpString("aa"); Assert.assertEquals(-1, a.compareTo(aa)); } /** * test HttpString.compareTo part: bytes.length - other.bytes.length */ @Test public void testCompareShorterFirst() { HttpString accept = new HttpString(Headers.ACCEPT_STRING); Assert.assertEquals(accept.compareTo(Headers.ACCEPT_CHARSET), Headers.ACCEPT.compareTo(Headers.ACCEPT_CHARSET)); HttpString acceptCharset = new HttpString(Headers.ACCEPT_CHARSET_STRING); Assert.assertEquals(acceptCharset.compareTo(Headers.ACCEPT), Headers.ACCEPT_CHARSET.compareTo(Headers.ACCEPT)); } /** * test HttpString.compareTo part: res = signum(higher(bytes[i]) - higher(other.bytes[i])); */ @Test public void testCompare() { HttpString contentType = new HttpString(Headers.CONTENT_TYPE_STRING); Assert.assertEquals(contentType.compareTo(Headers.COOKIE), Headers.CONTENT_TYPE.compareTo(Headers.COOKIE)); HttpString cookie = new HttpString(Headers.COOKIE_STRING); Assert.assertEquals(cookie.compareTo(Headers.CONTENT_TYPE), Headers.COOKIE.compareTo(Headers.CONTENT_TYPE)); } @Test public void testSerialization() throws IOException, ClassNotFoundException { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream so = new ObjectOutputStream(out); HttpString testString = new HttpString("test"); so.writeObject(testString); so.close(); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray())); Object res = in.readObject(); Assert.assertEquals(testString, res); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/LocaleUtilsTestCase.java000066400000000000000000000006611420065311100300660ustar00rootroot00000000000000package io.undertow.util; import io.undertow.testutils.category.UnitTest; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import java.util.Locale; @Category(UnitTest.class) public class LocaleUtilsTestCase { @Test public void testGetLocaleFromInvalidString() throws Exception { Assert.assertEquals(LocaleUtils.getLocaleFromString("-"), new Locale("")); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/MimeDecodingTestCase.java000066400000000000000000000220441420065311100301710ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.server.DefaultByteBufferPool; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.category.UnitTest; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; /** * @author Stuart Douglas */ @Category(UnitTest.class) public class MimeDecodingTestCase { @Test public void testSimpleMimeDecodingWithPreamble() throws IOException { final String data = fixLineEndings(FileUtils.readFile(MimeDecodingTestCase.class, "mime1.txt")); TestPartHandler handler = new TestPartHandler(); MultipartParser.ParseState parser = MultipartParser.beginParse(DefaultServer.getBufferPool(), handler, "unique-boundary-1".getBytes(), "ISO-8859-1"); ByteBuffer buf = ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8)); parser.parse(buf); Assert.assertTrue(parser.isComplete()); Assert.assertEquals(2, handler.parts.size()); Assert.assertEquals("Here is some text.", handler.parts.get(0).data.toString()); Assert.assertEquals("Here is some more text.", handler.parts.get(1).data.toString()); Assert.assertEquals("text/plain", handler.parts.get(0).map.getFirst(Headers.CONTENT_TYPE)); } @Test public void testMimeDecodingWithUTF8Headers() throws IOException { final String data = fixLineEndings(FileUtils.readFile(MimeDecodingTestCase.class, "mime-utf8.txt")); TestPartHandler handler = new TestPartHandler(); MultipartParser.ParseState parser = MultipartParser.beginParse(DefaultServer.getBufferPool(), handler, "unique-boundary-1".getBytes(), "UTF-8"); ByteBuffer buf = ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8)); parser.parse(buf); Assert.assertTrue(parser.isComplete()); Assert.assertEquals(1, handler.parts.size()); Assert.assertEquals("Just some chinese characters I copied from the internet, no idea what it says.", handler.parts.get(0).data.toString()); Assert.assertEquals("text/plain", handler.parts.get(0).map.getFirst(Headers.CONTENT_TYPE)); Assert.assertEquals("attachment; filename=个专为语文教学而设计的电脑软件.txt", handler.parts.get(0).map.getFirst(Headers.CONTENT_DISPOSITION)); } @Test public void testSimpleMimeDecodingWithoutPreamble() throws IOException { final String data = fixLineEndings(FileUtils.readFile(MimeDecodingTestCase.class, "mime2.txt")); TestPartHandler handler = new TestPartHandler(); MultipartParser.ParseState parser = MultipartParser.beginParse(DefaultServer.getBufferPool(), handler, "unique-boundary-1".getBytes(), "ISO-8859-1"); ByteBuffer buf = ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8)); parser.parse(buf); Assert.assertTrue(parser.isComplete()); Assert.assertEquals(2, handler.parts.size()); Assert.assertEquals("Here is some text.", handler.parts.get(0).data.toString()); Assert.assertEquals("Here is some more text.", handler.parts.get(1).data.toString()); Assert.assertEquals("text/plain", handler.parts.get(0).map.getFirst(Headers.CONTENT_TYPE)); } @Test public void testBase64MimeDecoding() throws IOException { final String data = fixLineEndings(FileUtils.readFile(MimeDecodingTestCase.class, "mime3.txt")); TestPartHandler handler = new TestPartHandler(); MultipartParser.ParseState parser = MultipartParser.beginParse(DefaultServer.getBufferPool(), handler, "unique-boundary-1".getBytes(), "ISO-8859-1"); ByteBuffer buf = ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8)); parser.parse(buf); Assert.assertTrue(parser.isComplete()); Assert.assertEquals(2, handler.parts.size()); Assert.assertEquals("This is some base64 text.", handler.parts.get(0).data.toString()); Assert.assertEquals("This is some more base64 text.", handler.parts.get(1).data.toString()); Assert.assertEquals("text/plain", handler.parts.get(0).map.getFirst(Headers.CONTENT_TYPE)); } @Test public void testBase64MimeDecodingWithSmallBuffers() throws IOException { final String data = fixLineEndings(FileUtils.readFile(MimeDecodingTestCase.class, "mime3.txt")); TestPartHandler handler = new TestPartHandler(); MultipartParser.ParseState parser = MultipartParser.beginParse(new DefaultByteBufferPool(true, 6, 100, 0), handler, "unique-boundary-1".getBytes(), "ISO-8859-1"); ByteBuffer buf = ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8)); parser.parse(buf); Assert.assertTrue(parser.isComplete()); Assert.assertEquals(2, handler.parts.size()); Assert.assertEquals("This is some base64 text.", handler.parts.get(0).data.toString()); Assert.assertEquals("This is some more base64 text.", handler.parts.get(1).data.toString()); Assert.assertEquals("text/plain", handler.parts.get(0).map.getFirst(Headers.CONTENT_TYPE)); } @Test public void testQuotedPrintable() throws IOException { final String data = fixLineEndings(FileUtils.readFile(MimeDecodingTestCase.class, "mime4.txt")); TestPartHandler handler = new TestPartHandler(); MultipartParser.ParseState parser = MultipartParser.beginParse(DefaultServer.getBufferPool(), handler, "someboundarytext".getBytes(), "ISO-8859-1"); ByteBuffer buf = ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8)); parser.parse(buf); Assert.assertTrue(parser.isComplete()); Assert.assertEquals(1, handler.parts.size()); Assert.assertEquals("time=money.", handler.parts.get(0).data.toString()); Assert.assertEquals("text/plain", handler.parts.get(0).map.getFirst(Headers.CONTENT_TYPE)); } @Test public void testMultilineHeader() throws IOException { final String data = fixLineEndings(FileUtils.readFile(MimeDecodingTestCase.class, "mime-multiline.txt")); TestPartHandler handler = new TestPartHandler(); MultipartParser.ParseState parser = MultipartParser.beginParse(DefaultServer.getBufferPool(), handler, "unique-boundary-1".getBytes(), "ISO-8859-1"); ByteBuffer buf = ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8)); parser.parse(buf); Assert.assertTrue(parser.isComplete()); Assert.assertEquals(2, handler.parts.size()); Assert.assertEquals("Here is some text.", handler.parts.get(0).data.toString()); Assert.assertEquals("Here is some more text.", handler.parts.get(1).data.toString()); Assert.assertEquals("text/plain; charset=\"ascii\"", handler.parts.get(0).map.getFirst(Headers.CONTENT_TYPE)); } private static class TestPartHandler implements MultipartParser.PartHandler { private final List parts = new ArrayList<>(); private Part current; @Override public void beginPart(final HeaderMap headers) { current = new Part(headers); parts.add(current); } @Override public void data(final ByteBuffer buffer) { while (buffer.hasRemaining()) { current.data.append((char) buffer.get()); } } @Override public void endPart() { } } private static class Part { private final HeaderMap map; private final StringBuilder data = new StringBuilder(); private Part(final HeaderMap map) { this.map = map; } } private static String fixLineEndings(final String string) { final StringBuilder builder = new StringBuilder(); for(int i = 0; i < string.length(); ++i) { char c = string.charAt(i); if(c == '\n') { if(i == 0 || string.charAt(i-1) != '\r') { builder.append("\r\n"); } else { builder.append('\n'); } } else if(c == '\r') { if(i+1 == string.length() || string.charAt(i+1) != '\n') { builder.append("\r\n"); } else { builder.append('\r'); } } else { builder.append(c); } } return builder.toString(); } } NetworkUtilsAddressObfuscationTestCase.java000066400000000000000000000034331420065311100337440ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/util/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import org.junit.Assert; import org.junit.Test; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; /** * verifies that the proxy protocol ip address parser correctly parses IP addresses as per the additional requirements * in the proxy protocol spec * * @author Stuart Douglas */ public class NetworkUtilsAddressObfuscationTestCase { private static String cvt(String input) throws UnknownHostException { return NetworkUtils.toObfuscatedString(InetAddress.getByName(input)); } @Test public void testIpV4Address() throws IOException { Assert.assertEquals("1.123.255.", cvt("1.123.255.2")); Assert.assertEquals("127.0.0.", cvt("127.0.0.1")); Assert.assertEquals("0.0.0.", cvt("0.0.0.0")); } @Test public void testIpv6Address() throws IOException { Assert.assertEquals("2001:1db8:", cvt("2001:1db8:100:3:6:ff00:42:8329")); Assert.assertEquals("2001:1db8:", cvt("2001:1db8:100::6:ff00:42:8329")); Assert.assertEquals("0:0:", cvt("::1")); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/NetworkUtilsAddressParsingTestCase.java000066400000000000000000000141731420065311100331550ustar00rootroot00000000000000package io.undertow.util; import org.junit.Assert; import org.junit.Test; import java.io.IOException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; /** * verifies that the proxy protocol ip address parser correctly parses IP addresses as per the additional requirements * in the proxy protocol spec * * @author Stuart Douglas */ public class NetworkUtilsAddressParsingTestCase { @Test public void testIpV4Address() throws IOException { InetAddress res = NetworkUtils.parseIpv4Address("1.123.255.2"); Assert.assertTrue(res instanceof Inet4Address); Assert.assertEquals(1, res.getAddress()[0]); Assert.assertEquals(123, res.getAddress()[1]); Assert.assertEquals((byte)255, res.getAddress()[2]); Assert.assertEquals(2, res.getAddress()[3]); Assert.assertEquals("/1.123.255.2", res.toString()); res = NetworkUtils.parseIpv4Address("127.0.0.1"); Assert.assertTrue(res instanceof Inet4Address); Assert.assertEquals(127, res.getAddress()[0]); Assert.assertEquals(0, res.getAddress()[1]); Assert.assertEquals((byte)0, res.getAddress()[2]); Assert.assertEquals(1, res.getAddress()[3]); Assert.assertEquals("/127.0.0.1", res.toString()); } @Test(expected = IOException.class) public void testIpV4AddressWithLeadingZero() throws IOException { NetworkUtils.parseIpv4Address("01.123.255.2"); } @Test(expected = IOException.class) public void testIpV4AddressToSmall() throws IOException { NetworkUtils.parseIpv4Address("01.123.255"); } @Test(expected = IOException.class) public void testIpV4AddressToLarge() throws IOException { NetworkUtils.parseIpv4Address("01.123.255.1.1"); } @Test(expected = IOException.class) public void testIpV4AddressMultipleDots() throws IOException { NetworkUtils.parseIpv4Address("1..255.2"); } @Test(expected = IOException.class) public void testIpV4AddressMultipleDots2() throws IOException { NetworkUtils.parseIpv4Address("1..3.255.2"); } @Test(expected = IOException.class) public void testIpV4Hostname() throws IOException { NetworkUtils.parseIpv4Address("localhost"); } @Test(expected = IOException.class) public void testIpV4Hostname2() throws IOException { NetworkUtils.parseIpv4Address("ff"); } @Test(expected = IOException.class) public void testIpV4AddressStartsWithDot() throws IOException { NetworkUtils.parseIpv4Address(".1.123.255.2"); } @Test public void testIpv6Address() throws IOException { String addressString = "2001:1db8:100:3:6:ff00:42:8329"; InetAddress res = NetworkUtils.parseIpv6Address(addressString); Assert.assertTrue(res instanceof Inet6Address); int[] parts = {0x2001, 0x1db8, 0x100, 0x3, 0x6, 0xff00, 0x42, 0x8329}; for(int i = 0 ; i < parts.length; ++i) { Assert.assertEquals(((byte)(parts[i]>>8)), res.getAddress()[i * 2]); Assert.assertEquals(((byte)(parts[i])), res.getAddress()[i * 2 + 1]); } Assert.assertEquals("/" + addressString, res.toString()); addressString = "2001:1db8:100::6:ff00:42:8329"; res = NetworkUtils.parseIpv6Address(addressString); Assert.assertTrue(res instanceof Inet6Address); parts = new int[]{0x2001, 0x1db8, 0x100, 0x0, 0x6, 0xff00, 0x42, 0x8329}; for(int i = 0 ; i < parts.length; ++i) { Assert.assertEquals(((byte)(parts[i]>>8)), res.getAddress()[i * 2]); Assert.assertEquals(((byte)(parts[i])), res.getAddress()[i * 2 + 1]); } Assert.assertEquals("/2001:1db8:100:0:6:ff00:42:8329", res.toString()); addressString = "2001:1db8:100::ff00:42:8329"; res = NetworkUtils.parseIpv6Address(addressString); Assert.assertTrue(res instanceof Inet6Address); parts = new int[]{0x2001, 0x1db8, 0x100, 0x0, 0x0, 0xff00, 0x42, 0x8329}; for(int i = 0 ; i < parts.length; ++i) { Assert.assertEquals(((byte)(parts[i]>>8)), res.getAddress()[i * 2]); Assert.assertEquals(((byte)(parts[i])), res.getAddress()[i * 2 + 1]); } Assert.assertEquals("/2001:1db8:100:0:0:ff00:42:8329", res.toString()); addressString = "::1"; res = NetworkUtils.parseIpv6Address(addressString); Assert.assertTrue(res instanceof Inet6Address); parts = new int[]{0, 0, 0, 0, 0, 0, 0, 0x1}; for(int i = 0 ; i < parts.length; ++i) { Assert.assertEquals(((byte)(parts[i]>>8)), res.getAddress()[i * 2]); Assert.assertEquals(((byte)(parts[i])), res.getAddress()[i * 2 + 1]); } Assert.assertEquals("/0:0:0:0:0:0:0:1", res.toString()); } @Test(expected = IOException.class) public void testIpV6AddressWithLeadingZero() throws IOException { NetworkUtils.parseIpv6Address("2001:1db8:100:03:6:ff00:42:8329"); } @Test(expected = IOException.class) public void testIpV6AddressToSmall() throws IOException { NetworkUtils.parseIpv6Address("2001:1db8:3:6:ff00:42:8329"); } @Test(expected = IOException.class) public void testIpV6AddressToLarge() throws IOException { NetworkUtils.parseIpv6Address("2001:1db8:100:3:6:7:ff00:42:8329"); } @Test(expected = IOException.class) public void testIpV6AddressMultipleColons() throws IOException { NetworkUtils.parseIpv6Address("2001:1db8:100::3:6:ff00:42:8329"); } @Test(expected = IOException.class) public void testIpV6AddressMultipleColons2() throws IOException { NetworkUtils.parseIpv6Address("2001::100::329"); } @Test(expected = IOException.class) public void testIpV6Hostname() throws IOException { NetworkUtils.parseIpv6Address("localhost"); } @Test(expected = IOException.class) public void testIpV6Hostname2() throws IOException { NetworkUtils.parseIpv6Address("ff"); } @Test(expected = IOException.class) public void testIpV6AddressStartsWithColon() throws IOException { NetworkUtils.parseIpv6Address(":2001:1db8:100:3:6:ff00:42:8329"); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/NodeStatusCodesTestCase.java000066400000000000000000000032131420065311100307110ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.testutils.category.UnitTest; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; /** * @author Stuart Douglas */ @Category(UnitTest.class) public class NodeStatusCodesTestCase { @Test public void testCodeLookup() { Assert.assertEquals("OK", StatusCodes.getReason(StatusCodes.OK)); } @Test public void testUnknownCode() { Assert.assertEquals("Unexpected reason phrase", "Unknown", StatusCodes.getReason(-1)); Assert.assertEquals("Unexpected reason phrase", "Unknown", StatusCodes.getReason(999)); Assert.assertEquals("Unexpected reason phrase", "Unknown", StatusCodes.getReason(735)); Assert.assertEquals("Unexpected reason phrase", "Unknown", StatusCodes.getReason(Integer.MAX_VALUE)); Assert.assertEquals("Unexpected reason phrase", "Unknown", StatusCodes.getReason(Integer.MIN_VALUE)); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/PathMatcherTestCase.java000066400000000000000000000131311420065311100300420ustar00rootroot00000000000000package io.undertow.util; import io.undertow.testutils.category.UnitTest; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; /** * Test the path matcher to ensure that it can handle different cases and * protect against common user mistakes either by throwing the proper exception * or by fixing them * * @author Chris Ruffalo * */ @Category(UnitTest.class) public class PathMatcherTestCase { /** * Test simple case with adding a prefix * */ @Test public void testSimplePrefixCase() { PathMatcher pathMatcher = new PathMatcher<>(); pathMatcher.addPrefixPath("prefix", "response"); Assert.assertEquals("response", pathMatcher.getPrefixPath("prefix")); Assert.assertEquals("response", pathMatcher.getPrefixPath("/prefix")); Assert.assertEquals("response", pathMatcher.getPrefixPath("/prefix/")); pathMatcher.addPrefixPath("/prefix", "new response"); Assert.assertEquals("new response", pathMatcher.getPrefixPath("prefix")); Assert.assertEquals("new response", pathMatcher.getPrefixPath("/prefix")); Assert.assertEquals("new response", pathMatcher.getPrefixPath("/prefix/")); pathMatcher.addPrefixPath("/prefix/", "different response"); Assert.assertEquals("different response", pathMatcher.getPrefixPath("prefix")); Assert.assertEquals("different response", pathMatcher.getPrefixPath("/prefix")); Assert.assertEquals("different response", pathMatcher.getPrefixPath("/prefix/")); pathMatcher.addPrefixPath("/prefix//////////////////////", "last response"); Assert.assertEquals("last response", pathMatcher.getPrefixPath("prefix")); Assert.assertEquals("last response", pathMatcher.getPrefixPath("/prefix")); Assert.assertEquals("last response", pathMatcher.getPrefixPath("/prefix/")); pathMatcher.clearPaths(); Assert.assertNull(pathMatcher.getPrefixPath("prefix")); Assert.assertNull(pathMatcher.getPrefixPath("/prefix")); Assert.assertNull(pathMatcher.getPrefixPath("/prefix/")); } /** * Test simple case with adding a prefix and getting default matches * */ @Test public void testSimpleMatchCase() { PathMatcher pathMatcher = new PathMatcher<>(); pathMatcher.addPrefixPath("prefix", "response"); Assert.assertEquals("response", pathMatcher.match("/prefix").getValue()); Assert.assertEquals("response", pathMatcher.match("/prefix/").getValue()); pathMatcher.addPrefixPath("/prefix", "new response"); Assert.assertEquals("new response", pathMatcher.match("/prefix").getValue()); Assert.assertEquals("new response", pathMatcher.match("/prefix/").getValue()); pathMatcher.addPrefixPath("/prefix/", "different response"); Assert.assertEquals("different response", pathMatcher.match("/prefix").getValue()); Assert.assertEquals("different response", pathMatcher.match("/prefix/").getValue()); pathMatcher.addPrefixPath("/prefix//////////////////////", "last response"); Assert.assertEquals("last response", pathMatcher.match("/prefix").getValue()); Assert.assertEquals("last response", pathMatcher.match("/prefix/").getValue()); pathMatcher.clearPaths(); Assert.assertNull(pathMatcher.match("/prefix").getValue()); Assert.assertNull(pathMatcher.match("/prefix/").getValue()); } /** * Test cases around default matches * */ @Test public void testSimpleDefaultCase() { PathMatcher pathMatcher = new PathMatcher<>(); pathMatcher.addPrefixPath("/", "default"); Assert.assertEquals("default", pathMatcher.getPrefixPath("/")); Assert.assertEquals("default", pathMatcher.match("/").getValue()); pathMatcher.addPrefixPath("//////", "needs normalize default"); Assert.assertEquals("needs normalize default", pathMatcher.getPrefixPath("/")); Assert.assertEquals("needs normalize default", pathMatcher.match("/").getValue()); pathMatcher.clearPaths(); Assert.assertNull(pathMatcher.getPrefixPath("/")); } /** * Test case based on value falling through to default value/handler * */ @Test public void testDefaultFallthrough() { PathMatcher pathMatcher = new PathMatcher<>("default"); // check defaults Assert.assertEquals("default", pathMatcher.getPrefixPath("/")); Assert.assertEquals("default", pathMatcher.match("/").getValue()); // add a few items pathMatcher.addPrefixPath("/test1", "test1"); pathMatcher.addPrefixPath("/test2", "test2"); pathMatcher.addPrefixPath("/test3", "test3"); pathMatcher.addPrefixPath("/test4", "test4"); // check matching with no matches Assert.assertEquals("default", pathMatcher.match("/adsfasdfdsaf").getValue()); Assert.assertEquals("default", pathMatcher.match("/ ").getValue()); Assert.assertEquals("default", pathMatcher.match("/drooadfas").getValue()); Assert.assertEquals("default", pathMatcher.match("/thing/thing").getValue()); Assert.assertEquals("default", pathMatcher.match("").getValue()); // check that matching actual matches still works Assert.assertEquals("test1", pathMatcher.match("/test1").getValue()); Assert.assertEquals("test2", pathMatcher.match("/test2").getValue()); Assert.assertEquals("test3", pathMatcher.match("/test3").getValue()); Assert.assertEquals("test4", pathMatcher.match("/test4").getValue()); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/PathTemplateMatcherTestCase.java000066400000000000000000000033601420065311100315410ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import io.undertow.server.HttpHandler; import io.undertow.testutils.category.UnitTest; import io.undertow.util.PathTemplateMatcher.PathMatchResult; import org.junit.Test; import org.junit.experimental.categories.Category; import java.util.ArrayList; import java.util.Arrays; @Category(UnitTest.class) public class PathTemplateMatcherTestCase { @Test public void pathTemplateMatchedShouldKeepOrder() { PathTemplateMatcher matcher = new PathTemplateMatcher<>(); matcher.add("/{context}/{version}/{entity}/{id}", exchange -> { }); PathMatchResult match = matcher.match("/api/v3/users/1"); assertNotNull("Not matched", match); assertNotNull("Value", match.getValue()); assertEquals("Matched parameters should keep order of definition", Arrays.asList("api", "v3", "users", "1"), new ArrayList<>(match.getParameters().values())); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/PathTemplateTestCase.java000066400000000000000000000140771420065311100302440ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.testutils.category.UnitTest; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import java.util.HashMap; import java.util.Map; import java.util.TreeSet; /** * @author Stuart Douglas */ @Category(UnitTest.class) public class PathTemplateTestCase { @Test public void testMatches() { // test normal use testMatch("/docs/mydoc", "/docs/mydoc"); testMatch("/docs/{docId}", "/docs/mydoc", "docId", "mydoc"); testMatch("/docs/{docId}/{op}", "/docs/mydoc/read", "docId", "mydoc", "op", "read"); testMatch("/docs/{docId}/{op}/{allowed}", "/docs/mydoc/read/true", "docId", "mydoc", "op", "read", "allowed", "true"); testMatch("/docs/{docId}/operation/{op}", "/docs/mydoc/operation/read", "docId", "mydoc", "op", "read"); testMatch("/docs/{docId}/read", "/docs/mydoc/read", "docId", "mydoc"); testMatch("/docs/{docId}/read", "/docs/mydoc/read?myQueryParam", "docId", "mydoc"); // test no leading slash testMatch("docs/mydoc", "/docs/mydoc"); testMatch("docs/{docId}", "/docs/mydoc", "docId", "mydoc"); testMatch("docs/{docId}/{op}", "/docs/mydoc/read", "docId", "mydoc", "op", "read"); testMatch("docs/{docId}/{op}/{allowed}", "/docs/mydoc/read/true", "docId", "mydoc", "op", "read", "allowed", "true"); testMatch("docs/{docId}/operation/{op}", "/docs/mydoc/operation/read", "docId", "mydoc", "op", "read"); testMatch("docs/{docId}/read", "/docs/mydoc/read", "docId", "mydoc"); testMatch("docs/{docId}/read", "/docs/mydoc/read?myQueryParam", "docId", "mydoc"); // test trailing slashes testMatch("/docs/mydoc/", "/docs/mydoc/"); testMatch("/docs/{docId}/", "/docs/mydoc/", "docId", "mydoc"); testMatch("/docs/{docId}/{op}/", "/docs/mydoc/read/", "docId", "mydoc", "op", "read"); testMatch("/docs/{docId}/{op}/{allowed}/", "/docs/mydoc/read/true/", "docId", "mydoc", "op", "read", "allowed", "true"); testMatch("/docs/{docId}/operation/{op}/", "/docs/mydoc/operation/read/", "docId", "mydoc", "op", "read"); testMatch("/docs/{docId}/read/", "/docs/mydoc/read/", "docId", "mydoc"); // test straight replacement of template testMatch("/{foo}", "/bob", "foo", "bob"); testMatch("{foo}", "/bob", "foo", "bob"); testMatch("/{foo}/", "/bob/", "foo", "bob"); // test that brackets (and the possibility of recursive templates) don't mess up the matching testMatch("/{value}", "/{value}", "value", "{value}"); } @Test public void wildCardTests() { // wildcard matches testMatch("/*", "/docs/mydoc/test","*","docs/mydoc/test"); testMatch("/docs/*", "/docs/mydoc/test","*","mydoc/test"); testMatch("/docs*", "/docs/mydoc/test","*","/mydoc/test"); testMatch("/docs/*", "/docs/mydoc/test/test2","*","mydoc/test/test2"); testMatch("/docs/{docId}/*", "/docs/mydoc/test", "docId", "mydoc", "*","test"); testMatch("/docs/{docId}/*", "/docs/mydoc/", "docId", "mydoc", "*",""); testMatch("/docs/{docId}/*", "/docs/mydoc/test/test2/test3/test4", "docId", "mydoc", "*","test/test2/test3/test4"); testMatch("/docs/{docId}/{docId2}/*", "/docs/mydoc/test/test2/test3/test4", "docId", "mydoc","docId2", "test", "*","test2/test3/test4"); } @Test(expected=IllegalArgumentException.class) public void testNullPath() { PathTemplate.create(null); } @Test public void testDetectDuplicates() { final TreeSet seen = new TreeSet<>(); seen.add(PathTemplate.create("/bob/{foo}")); Assert.assertTrue(seen.contains(PathTemplate.create("/bob/{ak}"))); Assert.assertFalse(seen.contains(PathTemplate.create("/bob/{ak}/other"))); } @Test public void testTrailingSlash() { PathTemplate template = PathTemplate.create("/bob/"); Assert.assertFalse(template.matches("/bob", new HashMap<>())); Assert.assertTrue(template.matches("/bob/", new HashMap<>())); template = PathTemplate.create("/bob/{id}/"); Assert.assertFalse(template.matches("/bob/1", new HashMap<>())); Assert.assertTrue(template.matches("/bob/1/", new HashMap<>())); } private void testMatch(final String template, final String path, final String ... pathParams) { Assert.assertEquals(0, pathParams.length % 2); final Map expected = new HashMap<>(); for(int i = 0; i < pathParams.length; i+=2) { expected.put(pathParams[i], pathParams[i+1]); } final Map params = new HashMap<>(); PathTemplate pathTemplate = PathTemplate.create(template); Assert.assertTrue("Failed. Template: " + pathTemplate, pathTemplate.matches(path, params)); Assert.assertEquals(expected, params); if(template.endsWith("*") && ! template.contains("{")){ Assert.assertEquals("Failed. Template: "+pathTemplate+"Must have a part representing the wildcard",1,new PathTemplateFriend(pathTemplate).getPartAmount()); } } static class PathTemplateFriend { private final PathTemplate template; PathTemplateFriend(PathTemplate template) { this.template = template; } int getPartAmount() { return template.parts.size(); } } } SameSiteNoneIncompatibleClientCheckerTestCase.java000066400000000000000000000150051420065311100350720ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/util/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.testutils.category.UnitTest; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; @Category(UnitTest.class) public class SameSiteNoneIncompatibleClientCheckerTestCase { /** * List of incompatible User-Agents that contain bug in same-site cookie behavior. * * @see SameSiteNoneIncompatibleClientChecker */ String[] incompatibleWebKitUserAgents = { // Safari on Mac OS X 10.14 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.2 Safari/605.1.15", // Safari on iOS 12 "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1", // Chrome on iOS 12 "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/69.0.3497.91 Mobile/15E148 Safari/605.1" }; /** * List of compatible User-Agents that not containing bug in same-site cookie behavior. * There is also empty string and 'null' to check incorrect input User-Agent value behavior. * * @see SameSiteNoneIncompatibleClientChecker */ String[] compatibleWebKitUserAgents = { // Safari on Mac OS X 10.15 "Mozilla/6.0 (Macintosh; U; Intel Mac OS X 10_15_3) AppleWebKit/663.16 (KHTML, like Gecko) Version/10.0 Safari/663.16", // Safari on iOS 13 "Mozilla/5.0 (iPhone; CPU iPhone OS 13_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.1 Mobile/15E148 Safari/604.1", // Chrome on iOS 13 "Mozilla/5.0 (iPhone; CPU iPhone OS 13_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/77.0.3865.69 Mobile/15E148 Safari/605.1", "", null }; /** * List of incompatible User-Agents that drop same-site cookies entirely. * * @see SameSiteNoneIncompatibleClientChecker */ String[] incompatibleWebKitUserAgents2 = { // Chrome 51 on Windows "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", // Chrome 62 on Windows "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36", // UC Browser 11.3.8 on Android "Mozilla/5.0 (Linux; U; Android 7.0; en-US; ...) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 UCBrowser/11.3.8.976 U3/0.8.0 Mobile Safari/534.30", // UC Browser 12.13.0 on Android "Mozilla/5.0 (Linux; U; Android 9; en-US; ...) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.13.0.1207 Mobile Safari/537.36", }; /** * List of compatible User-Agents that don't drop same-site cookies entirely. * There is also empty string and 'null' to check incorrect input User-Agent value behavior. * * @see SameSiteNoneIncompatibleClientChecker */ String[] compatibleWebKitUserAgents2 = { // Chrome 72 on Windows "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", // Chrome 78 on Linux "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36", // UC Browser 12.13.4 on Android "Mozilla/5.0 (Linux; U; Android 10; en-US; ...) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.13.4.1214 Mobile Safari/537.36", "", null }; @Test public void testShouldSendSameSiteNone() { boolean result; for (String userAgent : incompatibleWebKitUserAgents) { result = SameSiteNoneIncompatibleClientChecker.shouldSendSameSiteNone(userAgent); Assert.assertFalse("Tested user-agent: '" + userAgent + "'", result); } for (String userAgent : compatibleWebKitUserAgents) { result = SameSiteNoneIncompatibleClientChecker.shouldSendSameSiteNone(userAgent); Assert.assertTrue("Tested user-agent: '" + userAgent + "'", result); } for (String userAgent : incompatibleWebKitUserAgents2) { result = SameSiteNoneIncompatibleClientChecker.shouldSendSameSiteNone(userAgent); Assert.assertFalse("Tested user-agent: '" + userAgent + "'", result); } for (String userAgent : compatibleWebKitUserAgents2) { result = SameSiteNoneIncompatibleClientChecker.shouldSendSameSiteNone(userAgent); Assert.assertTrue("Tested user-agent: '" + userAgent + "'", result); } } @Test public void testIsSameSiteNoneIncompatible() { boolean result; for (String userAgent : incompatibleWebKitUserAgents) { result = SameSiteNoneIncompatibleClientChecker.isSameSiteNoneIncompatible(userAgent); Assert.assertTrue("Tested user-agent: '" + userAgent + "'", result); } for (String userAgent : compatibleWebKitUserAgents) { result = SameSiteNoneIncompatibleClientChecker.isSameSiteNoneIncompatible(userAgent); Assert.assertFalse("Tested user-agent: '" + userAgent + "'", result); } for (String userAgent : incompatibleWebKitUserAgents2) { result = SameSiteNoneIncompatibleClientChecker.isSameSiteNoneIncompatible(userAgent); Assert.assertTrue("Tested user-agent: '" + userAgent + "'", result); } for (String userAgent : compatibleWebKitUserAgents2) { result = SameSiteNoneIncompatibleClientChecker.isSameSiteNoneIncompatible(userAgent); Assert.assertFalse("Tested user-agent: '" + userAgent + "'", result); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/SimpleObjectPoolTestCase.java000066400000000000000000000054601420065311100310620ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; /** * @author Carter Kozak */ public class SimpleObjectPoolTestCase { @Rule public final ExpectedException expected = ExpectedException.none(); @Test public void testObjectAlreadyReturned() { SimpleObjectPool pool = new SimpleObjectPool<>(1, Object::new, obj -> {}, obj -> {}); PooledObject pooled = pool.allocate(); pooled.close(); expected.expect(IllegalStateException.class); pooled.getObject(); } @Test public void testCloseMayBeInvokedMultipleTimesWhenObjectIsRecycled() { AtomicInteger recycled = new AtomicInteger(); AtomicInteger destroyed = new AtomicInteger(); SimpleObjectPool pool = new SimpleObjectPool<>( 1, Object::new, obj -> recycled.incrementAndGet(), obj -> destroyed.incrementAndGet()); PooledObject pooled = pool.allocate(); pooled.close(); pooled.close(); assertEquals("Pooled object should only be recycled once", 1, recycled.get()); assertEquals("Pooled object should be queued for reuse, not destroyed", 0, destroyed.get()); } @Test public void testCloseMayBeInvokedMultipleTimesWhenObjectIsConsumed() { AtomicInteger recycled = new AtomicInteger(); AtomicInteger destroyed = new AtomicInteger(); SimpleObjectPool pool = new SimpleObjectPool<>( 1, Object::new, obj -> recycled.incrementAndGet(), obj -> destroyed.incrementAndGet()); PooledObject initial = pool.allocate(); PooledObject pooled = pool.allocate(); initial.close(); // This object fills the queue so that 'pooled' should be destroyed pooled.close(); pooled.close(); assertEquals("Each pooled object should be recycled", 2, recycled.get()); assertEquals("Pooled object should be destroyed exactly once", 1, destroyed.get()); } }undertow-2.2.16.Final/core/src/test/java/io/undertow/util/SingleByteStreamSinkConduit.java000066400000000000000000000074401420065311100316100ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.AbstractStreamSinkConduit; import org.xnio.conduits.StreamSinkConduit; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; /** * A channel that will only write a single byte at a time for a set number of calls to write. * * This can be used for testing purposes, to make sure that resuming writes works as expected. * * @author Stuart Douglas */ public class SingleByteStreamSinkConduit extends AbstractStreamSinkConduit { private final int singleByteWrites; private int state = 0; /** * Construct a new instance. * * @param next the delegate conduit to set * @param singleByteWrites */ public SingleByteStreamSinkConduit(StreamSinkConduit next, int singleByteWrites) { super(next); this.singleByteWrites = singleByteWrites; } @Override public int write(ByteBuffer src) throws IOException { if (state > singleByteWrites) { return next.write(src); } if (state++ % 2 == 0) { return 0; } else { if (src.remaining() == 0) { return 0; } int limit = src.limit(); try { src.limit(src.position() + 1); return next.write(src); } finally { src.limit(limit); } } } @Override public long write(ByteBuffer[] srcs, int offs, int len) throws IOException { if (state > singleByteWrites) { return next.write(srcs, offs, len); } if (state++ % 2 == 0) { return 0; } else { ByteBuffer src = null; for(int i = offs; i < offs + len; ++i) { if(srcs[i].hasRemaining()) { src = srcs[i]; break; } } if(src == null) { return 0; } int limit = src.limit(); try { src.limit(src.position() + 1); return next.write(src); } finally { src.limit(limit); } } } @Override public long transferFrom(FileChannel src, long position, long count) throws IOException { if (state > singleByteWrites) { return next.transferFrom(src, position, count); } if (state++ % 2 == 0) { return 0; } else { return next.transferFrom(src, position, count == 0 ? 0 : 1); } } @Override public long transferFrom(StreamSourceChannel source, long count, ByteBuffer throughBuffer) throws IOException { if (state > singleByteWrites) { return next.transferFrom(source, count, throughBuffer); } if (state++ % 2 == 0) { throughBuffer.limit(throughBuffer.position()); return 0; } else { return next.transferFrom(source, count == 0 ? 0 : 1, throughBuffer); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/SingleByteStreamSourceConduit.java000066400000000000000000000102141420065311100321350ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import org.xnio.channels.StreamSinkChannel; import org.xnio.conduits.AbstractStreamSourceConduit; import org.xnio.conduits.StreamSourceConduit; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; /** * @author Stuart Douglas */ public class SingleByteStreamSourceConduit extends AbstractStreamSourceConduit { private final int singleByteReads; private int state = 0; /** * Construct a new instance. * * @param next the delegate conduit to set * @param singleByteReads */ public SingleByteStreamSourceConduit(StreamSourceConduit next, int singleByteReads) { super(next); this.singleByteReads = singleByteReads; } @Override public int read(ByteBuffer dst) throws IOException { if (state > singleByteReads || dst.remaining() == 1) { //we always let a single byte read through, otherwise SSL renegotiation breaks return next.read(dst); } if (state++ % 2 == 0) { wakeupIfSsl(); return 0; } else { if (dst.remaining() == 0) { return 0; } int limit = dst.limit(); try { dst.limit(dst.position() + 1); int read = next.read(dst); if(read != -1) { wakeupIfSsl(); } return read; } finally { dst.limit(limit); } } } private void wakeupIfSsl() { //todo: work around a bug in the SSL channel where the read listener will not be invoked if there is more data in the buffer if(isReadResumed() && next.getClass().getSimpleName().startsWith("Jsse")) { wakeupReads(); } } @Override public long read(ByteBuffer[] dsts, int offs, int len) throws IOException { if (state > singleByteReads) { return next.read(dsts, offs, len); } if (state++ % 2 == 0) { return 0; } else { ByteBuffer dst = null; for (int i = offs; i < offs + len; ++i) { if (dsts[i].hasRemaining()) { dst = dsts[i]; break; } } if (dst == null) { return 0; } int limit = dst.limit(); try { dst.limit(dst.position() + 1); return next.read(dst); } finally { dst.limit(limit); } } } @Override public long transferTo(long position, long count, FileChannel target) throws IOException { if (state > singleByteReads) { return next.transferTo(position, count, target); } if (state++ % 2 == 0) { return 0; } else { return next.transferTo(position, count == 0 ? 0 : count, target); } } @Override public long transferTo(long count, ByteBuffer throughBuffer, StreamSinkChannel target) throws IOException { if (state > singleByteReads) { return next.transferTo(count, throughBuffer, target); } if (state++ % 2 == 0) { throughBuffer.position(throughBuffer.limit()); return 0; } else { return next.transferTo(count == 0 ? 0 : count, throughBuffer, target); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/SubstringMapTestCase.java000066400000000000000000000057111420065311100302650ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.testutils.category.UnitTest; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; /** * @author Stuart Douglas */ @Category(UnitTest.class) public class SubstringMapTestCase { public static final int NUM_TEST_VALUES = 1000; @Test public void testSubstringMap() { SubstringMap paths = new SubstringMap<>(); for (int count = 0; count < 10; ++count) { int seed = new Random().nextInt(); Random random = new Random(seed); System.out.println("Using Seed " + seed); List parts = new ArrayList<>(); Set keys = new HashSet<>(); for (int i = 0; i < NUM_TEST_VALUES; ++i) { String s = null; do { byte[] bytes = new byte[random.nextInt(30) + 5]; random.nextBytes(bytes); s = FlexBase64.encodeString(bytes, false); } while (keys.contains(s)); keys.add(s); parts.add(s); paths.put(s, i); Assert.assertEquals(Integer.valueOf(i), paths.get(s, s.length()).getValue()); Assert.assertEquals(Integer.valueOf(i), paths.get(s + "fooosdf", s.length()).getValue()); String missing = s + "asdfdasfasf"; Assert.assertNull(paths.get(missing, missing.length())); } for (String k : paths.keys()) { Assert.assertTrue(keys.remove(k)); } Assert.assertEquals(0, keys.size()); for (int i = 0; i < NUM_TEST_VALUES; ++i) { String p = parts.get(i); Assert.assertEquals(Integer.valueOf(i), paths.get(p, p.length()).getValue()); Assert.assertEquals(Integer.valueOf(i), paths.get(p + "asdfdsafasfw", p.length()).getValue()); } for (int i = 0; i < NUM_TEST_VALUES; ++i) { Integer p = paths.remove(parts.get(i)); Assert.assertEquals(Integer.valueOf(i), p); } } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/TestVersion.java000066400000000000000000000024651420065311100265030ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import io.undertow.Version; import io.undertow.testutils.category.UnitTest; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; /** * @author Tomaz Cerar (c) 2013 Red Hat Inc. */ @Category(UnitTest.class) public class TestVersion { @Test public void testVersionSet() { Assert.assertNotNull(Version.getVersionString()); String version = Version.getVersionString(); System.out.println("version = " + version); Assert.assertNotSame("Unknown", version); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/URLUtilsTestCase.java000066400000000000000000000105001420065311100273220ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import io.undertow.testutils.category.UnitTest; /** * @author Oleksandr Radchykov * @author Andre Schaefer */ @RunWith(Parameterized.class) @Category(UnitTest.class) public class URLUtilsTestCase { @Parameterized.Parameters public static Object[] spaceCodes() { return new Object[]{"%2f", "%2F"}; } @Parameterized.Parameter public String spaceCode = "%2f"; @Test public void testDecodingWithEncodedAndDecodedSlashAndSlashDecodingDisabled() throws Exception { String url = "http://localhost:3001/by-path/wild%20card/wild%28west%29/wild" + spaceCode + "wolf"; final String result = URLUtils.decode(url, Charset.defaultCharset().name(), false, new StringBuilder()); assertEquals("http://localhost:3001/by-path/wild card/wild(west)/wild" + spaceCode + "wolf", result); } @Test public void testDecodingURLMustNotMutateSpaceSymbolsCaseIfSpaceDecodingDisabled() throws Exception { final String url = "http://localhost:3001/wild" + spaceCode + "west"; final String result = URLUtils.decode(url, Charset.defaultCharset().name(), false, new StringBuilder()); assertEquals(url, result); } @Test public void testIsAbsoluteUrlRecognizingAbsolutUrls() { assertTrue(URLUtils.isAbsoluteUrl("https://some.valid.url:8080/path?query=val")); assertTrue(URLUtils.isAbsoluteUrl("http://some.valid.url:8080/path?query=val")); assertTrue(URLUtils.isAbsoluteUrl("http://some.valid.url")); } @Test public void testIsAbsoluteUrlRecognizingAppUrls() { assertTrue(URLUtils.isAbsoluteUrl("com.example.app:/oauth2redirect/example-provider")); assertTrue(URLUtils.isAbsoluteUrl("com.example.app:/oauth2redirect/example-provider?query=val")); } @Test public void testIsAbsoluteUrlRecognizingRelativeUrls() { assertFalse(URLUtils.isAbsoluteUrl("relative")); assertFalse(URLUtils.isAbsoluteUrl("relative/path")); assertFalse(URLUtils.isAbsoluteUrl("relative/path?query=val")); assertFalse(URLUtils.isAbsoluteUrl("relative/path:path")); assertFalse(URLUtils.isAbsoluteUrl("/root/relative/path")); } @Test public void testIsAbsoluteUrlRecognizingEmptyOrNullAsRelative() { assertFalse(URLUtils.isAbsoluteUrl(null)); assertFalse(URLUtils.isAbsoluteUrl("")); } @Test public void testIsAbsoluteUrlIgnoresSyntaxErrorsAreNotAbsolute() { assertFalse(URLUtils.isAbsoluteUrl(":")); } /** * @see UNDERTOW-1552 */ @Test public void testDecodingWithTrailingPercentChar() throws Exception { final String[] urls = new String[] {"https://example.com/?a=%", "https://example.com/?a=%2"}; for (final String url : urls) { try { URLUtils.decode(url, StandardCharsets.UTF_8.name(), false, new StringBuilder()); Assert.fail("Decode was expected to fail for " + url); } catch (IllegalArgumentException iae) { // expected } } } @Test public void testIsAbsoluteUrlInvalidChars() { assertTrue(URLUtils.isAbsoluteUrl("http://test.com/foobar?test={abc}")); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/util/mime-multiline.txt000066400000000000000000000002261420065311100270340ustar00rootroot00000000000000--unique-boundary-1 Content-type: text/plain; charset="ascii" Here is some text. --unique-boundary-1 Here is some more text. --unique-boundary-1-- undertow-2.2.16.Final/core/src/test/java/io/undertow/util/mime-utf8.txt000066400000000000000000000004021420065311100257140ustar00rootroot00000000000000This is a preamble --unique-boundary-1 Content-type: text/plain Content-Disposition: attachment; filename=个专为语文教学而设计的电脑软件.txt Just some chinese characters I copied from the internet, no idea what it says. --unique-boundary-1-- undertow-2.2.16.Final/core/src/test/java/io/undertow/util/mime1.txt000066400000000000000000000002271420065311100251160ustar00rootroot00000000000000This is a preamble --unique-boundary-1 Content-type: text/plain Here is some text. --unique-boundary-1 Here is some more text. --unique-boundary-1-- undertow-2.2.16.Final/core/src/test/java/io/undertow/util/mime2.txt000066400000000000000000000002041420065311100251120ustar00rootroot00000000000000--unique-boundary-1 Content-type: text/plain Here is some text. --unique-boundary-1 Here is some more text. --unique-boundary-1-- undertow-2.2.16.Final/core/src/test/java/io/undertow/util/mime3.txt000066400000000000000000000003531420065311100251200ustar00rootroot00000000000000--unique-boundary-1 Content-type: text/plain Content-Transfer-encoding: base64 VGhpcyBpcyBzb21lIGJhc2U2NCB0ZXh0Lg== --unique-boundary-1 Content-Transfer-encoding: base64 VGhpcyBpcyBzb21lIG1vcmUgYmFzZTY0IHRleHQu --unique-boundary-1-- undertow-2.2.16.Final/core/src/test/java/io/undertow/util/mime4.txt000066400000000000000000000001741420065311100251220ustar00rootroot00000000000000--someboundarytext Content-type: text/plain Content-Transfer-encoding: quoted-printable time=3Dmoney. --someboundarytext-- undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/000077500000000000000000000000001420065311100245405ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/client/000077500000000000000000000000001420065311100260165ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/client/version13/000077500000000000000000000000001420065311100276475ustar00rootroot00000000000000WebSocketClient13TestCase.java000066400000000000000000000255101420065311100352630ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/client/version13/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.client.version13; import java.io.IOException; import java.net.URI; import java.util.Deque; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import io.undertow.Undertow; import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.ConnectHandler; import io.undertow.testutils.ProxyIgnore; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.IoFuture; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.Xnio; import org.xnio.XnioWorker; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.util.StringWriteChannelListener; import io.undertow.websockets.client.WebSocketClient; import io.undertow.websockets.core.AbstractReceiveListener; import io.undertow.websockets.core.BufferedTextMessage; import io.undertow.websockets.core.StreamSinkFrameChannel; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketFrameType; import io.undertow.websockets.core.protocol.server.AutobahnWebSocketServer; import static io.undertow.testutils.StopServerWithExternalWorkerUtils.stopWorker; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) @HttpOneOnly public class WebSocketClient13TestCase { private static XnioWorker worker; private static Undertow server; private static final Deque connectLog = new LinkedBlockingDeque<>(); @BeforeClass public static void setup() throws IOException { DefaultServer.setRootHandler(AutobahnWebSocketServer.getRootHandler()); Xnio xnio = Xnio.getInstance(DefaultServer.class.getClassLoader()); worker = xnio.createWorker(OptionMap.builder() .set(Options.WORKER_IO_THREADS, 2) .set(Options.CONNECTION_HIGH_WATER, 1000000) .set(Options.CONNECTION_LOW_WATER, 1000000) .set(Options.WORKER_TASK_CORE_THREADS, 30) .set(Options.WORKER_TASK_MAX_THREADS, 30) .set(Options.TCP_NODELAY, true) .set(Options.CORK, true) .getMap()); final ConnectHandler handler = new ConnectHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.setStatusCode(500); } }); DefaultServer.startSSLServer(); server = Undertow.builder().addHttpListener(DefaultServer.getHostPort("default") + 10, DefaultServer.getHostAddress("default")) .setHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { connectLog.add(exchange.getRequestMethod() + " " + exchange.getRelativePath()); handler.handleRequest(exchange); } }) .build(); server.start(); } @AfterClass public static void stopAndShutdown() throws IOException { server.stop(); server = null; DefaultServer.stopSSLServer(); stopWorker(worker); } @Test public void testTextMessage() throws Exception { final WebSocketChannel webSocketChannel = WebSocketClient.connectionBuilder(worker, DefaultServer.getBufferPool(), new URI(DefaultServer.getDefaultServerURL())).connect().get(); final CountDownLatch latch = new CountDownLatch(1); final AtomicReference result = new AtomicReference<>(); webSocketChannel.getReceiveSetter().set(new AbstractReceiveListener() { @Override protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage message) throws IOException { String data = message.getData(); result.set(data); latch.countDown(); } @Override protected void onError(WebSocketChannel channel, Throwable error) { super.onError(channel, error); error.printStackTrace(); latch.countDown(); } }); webSocketChannel.resumeReceives(); StreamSinkFrameChannel sendChannel = webSocketChannel.send(WebSocketFrameType.TEXT); new StringWriteChannelListener("Hello World").setup(sendChannel); latch.await(10, TimeUnit.SECONDS); Assert.assertEquals("Hello World", result.get()); webSocketChannel.sendClose(); } @Test public void testTextMessageWss() throws Exception { testTextMessageSecure("wss://"); } @Test public void testTextMessageHttps() throws Exception { testTextMessageSecure("https://"); } public void testTextMessageSecure(final String urlProtocol) throws Exception { UndertowXnioSsl ssl = new UndertowXnioSsl(Xnio.getInstance(), OptionMap.EMPTY, DefaultServer.getClientSSLContext()); final WebSocketClient.ConnectionBuilder connectionBuilder = WebSocketClient.connectionBuilder(worker, DefaultServer.getBufferPool(), new URI(urlProtocol + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostSSLPort("default"))) .setSsl(ssl); IoFuture future = connectionBuilder.connect(); future.await(4, TimeUnit.SECONDS); final WebSocketChannel webSocketChannel = future.get(); final CountDownLatch latch = new CountDownLatch(1); final AtomicReference result = new AtomicReference<>(); webSocketChannel.getReceiveSetter().set(new AbstractReceiveListener() { @Override protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage message) throws IOException { String data = message.getData(); result.set(data); latch.countDown(); } @Override protected void onError(WebSocketChannel channel, Throwable error) { super.onError(channel, error); error.printStackTrace(); latch.countDown(); } }); webSocketChannel.resumeReceives(); StreamSinkFrameChannel sendChannel = webSocketChannel.send(WebSocketFrameType.TEXT); new StringWriteChannelListener("Hello World").setup(sendChannel); latch.await(10, TimeUnit.SECONDS); Assert.assertEquals("Hello World", result.get()); webSocketChannel.sendClose(); } @Test @ProxyIgnore public void testMessageViaProxy() throws Exception { final WebSocketChannel webSocketChannel = WebSocketClient.connectionBuilder(worker, DefaultServer.getBufferPool(), new URI(DefaultServer.getDefaultServerURL())) .setProxyUri(new URI("http", null, DefaultServer.getHostAddress("default"), DefaultServer.getHostPort("default") + 10, "/proxy", null, null)) .connect().get(); final CountDownLatch latch = new CountDownLatch(1); final AtomicReference result = new AtomicReference<>(); webSocketChannel.getReceiveSetter().set(new AbstractReceiveListener() { @Override protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage message) throws IOException { String data = message.getData(); result.set(data); latch.countDown(); } @Override protected void onError(WebSocketChannel channel, Throwable error) { super.onError(channel, error); error.printStackTrace(); latch.countDown(); } }); webSocketChannel.resumeReceives(); StreamSinkFrameChannel sendChannel = webSocketChannel.send(WebSocketFrameType.TEXT); new StringWriteChannelListener("Hello World").setup(sendChannel); latch.await(10, TimeUnit.SECONDS); Assert.assertEquals("Hello World", result.get()); webSocketChannel.sendClose(); Assert.assertEquals("CONNECT " + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default"), connectLog.poll()); } @Test @ProxyIgnore public void testMessageViaWssProxy() throws Exception { final WebSocketChannel webSocketChannel = WebSocketClient.connectionBuilder(worker, DefaultServer.getBufferPool(), new URI(DefaultServer.getDefaultServerSSLAddress())) .setSsl(new UndertowXnioSsl(Xnio.getInstance(), OptionMap.EMPTY, DefaultServer.getClientSSLContext())) .setProxyUri(new URI("http", null, DefaultServer.getHostAddress("default"), DefaultServer.getHostPort("default") + 10, "/proxy", null, null)) .connect().get(); final CountDownLatch latch = new CountDownLatch(1); final AtomicReference result = new AtomicReference<>(); webSocketChannel.getReceiveSetter().set(new AbstractReceiveListener() { @Override protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage message) throws IOException { String data = message.getData(); result.set(data); latch.countDown(); } @Override protected void onError(WebSocketChannel channel, Throwable error) { super.onError(channel, error); error.printStackTrace(); latch.countDown(); } }); webSocketChannel.resumeReceives(); StreamSinkFrameChannel sendChannel = webSocketChannel.send(WebSocketFrameType.TEXT); new StringWriteChannelListener("Hello World").setup(sendChannel); latch.await(10, TimeUnit.SECONDS); Assert.assertEquals("Hello World", result.get()); webSocketChannel.sendClose(); Assert.assertEquals("CONNECT " + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostSSLPort("default"), connectLog.poll()); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/core/000077500000000000000000000000001420065311100254705ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/core/protocol/000077500000000000000000000000001420065311100273315ustar00rootroot00000000000000AbstractWebSocketServerTest.java000066400000000000000000000171471420065311100355300ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/core/protocol/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol; import io.netty.buffer.Unpooled; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.util.NetworkUtils; import io.undertow.websockets.WebSocketConnectionCallback; import io.undertow.websockets.WebSocketProtocolHandshakeHandler; import io.undertow.websockets.core.AbstractReceiveListener; import io.undertow.websockets.core.BufferedBinaryMessage; import io.undertow.websockets.core.BufferedTextMessage; import io.undertow.websockets.core.WebSocketCallback; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSockets; import io.undertow.websockets.spi.WebSocketHttpExchange; import io.undertow.websockets.utils.FrameChecker; import io.undertow.websockets.utils.WebSocketTestClient; import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketVersion; import io.netty.util.CharsetUtil; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.FutureResult; import org.xnio.Pooled; import java.io.IOException; import java.net.URI; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicBoolean; /** * @author Norman Maurer */ @RunWith(DefaultServer.class) @HttpOneOnly public class AbstractWebSocketServerTest { @Test public void testText() throws Exception { if (getVersion() == WebSocketVersion.V00) { // ignore 00 tests for now return; } final AtomicBoolean connected = new AtomicBoolean(false); DefaultServer.setRootHandler(new WebSocketProtocolHandshakeHandler(new WebSocketConnectionCallback() { @Override public void onConnect(final WebSocketHttpExchange exchange, final WebSocketChannel channel) { connected.set(true); channel.getReceiveSetter().set(new AbstractReceiveListener() { @Override protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage message) throws IOException { String string = message.getData(); if (string.equals("hello")) { WebSockets.sendText("world", channel, null); } else { WebSockets.sendText(string, channel, null); } } }); channel.resumeReceives(); } })); final FutureResult latch = new FutureResult(); WebSocketTestClient client = new WebSocketTestClient(getVersion(), new URI("ws://" + NetworkUtils.formatPossibleIpv6Address(DefaultServer.getHostAddress("default")) + ":" + DefaultServer.getHostPort("default") + "/")); client.connect(); client.send(new TextWebSocketFrame(Unpooled.copiedBuffer("hello", CharsetUtil.US_ASCII)), new FrameChecker(TextWebSocketFrame.class, "world".getBytes(CharsetUtil.US_ASCII), latch)); latch.getIoFuture().get(); client.destroy(); } @Test public void testBinary() throws Exception { if (getVersion() == WebSocketVersion.V00) { // ignore 00 tests for now return; } final AtomicBoolean connected = new AtomicBoolean(false); DefaultServer.setRootHandler(new WebSocketProtocolHandshakeHandler(new WebSocketConnectionCallback() { @Override public void onConnect(final WebSocketHttpExchange exchange, final WebSocketChannel channel) { connected.set(true); channel.getReceiveSetter().set(new AbstractReceiveListener() { @Override protected void onFullBinaryMessage(WebSocketChannel channel, BufferedBinaryMessage message) throws IOException { final Pooled data = message.getData(); WebSockets.sendBinary(data.getResource(), channel, new WebSocketCallback() { @Override public void complete(WebSocketChannel channel, Void context) { data.close(); } @Override public void onError(WebSocketChannel channel, Void context, Throwable throwable) { data.close(); } }); } }); channel.resumeReceives(); } })); final FutureResult latch = new FutureResult(); final byte[] payload = "payload".getBytes(); WebSocketTestClient client = new WebSocketTestClient(getVersion(), new URI("ws://" + NetworkUtils.formatPossibleIpv6Address(DefaultServer.getHostAddress("default")) + ":" + DefaultServer.getHostPort("default") + "/")); client.connect(); client.send(new BinaryWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(BinaryWebSocketFrame.class, payload, latch)); latch.getIoFuture().get(); client.destroy(); } @Test public void testCloseFrame() throws Exception { if (getVersion() == WebSocketVersion.V00) { // ignore 00 tests for now return; } final AtomicBoolean connected = new AtomicBoolean(false); DefaultServer.setRootHandler(new WebSocketProtocolHandshakeHandler(new WebSocketConnectionCallback() { @Override public void onConnect(final WebSocketHttpExchange exchange, final WebSocketChannel channel) { connected.set(true); channel.getReceiveSetter().set(new AbstractReceiveListener() { @Override protected void onFullCloseMessage(WebSocketChannel channel, BufferedBinaryMessage message) throws IOException { message.getData().close(); channel.sendClose(); } }); channel.resumeReceives(); } })); final AtomicBoolean receivedResponse = new AtomicBoolean(false); final FutureResult latch = new FutureResult(); WebSocketTestClient client = new WebSocketTestClient(getVersion(), new URI("ws://" + NetworkUtils.formatPossibleIpv6Address(DefaultServer.getHostAddress("default")) + ":" + DefaultServer.getHostPort("default") + "/")); client.connect(); client.send(new CloseWebSocketFrame(), new FrameChecker(CloseWebSocketFrame.class, new byte[0], latch)); latch.getIoFuture().get(); Assert.assertFalse(receivedResponse.get()); client.destroy(); } protected WebSocketVersion getVersion() { return WebSocketVersion.V00; } } WebSocket07ServerTest.java000066400000000000000000000077171420065311100342150ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/core/protocol/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol; import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketVersion; import io.undertow.testutils.DefaultServer; import io.undertow.util.NetworkUtils; import io.undertow.websockets.WebSocketConnectionCallback; import io.undertow.websockets.WebSocketProtocolHandshakeHandler; import io.undertow.websockets.core.AbstractReceiveListener; import io.undertow.websockets.core.BufferedBinaryMessage; import io.undertow.websockets.core.WebSocketCallback; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSockets; import io.undertow.websockets.spi.WebSocketHttpExchange; import io.undertow.websockets.utils.FrameChecker; import io.undertow.websockets.utils.WebSocketTestClient; import org.junit.Test; import org.xnio.FutureResult; import org.xnio.Pooled; import java.io.IOException; import java.net.URI; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicBoolean; /** * @author Norman Maurer */ public class WebSocket07ServerTest extends AbstractWebSocketServerTest { @Override protected WebSocketVersion getVersion() { return WebSocketVersion.V07; } @Test public void testPing() throws Exception { final AtomicBoolean connected = new AtomicBoolean(false); DefaultServer.setRootHandler(new WebSocketProtocolHandshakeHandler(new WebSocketConnectionCallback() { @Override public void onConnect(final WebSocketHttpExchange exchange, final WebSocketChannel channel) { connected.set(true); channel.getReceiveSetter().set(new AbstractReceiveListener() { @Override protected void onFullPingMessage(WebSocketChannel channel, BufferedBinaryMessage message) throws IOException { final Pooled data = message.getData(); WebSockets.sendPong(data.getResource(), channel, new WebSocketCallback() { @Override public void complete(WebSocketChannel channel, Void context) { data.close(); } @Override public void onError(WebSocketChannel channel, Void context, Throwable throwable) { data.close(); } }); } }); channel.resumeReceives(); } })); final FutureResult latch = new FutureResult(); final byte[] payload = "payload".getBytes(); WebSocketTestClient client = new WebSocketTestClient(getVersion(), new URI("ws://" + NetworkUtils.formatPossibleIpv6Address(DefaultServer.getHostAddress("default")) + ':' + DefaultServer.getHostPort("default") + '/')); client.connect(); client.send(new PingWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(PongWebSocketFrame.class, payload, latch)); latch.getIoFuture().get(); client.destroy(); } } WebSocket08ServerTest.java000066400000000000000000000020551420065311100342040ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/core/protocol/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol; import io.netty.handler.codec.http.websocketx.WebSocketVersion; /** * @author Norman Maurer */ public class WebSocket08ServerTest extends WebSocket07ServerTest { @Override protected WebSocketVersion getVersion() { return WebSocketVersion.V08; } } WebSocket13ServerTestCase.java000066400000000000000000000020621420065311100347720ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/core/protocol/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol; import io.netty.handler.codec.http.websocketx.WebSocketVersion; /** * @author Norman Maurer */ public class WebSocket13ServerTestCase extends WebSocket08ServerTest { @Override protected WebSocketVersion getVersion() { return WebSocketVersion.V13; } } undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/core/protocol/server/000077500000000000000000000000001420065311100306375ustar00rootroot00000000000000AutobahnWebSocketServer.java000066400000000000000000000171621420065311100361710ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/core/protocol/server/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.core.protocol.server; import io.undertow.server.DefaultByteBufferPool; import io.undertow.server.HttpHandler; import io.undertow.server.protocol.http.HttpOpenListener; import io.undertow.util.Transfer; import io.undertow.websockets.core.StreamSinkFrameChannel; import io.undertow.websockets.core.StreamSourceFrameChannel; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketFrameType; import io.undertow.websockets.WebSocketConnectionCallback; import io.undertow.websockets.WebSocketProtocolHandshakeHandler; import io.undertow.websockets.spi.WebSocketHttpExchange; import org.xnio.ChannelExceptionHandler; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.StreamConnection; import org.xnio.Xnio; import org.xnio.XnioWorker; import org.xnio.channels.AcceptingChannel; import java.io.IOException; import java.net.InetSocketAddress; /** * This class is intended for use with testing against the Python * AutoBahn test suite. *

    * Autobahn installation documentation can be found here. *

    *

    How to run the tests on Linux/OSX.

    *

    *

    01. Install AutoBahn: sudo easy_install autobahntestsuite. Test using wstest --help. *

    *

    02. Create a directory for test configuration and results: mkdir ~/autobahn cd ~/autobahn. *

    *

    03. Create fuzzing_client_spec.json in the above directory * {@code * { * "options": {"failByDrop": false}, * "outdir": "./reports/servers", *

    * "servers": [ * {"agent": "Netty4", * "url": "ws://localhost:9000", * "options": {"version": 18}} * ], *

    * "cases": ["*"], * "exclude-cases": [], * "exclude-agent-cases": {} * } * } *

    *

    04. Run the AutobahnServer located in this package. If you are in Eclipse IDE, right click on * AutobahnServer.java and select Run As > Java Application. *

    *

    05. Run the Autobahn test wstest -m fuzzingclient -s fuzzing_client_spec.json. *

    *

    06. See the results in ./reports/servers/index.html * * @author Norman Maurer */ public class AutobahnWebSocketServer { private HttpOpenListener openListener; private XnioWorker worker; private AcceptingChannel server; private Xnio xnio; private final int port; public static WebSocketChannel current; public AutobahnWebSocketServer(int port) { this.port = port; } private static final ChannelExceptionHandler W_H = new ChannelExceptionHandler() { @Override public void handleException(StreamSinkFrameChannel channel, IOException exception) { exception.printStackTrace(); } }; private static final ChannelExceptionHandler R_H = new ChannelExceptionHandler() { @Override public void handleException(StreamSourceFrameChannel channel, IOException exception) { exception.printStackTrace(); } }; public void run() { xnio = Xnio.getInstance(); try { worker = xnio.createWorker(OptionMap.builder() .set(Options.WORKER_WRITE_THREADS, 4) .set(Options.WORKER_READ_THREADS, 4) .set(Options.CONNECTION_HIGH_WATER, 1000000) .set(Options.CONNECTION_LOW_WATER, 1000000) .set(Options.WORKER_TASK_CORE_THREADS, 10) .set(Options.WORKER_TASK_MAX_THREADS, 12) .set(Options.TCP_NODELAY, true) .set(Options.CORK, true) .getMap()); OptionMap serverOptions = OptionMap.builder() .set(Options.WORKER_ACCEPT_THREADS, 4) .set(Options.TCP_NODELAY, true) .set(Options.REUSE_ADDRESSES, true) .getMap(); openListener = new HttpOpenListener(new DefaultByteBufferPool(false, 8192)); ChannelListener acceptListener = ChannelListeners.openListenerAdapter(openListener); server = worker.createStreamConnectionServer(new InetSocketAddress(port), acceptListener, serverOptions); setRootHandler(getRootHandler()); server.resumeAccepts(); } catch (IOException e) { throw new RuntimeException(e); } } public static WebSocketProtocolHandshakeHandler getRootHandler() { return new WebSocketProtocolHandshakeHandler(new WebSocketConnectionCallback() { @Override public void onConnect(final WebSocketHttpExchange exchange, final WebSocketChannel channel) { current = channel; channel.getReceiveSetter().set(new Receiver()); channel.resumeReceives(); } }); } private static final class Receiver implements ChannelListener { @Override public void handleEvent(final WebSocketChannel channel) { try { final StreamSourceFrameChannel ws = channel.receive(); if (ws != null) { StreamSinkFrameChannel target; if (ws.getType() == WebSocketFrameType.PING || ws.getType() == WebSocketFrameType.CLOSE) { target = channel.send(ws.getType() == WebSocketFrameType.PING ? WebSocketFrameType.PONG : WebSocketFrameType.CLOSE); } else if (ws.getType() == WebSocketFrameType.PONG) { ws.getReadSetter().set(ChannelListeners.drainListener(Long.MAX_VALUE, null, null)); ws.wakeupReads(); return; } else { target = channel.send(ws.getType()); } Transfer.initiateTransfer(ws, target, null, ChannelListeners.writeShutdownChannelListener(new ChannelListener() { @Override public void handleEvent(StreamSinkFrameChannel c) { channel.resumeReceives(); } }, W_H), R_H, W_H, channel.getBufferPool()); } } catch (IOException e) { e.printStackTrace(); //IoUtils.safeClose(channel); } } } /** * Sets the root handler for the default web server * * @param rootHandler The handler to use */ private void setRootHandler(HttpHandler rootHandler) { openListener.setRootHandler(rootHandler); } public static void main(String[] args) { new AutobahnWebSocketServer(7777).run(); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/extensions/000077500000000000000000000000001420065311100267375ustar00rootroot00000000000000AutobahnExtensionCustomReceiverServer.java000066400000000000000000000162521420065311100372360ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/extensions/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.extensions; import java.io.IOException; import java.net.InetSocketAddress; import io.undertow.server.DefaultByteBufferPool; import io.undertow.server.HttpHandler; import io.undertow.server.protocol.http.HttpOpenListener; import io.undertow.util.Transfer; import io.undertow.websockets.WebSocketConnectionCallback; import io.undertow.websockets.WebSocketProtocolHandshakeHandler; import io.undertow.websockets.core.StreamSinkFrameChannel; import io.undertow.websockets.core.StreamSourceFrameChannel; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketFrameType; import io.undertow.websockets.spi.WebSocketHttpExchange; import org.apache.log4j.BasicConfigurator; import org.xnio.ChannelExceptionHandler; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.StreamConnection; import org.xnio.Xnio; import org.xnio.XnioWorker; import org.xnio.channels.AcceptingChannel; /** * A WebSocket Server implementation for use with AutoBahn test suite. *

    * A variant of {@link io.undertow.websockets.core.protocol.server.AutobahnWebSocketServer} but focus in extensions capabilities. *

    * It uses a custom {@link ChannelListener} as a receiver, instead to use a {@link io.undertow.websockets.core.AbstractReceiveListener} . * * @author Norman Maurer * @author Lucas Ponce */ public class AutobahnExtensionCustomReceiverServer { private HttpOpenListener openListener; private XnioWorker worker; private AcceptingChannel server; private Xnio xnio; private final int port; public static WebSocketChannel current; public AutobahnExtensionCustomReceiverServer(int port) { this.port = port; } private static final ChannelExceptionHandler W_H = new ChannelExceptionHandler() { @Override public void handleException(StreamSinkFrameChannel channel, IOException exception) { exception.printStackTrace(); } }; private static final ChannelExceptionHandler R_H = new ChannelExceptionHandler() { @Override public void handleException(StreamSourceFrameChannel channel, IOException exception) { exception.printStackTrace(); } }; public void run() { xnio = Xnio.getInstance(); try { worker = xnio.createWorker(OptionMap.builder() .set(Options.WORKER_WRITE_THREADS, 4) .set(Options.WORKER_READ_THREADS, 4) .set(Options.CONNECTION_HIGH_WATER, 1000000) .set(Options.CONNECTION_LOW_WATER, 1000000) .set(Options.WORKER_TASK_CORE_THREADS, 10) .set(Options.WORKER_TASK_MAX_THREADS, 12) .set(Options.TCP_NODELAY, true) .set(Options.CORK, true) .getMap()); OptionMap serverOptions = OptionMap.builder() .set(Options.WORKER_ACCEPT_THREADS, 4) .set(Options.TCP_NODELAY, true) .set(Options.REUSE_ADDRESSES, true) .getMap(); openListener = new HttpOpenListener(new DefaultByteBufferPool(false, 8192)); ChannelListener acceptListener = ChannelListeners.openListenerAdapter(openListener); server = worker.createStreamConnectionServer(new InetSocketAddress(port), acceptListener, serverOptions); setRootHandler(getRootHandler() .addExtension(new PerMessageDeflateHandshake()) ); server.resumeAccepts(); } catch (IOException e) { throw new RuntimeException(e); } } public static WebSocketProtocolHandshakeHandler getRootHandler() { return new WebSocketProtocolHandshakeHandler(new WebSocketConnectionCallback() { @Override public void onConnect(final WebSocketHttpExchange exchange, final WebSocketChannel channel) { current = channel; channel.getReceiveSetter().set(new Receiver()); channel.resumeReceives(); } }); } private static final class Receiver implements ChannelListener { @Override public void handleEvent(final WebSocketChannel channel) { try { final StreamSourceFrameChannel ws = channel.receive(); if (ws != null) { StreamSinkFrameChannel target; if (ws.getType() == WebSocketFrameType.PING || ws.getType() == WebSocketFrameType.CLOSE) { target = channel.send(ws.getType() == WebSocketFrameType.PING ? WebSocketFrameType.PONG : WebSocketFrameType.CLOSE); } else if (ws.getType() == WebSocketFrameType.PONG) { ws.getReadSetter().set(ChannelListeners.drainListener(Long.MAX_VALUE, null, null)); ws.wakeupReads(); return; } else { target = channel.send(ws.getType()); } Transfer.initiateTransfer(ws, target, null, ChannelListeners.writeShutdownChannelListener(new ChannelListener() { @Override public void handleEvent(StreamSinkFrameChannel c) { channel.resumeReceives(); } }, W_H), R_H, W_H, channel.getBufferPool()); } } catch (IOException e) { e.printStackTrace(); //IoUtils.safeClose(channel); } } } /** * Sets the root handler for the default web server * * @param rootHandler The handler to use */ private void setRootHandler(HttpHandler rootHandler) { openListener.setRootHandler(rootHandler); } public static void main(String[] args) { /* Use BasicConfigurator.configure() for fully console debug */ if (args.length == 1) { if (args[0].equals("--debug")) { BasicConfigurator.configure(); } } new AutobahnExtensionCustomReceiverServer(7777).run(); } } AutobahnExtensionsServer.java000066400000000000000000000111001420065311100345240ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/extensions/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.extensions; import java.io.IOException; import java.net.InetSocketAddress; import io.undertow.server.DefaultByteBufferPool; import io.undertow.server.HttpHandler; import io.undertow.server.protocol.http.HttpOpenListener; import io.undertow.websockets.WebSocketConnectionCallback; import io.undertow.websockets.WebSocketProtocolHandshakeHandler; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketLogger; import io.undertow.websockets.spi.WebSocketHttpExchange; import org.apache.log4j.BasicConfigurator; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.StreamConnection; import org.xnio.Xnio; import org.xnio.XnioWorker; import org.xnio.channels.AcceptingChannel; /** * A WebSocket Server implementation for use with AutoBahn test suite. *

    * A variant of {@link io.undertow.websockets.core.protocol.server.AutobahnWebSocketServer} but focus in extensions capabilities. *

    * It uses {@link DebugExtensionsHeaderHandler} and {@link DebugExtensionsListener} for WebSocket handler and listener. * * @author Lucas Ponce */ public class AutobahnExtensionsServer { private HttpOpenListener openListener; private XnioWorker worker; private AcceptingChannel server; private Xnio xnio; private final int port; public static WebSocketProtocolHandshakeHandler webSocketDebugHandler() { return new WebSocketProtocolHandshakeHandler(new WebSocketConnectionCallback() { @Override public void onConnect(final WebSocketHttpExchange exchange, final WebSocketChannel channel) { WebSocketLogger.EXTENSION_LOGGER.info("onConnect() "); channel.getReceiveSetter().set(new DebugExtensionsListener()); channel.resumeReceives(); } }); } public AutobahnExtensionsServer(int port) { this.port = port; } public void run() { xnio = Xnio.getInstance(); try { worker = xnio.createWorker(OptionMap.builder() .set(Options.CONNECTION_HIGH_WATER, 1000000) .set(Options.CONNECTION_LOW_WATER, 1000000) .set(Options.WORKER_TASK_CORE_THREADS, 10) .set(Options.WORKER_TASK_MAX_THREADS, 12) .set(Options.TCP_NODELAY, true) .set(Options.CORK, true) .getMap()); OptionMap serverOptions = OptionMap.builder() .set(Options.TCP_NODELAY, true) .set(Options.REUSE_ADDRESSES, true) .getMap(); openListener = new HttpOpenListener(new DefaultByteBufferPool(false, 8192)); ChannelListener acceptListener = ChannelListeners.openListenerAdapter(openListener); server = worker.createStreamConnectionServer(new InetSocketAddress(port), acceptListener, serverOptions); WebSocketProtocolHandshakeHandler handler = webSocketDebugHandler() .addExtension(new PerMessageDeflateHandshake()); DebugExtensionsHeaderHandler debug = new DebugExtensionsHeaderHandler(handler); setRootHandler(debug); server.resumeAccepts(); } catch (IOException e) { throw new RuntimeException(e); } } private void setRootHandler(HttpHandler rootHandler) { openListener.setRootHandler(rootHandler); } public static void main(String[] args) { /* Use BasicConfigurator.configure() for fully debug */ if (args.length == 1) { if (args[0].equals("--debug")) { BasicConfigurator.configure(); } } new AutobahnExtensionsServer(7777).run(); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/extensions/CompressionUtilsTest.java000066400000000000000000000236041420065311100337710ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.extensions; import io.undertow.testutils.category.UnitTest; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; import java.util.zip.Deflater; import java.util.zip.Inflater; /** * An auxiliar test class for compression/decompression operations implemented on extensions context. * * @author Lucas Ponce */ @Category(UnitTest.class) public class CompressionUtilsTest { private static Inflater decompress; private static Deflater compress; private static byte[] buf = new byte[1024]; @Before public void setup() throws Exception { compress = new Deflater(Deflater.BEST_SPEED, true); decompress = new Inflater(true); } @After public void finish() throws Exception { compress.end(); decompress.end(); } @Test public void testBasicCompressDecompress() throws Exception { String raw = "Hello"; compress.setInput(raw.getBytes()); compress.finish(); int read = compress.deflate(buf, 0, buf.length, Deflater.SYNC_FLUSH); decompress.setInput(buf, 0, read); read = decompress.inflate(buf); Assert.assertEquals("Hello", new String(buf, 0, read, "UTF-8")); compress.reset(); decompress.reset(); raw = "Hello, World!"; compress.setInput(raw.getBytes()); compress.finish(); read = compress.deflate(buf, 0, buf.length, Deflater.SYNC_FLUSH); decompress.setInput(buf, 0, read); read = decompress.inflate(buf); Assert.assertEquals("Hello, World!", new String(buf, 0, read, "UTF-8")); } @Test public void testCompressDecompressByFrames() throws Exception { String raw = "Hello, World! This is a long input example data with a lot of content for testing"; /* This test shares same buffer, 0-511 for compress, 512-1023 for decompress */ int position1 = 0; int position2 = 512; int chunkLength = 10; // Frame 1 compress.setInput(raw.getBytes(), position1, chunkLength); int compressed = compress.deflate(buf, 0, 512, Deflater.SYNC_FLUSH); decompress.setInput(buf, 0, compressed); int decompressed = decompress.inflate(buf, position2, buf.length - position2); // Frame 2 position1 += chunkLength; position2 += decompressed; compress.setInput(raw.getBytes(), position1, chunkLength); compressed = compress.deflate(buf, 0, 512, Deflater.NO_FLUSH); decompress.setInput(buf, 0, compressed); decompress.finished(); decompressed = decompress.inflate(buf, position2, buf.length - position2); // Frame 3 position1 += chunkLength; position2 += decompressed; compress.setInput(raw.getBytes(), position1, raw.getBytes().length - position1); compress.finish(); compressed = compress.deflate(buf, 0, 512, Deflater.NO_FLUSH); decompress.setInput(buf, 0, compressed); decompressed = decompress.inflate(buf, position2, buf.length - position2); Assert.assertEquals(raw, new String(buf, 512, position2 + decompressed - 512, "UTF-8")); } @Test public void testCompressByFramesDecompressWhole() throws Exception { String raw = "Hello, World! This is a long input example data with a lot of content for testing"; byte[] compressed = new byte[raw.length() + 64]; byte[] decompressed = new byte[raw.length()]; int n = 0, total = 0; // Compress Frame1 compress.setInput(raw.getBytes(), 0, 10); n = compress.deflate(compressed, 0, compressed.length, Deflater.SYNC_FLUSH); total += n; // Compress Frame2 compress.setInput(raw.getBytes(), 10, 10); n = compress.deflate(compressed, total, compressed.length - total, Deflater.SYNC_FLUSH); total += n; // Compress Frame3 compress.setInput(raw.getBytes(), 20, raw.getBytes().length - 20); n = compress.deflate(compressed, total, compressed.length - total, Deflater.SYNC_FLUSH); total += n; // Uncompress decompress.setInput(compressed, 0, total); n = decompress.inflate(decompressed, 0, decompressed.length); Assert.assertEquals(raw, new String(decompressed, 0, n, "UTF-8")); } @Test public void testLongMessage() throws Exception { int LONG_MSG = 16384; StringBuilder longMsg = new StringBuilder(LONG_MSG); byte[] longbuf = new byte[LONG_MSG + 64]; byte[] output = new byte[LONG_MSG]; for (int i = 0; i < LONG_MSG; i++) { longMsg.append(new Integer(i).toString().charAt(0)); } String msg = longMsg.toString(); byte[] input = msg.getBytes(); byte[] compressBuf = new byte[LONG_MSG + 64]; byte[] decompressBuf = new byte[LONG_MSG]; compress.setInput(input); compress.finish(); int read = compress.deflate(compressBuf, 0, compressBuf.length, Deflater.SYNC_FLUSH); decompress.setInput(compressBuf, 0, read); read = decompress.inflate(decompressBuf); Assert.assertEquals(msg, new String(decompressBuf, 0, read, "UTF-8")); } @Test public void testCompressByFramesDecompressWholeLongMessage() throws Exception { int LONG_MSG = 75 * 1024; StringBuilder longMsg = new StringBuilder(LONG_MSG); byte[] longbuf = new byte[LONG_MSG + 64]; byte[] output = new byte[LONG_MSG]; for (int i = 0; i < LONG_MSG; i++) { longMsg.append(new Integer(i).toString().charAt(0)); } String msg = longMsg.toString(); byte[] input = msg.getBytes(); /* Compress in chunks of 1024 bytes */ boolean finished = false; int start = 0; int end; int compressed; int total = 0; while (!finished) { end = (start + 1024) < input.length ? 1024 : input.length - start; compress.setInput(input, start, end); start += 1024; finished = start >= input.length; if (finished) { compress.finish(); } compressed = compress.deflate(longbuf, total, longbuf.length - total, Deflater.SYNC_FLUSH); total += compressed; } /* Decompress whole message */ int decompressed = 0; decompress.setInput(longbuf, 0, total); decompress.finished(); decompressed = decompress.inflate(output, 0, output.length); Assert.assertEquals(longMsg.toString(), new String(output, 0, decompressed, "UTF-8")); } @Test public void testEmptyFrames() throws Exception { decompress.reset(); byte[] compressedFrame1 = { (byte)0xf2, (byte)0x48, (byte)0xcd }; byte[] compressedFrame2 = { (byte)0xc9, (byte)0xc9, (byte)0x07, (byte)0x00 }; byte[] compressedFrame3 = { (byte)0x00, (byte)0x00, (byte)0xff, (byte)0xff }; byte[] output = new byte[1024]; int decompressed = 0; decompress.setInput(compressedFrame1); decompressed = decompress.inflate(output, 0, output.length); Assert.assertEquals(2, decompressed); Assert.assertEquals("He", new String(output, 0, decompressed, "UTF-8")); decompress.setInput(compressedFrame2); decompressed = decompress.inflate(output, 0, output.length); Assert.assertEquals(3, decompressed); Assert.assertEquals("llo", new String(output, 0, decompressed, "UTF-8")); decompress.setInput(compressedFrame3); decompressed = decompress.inflate(output, 0, output.length); Assert.assertEquals(0, decompressed); decompress.setInput(compressedFrame1); decompressed = decompress.inflate(output, 0, output.length); Assert.assertEquals(2, decompressed); Assert.assertEquals("He", new String(output, 0, decompressed, "UTF-8")); decompress.setInput(compressedFrame2); decompressed = decompress.inflate(output, 0, output.length); Assert.assertEquals(3, decompressed); Assert.assertEquals("llo", new String(output, 0, decompressed, "UTF-8")); } @Test public void testPadding() throws Exception { String original = "This is a long message - This is a long message - This is a long message"; byte[] compressed = new byte[1024]; int nCompressed; compress.setInput(original.getBytes()); nCompressed = compress.deflate(compressed, 0, compressed.length, Deflater.SYNC_FLUSH); /* Padding */ byte[] padding = {0, 0, 0, (byte)0xff, (byte)0xff, 0, 0, 0, (byte)0xff, (byte)0xff, 0, 0, 0, (byte)0xff, (byte)0xff}; int nPadding = padding.length; for (int i = 0; i < padding.length; i++) { compressed[nCompressed + i] = padding[i]; } byte[] uncompressed = new byte[1024]; int nUncompressed; decompress.setInput(compressed, 0, nCompressed + nPadding); nUncompressed = decompress.inflate(uncompressed); Assert.assertEquals(original, new String(uncompressed, 0, nUncompressed, "UTF-8")); } } DebugExtensionsHeaderHandler.java000066400000000000000000000064661420065311100352540ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/extensions/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.extensions; import io.undertow.UndertowLogger; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.HeaderValues; import io.undertow.util.Headers; /** * A {@link HttpHandler} implementation used for debugging WebSocket headers parameters. * * @author Lucas Ponce */ public class DebugExtensionsHeaderHandler implements HttpHandler { private final HttpHandler next; private HeaderValues requestExtensions; private HeaderValues responseExtensions; public DebugExtensionsHeaderHandler(final HttpHandler next) { this.next = next; } public HeaderValues getRequestExtensions() { return requestExtensions; } public HeaderValues getResponseExtensions() { return responseExtensions; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { final StringBuilder sb = new StringBuilder(); requestExtensions = exchange.getRequestHeaders().get(Headers.SEC_WEB_SOCKET_EXTENSIONS_STRING); if (requestExtensions != null) { for (String value : requestExtensions) { sb.append("\n") .append("--- REQUEST ---") .append("\n") .append(Headers.SEC_WEB_SOCKET_EXTENSIONS_STRING) .append(": ") .append(value) .append("\n"); } exchange.addExchangeCompleteListener(new ExchangeCompletionListener() { @Override public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { responseExtensions = exchange.getResponseHeaders().get(Headers.SEC_WEB_SOCKET_EXTENSIONS_STRING); if (responseExtensions != null) { for (String value : responseExtensions) { sb.append("\n") .append("--- RESPONSE ---") .append("\n") .append(Headers.SEC_WEB_SOCKET_EXTENSIONS_STRING) .append(": ") .append(value) .append("\n"); } } nextListener.proceed(); UndertowLogger.REQUEST_DUMPER_LOGGER.info(sb.toString()); } }); } next.handleRequest(exchange); } } DebugExtensionsListener.java000066400000000000000000000105031420065311100343360ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/extensions/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.extensions; import java.io.IOException; import java.nio.ByteBuffer; import io.undertow.websockets.core.AbstractReceiveListener; import io.undertow.websockets.core.BufferedBinaryMessage; import io.undertow.websockets.core.BufferedTextMessage; import io.undertow.websockets.core.CloseMessage; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketLogger; import io.undertow.websockets.core.WebSockets; import org.xnio.Pooled; /** * A {@link AbstractReceiveListener} implementation used as echo server in Autobahn tests. * * @author Lucas Ponce */ public class DebugExtensionsListener extends AbstractReceiveListener { private int binMsgs = 0; private int txtMsgs = 0; @Override protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage message) throws IOException { txtMsgs++; String data = message.getData(); WebSocketLogger.EXTENSION_LOGGER.info("#" + txtMsgs + " onFullTextMessage() - Received: " + data.getBytes().length + " bytes. "); for (WebSocketChannel peerChannel : channel.getPeerConnections()) { WebSockets.sendText(data, peerChannel, null); } } @Override protected void onFullBinaryMessage(WebSocketChannel channel, BufferedBinaryMessage message) throws IOException { binMsgs++; ByteBuffer[] data = message.getData().getResource(); int total = 0; for (int i =0; i < data.length; i++) { total += data[i].remaining(); } StringBuilder received = new StringBuilder(); received.append("# " + binMsgs + " onFullBinaryMessage() - Received: ").append(total).append(" length.").append("\n"); WebSocketLogger.EXTENSION_LOGGER.info(received.toString()); for (WebSocketChannel peerChannel : channel.getPeerConnections()) { WebSockets.sendBinary(data, peerChannel, null); } } @Override protected void onFullPingMessage(WebSocketChannel channel, BufferedBinaryMessage message) throws IOException { WebSocketLogger.EXTENSION_LOGGER.info("onFullPingMessage() "); ByteBuffer[] data = message.getData().getResource(); for (WebSocketChannel peerChannel : channel.getPeerConnections()) { WebSockets.sendPong(data, peerChannel, null); } } @Override protected void onFullPongMessage(WebSocketChannel channel, BufferedBinaryMessage message) throws IOException { WebSocketLogger.EXTENSION_LOGGER.info("onFullPongMessage() "); } @Override protected void onFullCloseMessage(WebSocketChannel channel, BufferedBinaryMessage message) throws IOException { WebSocketLogger.EXTENSION_LOGGER.info("onFullCloseMessage() "); Pooled pooled = message.getData(); try { ByteBuffer[] data = pooled.getResource(); /* Empty messages should be closed as NORMAL_CLOSURE. */ if (data.length == 1 || !data[0].hasRemaining()) { for (WebSocketChannel peerChannel : channel.getPeerConnections()) { WebSockets.sendClose(CloseMessage.NORMAL_CLOSURE, "", peerChannel, null); } } else { for (WebSocketChannel peerChannel : channel.getPeerConnections()) { WebSockets.sendClose(data, peerChannel, null); } } } finally { pooled.close(); } } @Override protected void onError(WebSocketChannel channel, Throwable error) { WebSocketLogger.EXTENSION_LOGGER.info("onError(): " + error.getMessage()); } } WebSocketExtensionBasicTestCase.java000066400000000000000000000322621420065311100357110ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/extensions/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.extensions; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.util.StringWriteChannelListener; import io.undertow.websockets.WebSocketConnectionCallback; import io.undertow.websockets.WebSocketExtension; import io.undertow.websockets.WebSocketProtocolHandshakeHandler; import io.undertow.websockets.client.WebSocketClient; import io.undertow.websockets.client.WebSocketClientNegotiation; import io.undertow.websockets.core.AbstractReceiveListener; import io.undertow.websockets.core.BufferedBinaryMessage; import io.undertow.websockets.core.BufferedTextMessage; import io.undertow.websockets.core.StreamSinkFrameChannel; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketFrameType; import io.undertow.websockets.core.WebSocketLogger; import io.undertow.websockets.core.WebSocketVersion; import io.undertow.websockets.core.WebSockets; import io.undertow.websockets.spi.WebSocketHttpExchange; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.Xnio; import org.xnio.XnioWorker; import java.io.IOException; import java.net.URI; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import static io.undertow.Handlers.path; import static io.undertow.testutils.StopServerWithExternalWorkerUtils.stopWorker; /** * * A test class for WebSocket client scenarios with extensions. * * @author Lucas Ponce */ @HttpOneOnly @RunWith(DefaultServer.class) public class WebSocketExtensionBasicTestCase { public static WebSocketProtocolHandshakeHandler webSocketDebugHandler() { return new WebSocketProtocolHandshakeHandler(new WebSocketConnectionCallback() { @Override public void onConnect(final WebSocketHttpExchange exchange, final WebSocketChannel channel) { WebSocketLogger.EXTENSION_LOGGER.info("onConnect() "); channel.getReceiveSetter().set(new DebugExtensionsListener()); channel.resumeReceives(); } }); } @Test public void testLongTextMessage() throws Exception { XnioWorker client; Xnio xnio = Xnio.getInstance(WebSocketExtensionBasicTestCase.class.getClassLoader()); client = xnio.createWorker(OptionMap.builder() .set(Options.WORKER_IO_THREADS, 2) .set(Options.CONNECTION_HIGH_WATER, 1000000) .set(Options.CONNECTION_LOW_WATER, 1000000) .set(Options.WORKER_TASK_CORE_THREADS, 30) .set(Options.WORKER_TASK_MAX_THREADS, 30) .set(Options.TCP_NODELAY, true) .set(Options.CORK, true) .getMap()); WebSocketProtocolHandshakeHandler handler = webSocketDebugHandler() .addExtension(new PerMessageDeflateHandshake()); DebugExtensionsHeaderHandler debug = new DebugExtensionsHeaderHandler(handler); DefaultServer.setRootHandler(path().addPrefixPath("/", debug)); final String SEC_WEBSOCKET_EXTENSIONS = "permessage-deflate; client_no_context_takeover; client_max_window_bits"; List extensionsList = WebSocketExtension.parse(SEC_WEBSOCKET_EXTENSIONS); final WebSocketClientNegotiation negotiation = new WebSocketClientNegotiation(null, extensionsList); Set extensionHandshakes = new HashSet<>(); extensionHandshakes.add(new PerMessageDeflateHandshake(true)); final WebSocketChannel clientChannel = WebSocketClient.connect(client, null, DefaultServer.getBufferPool(), OptionMap.EMPTY, new URI(DefaultServer.getDefaultServerURL()), WebSocketVersion.V13, negotiation, extensionHandshakes).get(); final CountDownLatch latch = new CountDownLatch(1); final AtomicReference result = new AtomicReference<>(); clientChannel.getReceiveSetter().set(new AbstractReceiveListener() { @Override protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage message) throws IOException { String data = message.getData(); //WebSocketLogger.ROOT_LOGGER.info("onFullTextMessage() - Client - Received: " + data.getBytes().length + " bytes."); result.set(data); latch.countDown(); } @Override protected void onFullCloseMessage(WebSocketChannel channel, BufferedBinaryMessage message) throws IOException { message.getData().close(); WebSocketLogger.ROOT_LOGGER.info("onFullCloseMessage"); } @Override protected void onError(WebSocketChannel channel, Throwable error) { WebSocketLogger.ROOT_LOGGER.info("onError"); super.onError(channel, error); error.printStackTrace(); latch.countDown(); } }); clientChannel.resumeReceives(); int LONG_MSG = 125 * 1024; StringBuilder longMsg = new StringBuilder(LONG_MSG); for (int i = 0; i < LONG_MSG; i++) { longMsg.append(Integer.toString(i).charAt(0)); } WebSockets.sendTextBlocking(longMsg.toString(), clientChannel); latch.await(300, TimeUnit.SECONDS); Assert.assertEquals(longMsg.toString(), result.get()); clientChannel.sendClose(); stopWorker(client); } @Test @Ignore public void testLongMessageWithoutExtensions() throws Exception { XnioWorker client; Xnio xnio = Xnio.getInstance(WebSocketExtensionBasicTestCase.class.getClassLoader()); client = xnio.createWorker(OptionMap.builder() .set(Options.WORKER_IO_THREADS, 2) .set(Options.CONNECTION_HIGH_WATER, 1000000) .set(Options.CONNECTION_LOW_WATER, 1000000) .set(Options.WORKER_TASK_CORE_THREADS, 30) .set(Options.WORKER_TASK_MAX_THREADS, 30) .set(Options.TCP_NODELAY, true) .set(Options.CORK, true) .getMap()); WebSocketProtocolHandshakeHandler handler = webSocketDebugHandler() .addExtension(new PerMessageDeflateHandshake()); DebugExtensionsHeaderHandler debug = new DebugExtensionsHeaderHandler(handler); DefaultServer.setRootHandler(path().addPrefixPath("/", debug)); final WebSocketClientNegotiation negotiation = null; final WebSocketChannel clientChannel = WebSocketClient.connect(client, DefaultServer.getBufferPool(), OptionMap.EMPTY, new URI("http://localhost:8080"), WebSocketVersion.V13, negotiation).get(); final CountDownLatch latch = new CountDownLatch(1); final AtomicReference result = new AtomicReference<>(); clientChannel.getReceiveSetter().set(new AbstractReceiveListener() { @Override protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage message) throws IOException { String data = message.getData(); WebSocketLogger.ROOT_LOGGER.info("onFullTextMessage() - Client - Received: " + data.getBytes().length + " bytes"); result.set(data); latch.countDown(); } @Override protected void onFullCloseMessage(WebSocketChannel channel, BufferedBinaryMessage message) throws IOException { message.getData().close(); WebSocketLogger.ROOT_LOGGER.info("onFullCloseMessage"); } @Override protected void onError(WebSocketChannel channel, Throwable error) { WebSocketLogger.ROOT_LOGGER.info("onError"); super.onError(channel, error); error.printStackTrace(); latch.countDown(); } }); clientChannel.resumeReceives(); int LONG_MSG = 75 * 1024; StringBuilder longMsg = new StringBuilder(LONG_MSG); for (int i = 0; i < LONG_MSG; i++) { longMsg.append(new Integer(i).toString().charAt(0)); } StreamSinkFrameChannel sendChannel = clientChannel.send(WebSocketFrameType.TEXT); new StringWriteChannelListener(longMsg.toString()).setup(sendChannel); latch.await(10, TimeUnit.SECONDS); Assert.assertEquals(longMsg.toString(), result.get()); clientChannel.sendClose(); stopWorker(client); } /** * Simulate an extensions request. * *

    {@code
    
        GET / HTTP/1.1
        User-Agent: AutobahnTestSuite/0.7.0-0.9.0
        Host: localhost:7777
        Upgrade: WebSocket
        Connection: Upgrade
        Pragma: no-cache
        Cache-Control: no-cache
        Sec-WebSocket-Key: pRAuwtkO0SUKzufqA2g+ig==
        Sec-WebSocket-Extensions: permessage-deflate; client_no_context_takeover; client_max_window_bits
        Sec-WebSocket-Version: 13
    
         * }
         * 
    */ @Test public void testExtensionsHeaders() throws Exception { XnioWorker client; Xnio xnio = Xnio.getInstance(WebSocketExtensionBasicTestCase.class.getClassLoader()); client = xnio.createWorker(OptionMap.builder() .set(Options.WORKER_IO_THREADS, 2) .set(Options.CONNECTION_HIGH_WATER, 1000000) .set(Options.CONNECTION_LOW_WATER, 1000000) .set(Options.WORKER_TASK_CORE_THREADS, 30) .set(Options.WORKER_TASK_MAX_THREADS, 30) .set(Options.TCP_NODELAY, true) .set(Options.CORK, true) .getMap()); WebSocketProtocolHandshakeHandler handler = webSocketDebugHandler() .addExtension(new PerMessageDeflateHandshake()); DebugExtensionsHeaderHandler debug = new DebugExtensionsHeaderHandler(handler); DefaultServer.setRootHandler(path().addPrefixPath("/", debug)); final String SEC_WEBSOCKET_EXTENSIONS = "permessage-deflate; client_no_context_takeover; client_max_window_bits"; final String SEC_WEBSOCKET_EXTENSIONS_EXPECTED = "[permessage-deflate; client_no_context_takeover]"; // List format List extensions = WebSocketExtension.parse(SEC_WEBSOCKET_EXTENSIONS); final WebSocketClientNegotiation negotiation = new WebSocketClientNegotiation(null, extensions); Set extensionHandshakes = new HashSet<>(); extensionHandshakes.add(new PerMessageDeflateHandshake(true)); final WebSocketChannel clientChannel = WebSocketClient.connect(client, null, DefaultServer.getBufferPool(), OptionMap.EMPTY, new URI(DefaultServer.getDefaultServerURL()), WebSocketVersion.V13, negotiation, extensionHandshakes).get(); final CountDownLatch latch = new CountDownLatch(1); final AtomicReference result = new AtomicReference<>(); clientChannel.getReceiveSetter().set(new AbstractReceiveListener() { @Override protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage message) throws IOException { String data = message.getData(); WebSocketLogger.ROOT_LOGGER.info("onFullTextMessage - Client - Received: " + data.getBytes().length + " bytes . Data: " + data); result.set(data); latch.countDown(); } @Override protected void onFullCloseMessage(WebSocketChannel channel, BufferedBinaryMessage message) throws IOException { message.getData().close(); WebSocketLogger.ROOT_LOGGER.info("onFullCloseMessage"); } @Override protected void onError(WebSocketChannel channel, Throwable error) { WebSocketLogger.ROOT_LOGGER.info("onError"); super.onError(channel, error); error.printStackTrace(); latch.countDown(); } }); clientChannel.resumeReceives(); StreamSinkFrameChannel sendChannel = clientChannel.send(WebSocketFrameType.TEXT); new StringWriteChannelListener("Hello, World!").setup(sendChannel); latch.await(10, TimeUnit.SECONDS); Assert.assertEquals("Hello, World!", result.get()); clientChannel.sendClose(); client.shutdown(); stopWorker(client); Assert.assertEquals(SEC_WEBSOCKET_EXTENSIONS_EXPECTED, debug.getResponseExtensions().toString()); } } WebSocketExtensionParserTest.java000066400000000000000000000147641420065311100353370ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/extensions/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.extensions; import io.undertow.testutils.category.UnitTest; import io.undertow.websockets.WebSocketExtension; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import java.util.List; /** * A test class for WebSocket Extensions parsing operations. * * @author Lucas Ponce */ @Category(UnitTest.class) public class WebSocketExtensionParserTest { @Test public void testParseExtension() { /* Original header: Sec-WebSocket-Extensions: x-webkit-deflate-message, x-custom-extension */ final String EXTENSION_HEADER1 = " x-webkit-deflate-message , x-custom-extension "; final List extensions1 = WebSocketExtension.parse(EXTENSION_HEADER1); Assert.assertEquals(2, extensions1.size()); Assert.assertEquals("x-webkit-deflate-message", extensions1.get(0).getName()); Assert.assertEquals("x-custom-extension", extensions1.get(1).getName()); /* Original header: Sec-WebSocket-Extensions: foo, bar; baz=2 */ final String EXTENSION_HEADER2 = " foo, bar; baz=2"; final List extensions2 = WebSocketExtension.parse(EXTENSION_HEADER2); Assert.assertEquals(2, extensions2.size()); Assert.assertEquals("foo", extensions2.get(0).getName()); Assert.assertEquals(0, extensions2.get(0).getParameters().size()); Assert.assertEquals("bar", extensions2.get(1).getName()); Assert.assertEquals(1, extensions2.get(1).getParameters().size()); Assert.assertEquals("baz", extensions2.get(1).getParameters().get(0).getName()); Assert.assertEquals("2", extensions2.get(1).getParameters().get(0).getValue()); } @Test public void testToExtensionHeader() { /* Original header: Sec-WebSocket-Extensions: x-webkit-deflate-message, x-custom-extension */ final String EXTENSION_HEADER1 = " x-webkit-deflate-message , x-custom-extension "; final List extensions1 = WebSocketExtension.parse(EXTENSION_HEADER1); final String extensionHeader1 = WebSocketExtension.toExtensionHeader(extensions1); Assert.assertEquals("x-webkit-deflate-message, x-custom-extension", extensionHeader1); /* Original header: Sec-WebSocket-Extensions: foo, bar; baz=2 */ final String EXTENSION_HEADER2 = " foo, bar; baz=2"; final List extensions2 = WebSocketExtension.parse(EXTENSION_HEADER2); final String extensionHeader2 = WebSocketExtension.toExtensionHeader(extensions2); Assert.assertEquals("foo, bar; baz=2", extensionHeader2); } @Test public void testWriteRsvBits() { int rsv = 4; Assert.assertEquals(ExtensionFunction.RSV1, rsv & ExtensionFunction.RSV1); Assert.assertNotEquals(ExtensionFunction.RSV2, rsv & ExtensionFunction.RSV2); Assert.assertNotEquals(ExtensionFunction.RSV3, rsv & ExtensionFunction.RSV3); rsv = 2; Assert.assertEquals(ExtensionFunction.RSV2, rsv & ExtensionFunction.RSV2); Assert.assertNotEquals(ExtensionFunction.RSV1, rsv & ExtensionFunction.RSV1); Assert.assertNotEquals(ExtensionFunction.RSV3, rsv & ExtensionFunction.RSV3); rsv = 1; Assert.assertEquals(ExtensionFunction.RSV3, rsv & ExtensionFunction.RSV3); Assert.assertNotEquals(ExtensionFunction.RSV1, rsv & ExtensionFunction.RSV1); Assert.assertNotEquals(ExtensionFunction.RSV2, rsv & ExtensionFunction.RSV2); rsv = 6; Assert.assertEquals(ExtensionFunction.RSV1, rsv & ExtensionFunction.RSV1); Assert.assertEquals(ExtensionFunction.RSV2, rsv & ExtensionFunction.RSV2); Assert.assertNotEquals(ExtensionFunction.RSV3, rsv & ExtensionFunction.RSV3); rsv = 3; Assert.assertNotEquals(ExtensionFunction.RSV1, rsv & ExtensionFunction.RSV1); Assert.assertEquals(ExtensionFunction.RSV2, rsv & ExtensionFunction.RSV2); Assert.assertEquals(ExtensionFunction.RSV3, rsv & ExtensionFunction.RSV3); rsv = 5; Assert.assertEquals(ExtensionFunction.RSV1, rsv & ExtensionFunction.RSV1); Assert.assertNotEquals(ExtensionFunction.RSV2, rsv & ExtensionFunction.RSV2); Assert.assertEquals(ExtensionFunction.RSV3, rsv & ExtensionFunction.RSV3); rsv = 7; Assert.assertEquals(ExtensionFunction.RSV1, rsv & ExtensionFunction.RSV1); Assert.assertEquals(ExtensionFunction.RSV2, rsv & ExtensionFunction.RSV2); Assert.assertEquals(ExtensionFunction.RSV3, rsv & ExtensionFunction.RSV3); rsv = 8; Assert.assertNotEquals(ExtensionFunction.RSV1, rsv & ExtensionFunction.RSV1); Assert.assertNotEquals(ExtensionFunction.RSV2, rsv & ExtensionFunction.RSV2); Assert.assertNotEquals(ExtensionFunction.RSV3, rsv & ExtensionFunction.RSV3); rsv = 0 | ExtensionFunction.RSV1; Assert.assertEquals(ExtensionFunction.RSV1, rsv & ExtensionFunction.RSV1); Assert.assertNotEquals(ExtensionFunction.RSV2, rsv & ExtensionFunction.RSV2); Assert.assertNotEquals(ExtensionFunction.RSV3, rsv & ExtensionFunction.RSV3); rsv = 0 | ExtensionFunction.RSV2; Assert.assertNotEquals(ExtensionFunction.RSV1, rsv & ExtensionFunction.RSV1); Assert.assertEquals(ExtensionFunction.RSV2, rsv & ExtensionFunction.RSV2); Assert.assertNotEquals(ExtensionFunction.RSV3, rsv & ExtensionFunction.RSV3); rsv = 0 | ExtensionFunction.RSV3; Assert.assertNotEquals(ExtensionFunction.RSV1, rsv & ExtensionFunction.RSV1); Assert.assertNotEquals(ExtensionFunction.RSV2, rsv & ExtensionFunction.RSV2); Assert.assertEquals(ExtensionFunction.RSV3, rsv & ExtensionFunction.RSV3); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/utils/000077500000000000000000000000001420065311100257005ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/utils/FrameChecker.java000066400000000000000000000054161420065311100310700ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.utils; import io.netty.buffer.ByteBuf; import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketFrame; import org.junit.Assert; import org.xnio.FutureResult; import java.io.IOException; import java.nio.charset.StandardCharsets; /** * @author Norman Maurer */ public final class FrameChecker implements WebSocketTestClient.FrameListener { private final Class clazz; private final byte[] expectedPayload; private final FutureResult latch; private volatile boolean first = true; public FrameChecker(Class clazz, byte[] expectedPayload, FutureResult latch) { this.clazz = clazz; this.expectedPayload = expectedPayload; this.latch = latch; } @Override public void onFrame(WebSocketFrame frame) { try { if (first) { first = false; Assert.assertTrue(clazz.isInstance(frame)); if (frame instanceof TextWebSocketFrame) { String buf = ((TextWebSocketFrame) frame).text(); Assert.assertEquals(new String(expectedPayload, StandardCharsets.UTF_8), buf); } else { ByteBuf buf = frame.content(); byte[] data = new byte[buf.readableBytes()]; buf.readBytes(data); Assert.assertArrayEquals(expectedPayload, data); } latch.setResult(null); } else { Assert.assertTrue(CloseWebSocketFrame.class.isInstance(frame)); } } catch (Throwable e) { latch.setException(new IOException(e)); } } @Override public void onError(Throwable t) { try { t.printStackTrace(); Assert.fail(); } finally { latch.setException(new IOException(t)); } } } undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/utils/StreamSinkChannelAdapter.java000066400000000000000000000126761420065311100334310ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.utils; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.WritableByteChannel; import java.util.concurrent.TimeUnit; import org.xnio.ChannelListener.Setter; import org.xnio.ChannelListener; import org.xnio.Option; import org.xnio.XnioExecutor; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.Channels; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; /** * * * @author Norman Maurer * */ public class StreamSinkChannelAdapter implements StreamSinkChannel { private final ChannelListener.SimpleSetter writeSetter = new ChannelListener.SimpleSetter<>(); private final ChannelListener.SimpleSetter closeSetter = new ChannelListener.SimpleSetter<>(); private final WritableByteChannel channel; public StreamSinkChannelAdapter(WritableByteChannel channel) { this.channel = channel; } @Override public int write(ByteBuffer src) throws IOException { return channel.write(src); } @Override public void close() throws IOException { channel.close(); } @Override public boolean isOpen() { return channel.isOpen(); } @Override public long write(ByteBuffer[] srcs) throws IOException { return write(srcs, 0, srcs.length); } @Override public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { long written = 0; for (int i = offset; i < length; i++) { int w = write(srcs[i]); if (w < 0) { return w; } written += w; } return written; } @Override public void suspendWrites() { // Noop } @Override public void resumeWrites() { // Noop } @Override public boolean isWriteResumed() { return false; } @Override public void wakeupWrites() { throw new UnsupportedOperationException(); } @Override public void shutdownWrites() throws IOException { throw new UnsupportedOperationException(); } @Override public void awaitWritable() throws IOException { } @Override public void awaitWritable(long time, TimeUnit timeUnit) throws IOException { } @Override public XnioExecutor getWriteThread() { throw new UnsupportedOperationException(); } @Override public boolean flush() throws IOException { return true; } @Override public XnioWorker getWorker() { throw new UnsupportedOperationException(); } @Override public XnioIoThread getIoThread() { return null; } @Override public boolean supportsOption(Option option) { return false; } @Override public T getOption(Option option) throws IOException { return null; } @Override public T setOption(Option option, T value) throws IOException { return null; } @Override public long transferFrom(FileChannel src, long position, long count) throws IOException { return src.transferTo(position, count, channel); } @Override public long transferFrom(StreamSourceChannel source, long count, ByteBuffer throughBuffer) throws IOException { long transferred = 0; while (transferred < count) { int r = source.read(throughBuffer); if (r > 0) { throughBuffer.flip(); while(throughBuffer.hasRemaining()) { int w = write(throughBuffer); if (w < 1) { throughBuffer.flip(); return transferred; } else { transferred += w; } } throughBuffer.clear(); } return transferred; } return transferred; } @Override public Setter getWriteSetter() { return writeSetter; } @Override public Setter getCloseSetter() { return closeSetter; } @Override public int writeFinal(ByteBuffer src) throws IOException { return Channels.writeFinalBasic(this, src); } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { return Channels.writeFinalBasic(this, srcs, offset, length); } @Override public long writeFinal(ByteBuffer[] srcs) throws IOException { return Channels.writeFinalBasic(this, srcs, 0, srcs.length); } } StreamSourceChannelAdapter.java000066400000000000000000000115041420065311100336730ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/utils/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.utils; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import java.util.concurrent.TimeUnit; import org.xnio.ChannelListener; import org.xnio.Option; import org.xnio.XnioExecutor; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.ChannelListener.Setter; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; /** * * * @author Norman Maurer * */ public class StreamSourceChannelAdapter implements StreamSourceChannel { private final ReadableByteChannel channel; private final ChannelListener.SimpleSetter readSetter = new ChannelListener.SimpleSetter<>(); private final ChannelListener.SimpleSetter closeSetter = new ChannelListener.SimpleSetter<>(); public StreamSourceChannelAdapter(ReadableByteChannel channel) { this.channel = channel; } @Override public int read(ByteBuffer dst) throws IOException { return channel.read(dst); } @Override public void close() throws IOException { channel.close(); } @Override public boolean isOpen() { return channel.isOpen(); } @Override public long read(ByteBuffer[] dst) throws IOException { return read(dst, 0, dst.length); } @Override public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { return channel.read(dsts[0]); } @Override public void suspendReads() { throw new UnsupportedOperationException(); } @Override public void resumeReads() { throw new UnsupportedOperationException(); } @Override public boolean isReadResumed() { throw new UnsupportedOperationException(); } @Override public void wakeupReads() { throw new UnsupportedOperationException(); } @Override public void shutdownReads() throws IOException { throw new UnsupportedOperationException(); } @Override public void awaitReadable() throws IOException { throw new UnsupportedOperationException(); } @Override public void awaitReadable(long time, TimeUnit timeUnit) throws IOException { throw new UnsupportedOperationException(); } @Override public XnioExecutor getReadThread() { throw new UnsupportedOperationException(); } @Override public XnioWorker getWorker() { throw new UnsupportedOperationException(); } @Override public XnioIoThread getIoThread() { return null; } @Override public boolean supportsOption(Option option) { return false; } @Override public T getOption(Option option) throws IOException { return null; } @Override public T setOption(Option option, T value) throws IOException { return null; } @Override public long transferTo(long position, long count, FileChannel target) throws IOException { ByteBuffer buf = ByteBuffer.allocate((int) count); int r = channel.read(buf); buf.flip(); while(buf.hasRemaining()) { if ( target.write(buf) < 1) { throw new IOException("Unable to write out all bytes"); } } buf.clear(); return r; } @Override public long transferTo(long count, ByteBuffer buf, StreamSinkChannel target) throws IOException { buf.flip(); if (count < buf.remaining()) { buf.limit(buf.position() + (int) count); } int r = channel.read(buf); while(buf.hasRemaining()) { if ( target.write(buf) < 1) { throw new IOException("Unable to write out all bytes"); } } return r; } @Override public Setter getReadSetter() { return readSetter; } @Override public Setter getCloseSetter() { return closeSetter; } } undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/utils/TestUtils.java000066400000000000000000000032431420065311100305050ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.utils; import static org.easymock.EasyMock.reset; import static org.easymock.EasyMock.verify; import java.nio.ByteBuffer; /** * An utility class which is used for testing * * @author norman */ public final class TestUtils { private TestUtils() { // utility class } /** * Return a array of bytes that holds all the readable data of the {@link ByteBuffer}. It will not increase the position * of the given {@link ByteBuffer} */ public static byte[] readableBytes(ByteBuffer buffer) { byte[] readBytes = new byte[buffer.remaining()]; System.arraycopy(buffer.array(), buffer.arrayOffset() + buffer.position(), readBytes, 0, readBytes.length); return readBytes; } /** * Verify and reset the mocks which were created via {@link org.easymock.EasyMock}. */ public static void verifyAndReset(Object... objects) { verify(objects); reset(objects); } } undertow-2.2.16.Final/core/src/test/java/io/undertow/websockets/utils/WebSocketTestClient.java000066400000000000000000000207011420065311100324300ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.utils; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; import io.netty.channel.EventLoopGroup; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpClientCodec; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker; import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory; import io.netty.handler.codec.http.websocketx.WebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketVersion; import io.netty.util.CharsetUtil; import io.netty.util.ReferenceCountUtil; import java.net.InetSocketAddress; import java.net.URI; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * Client which can be used to Test a websocket server * * @author Norman Maurer */ public final class WebSocketTestClient { private final Bootstrap bootstrap = new Bootstrap(); private Channel ch; private final URI uri; private final WebSocketVersion version; private volatile boolean closed; private static final AtomicInteger count = new AtomicInteger(); public WebSocketTestClient(WebSocketVersion version, URI uri) { this.uri = uri; this.version = version; } /** * Connect the WebSocket client * * @throws Exception */ public WebSocketTestClient connect() throws Exception { String protocol = uri.getScheme(); if (!"ws".equals(protocol)) { throw new IllegalArgumentException("Unsupported protocol: " + protocol); } final WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker( uri, version, null, false, new DefaultHttpHeaders()); WSClientHandler handler = new WSClientHandler(handshaker); EventLoopGroup group = new NioEventLoopGroup(); bootstrap.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer() { @Override protected void initChannel(Channel channel) throws Exception { ChannelPipeline p = channel.pipeline(); p.addLast( new HttpClientCodec(), new HttpObjectAggregator(8192), handler); } }); // Connect ChannelFuture future = bootstrap.connect( new InetSocketAddress(uri.getHost(), uri.getPort())); future.syncUninterruptibly(); handler.handshakeFuture.syncUninterruptibly(); ch = future.channel(); return this; } /** * Send the WebSocketFrame and call the FrameListener once a frame was received as response or * when an Exception was caught. */ public WebSocketTestClient send(WebSocketFrame frame, final FrameListener listener) { ch.pipeline().addLast("responseHandler" + count.incrementAndGet(), new SimpleChannelInboundHandler() { @Override protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof CloseWebSocketFrame) { closed = true; } listener.onFrame((WebSocketFrame) msg); ctx.pipeline().remove(this); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); listener.onError(cause); ctx.pipeline().remove(this); } }); ChannelFuture cf = ch.writeAndFlush(frame).syncUninterruptibly(); if (!cf.isSuccess()) { listener.onError(cf.cause()); } return this; } /** * Destroy the client and also close open connections if any exist */ public void destroy() { if (!closed) { final CountDownLatch latch = new CountDownLatch(1); send(new CloseWebSocketFrame(), new FrameListener() { @Override public void onFrame(WebSocketFrame frame) { latch.countDown(); } @Override public void onError(Throwable t) { latch.countDown(); } }); try { latch.await(10, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); } } //bootstrap.releaseExternalResources(); if (ch != null) { ch.close().syncUninterruptibly(); } try { bootstrap.group().shutdownGracefully(0, 1, TimeUnit.SECONDS).get(); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { throw new RuntimeException(e); } } public interface FrameListener { /** * Is called if an WebSocketFrame was received */ void onFrame(WebSocketFrame frame); /** * Is called if an error occurred */ void onError(Throwable t); } private static final class WSClientHandler extends SimpleChannelInboundHandler { private final WebSocketClientHandshaker handshaker; private ChannelPromise handshakeFuture; WSClientHandler(WebSocketClientHandshaker handshaker) { super(false); this.handshaker = handshaker; } public ChannelFuture handshakeFuture() { return handshakeFuture; } @Override public void handlerAdded(ChannelHandlerContext ctx) { handshakeFuture = ctx.newPromise(); } @Override public void channelActive(ChannelHandlerContext ctx) { handshaker.handshake(ctx.channel()); } @Override protected void channelRead0(ChannelHandlerContext ctx, Object o) throws Exception { Channel ch = ctx.channel(); if (!handshaker.isHandshakeComplete()) { handshaker.finishHandshake(ch, (FullHttpResponse) o); // the handshake response was processed upgrade is complete handshakeFuture.setSuccess(); ReferenceCountUtil.release(o); return; } if (o instanceof FullHttpResponse) { FullHttpResponse response = (FullHttpResponse) o; ReferenceCountUtil.release(o); throw new Exception("Unexpected HttpResponse (status=" + response.getStatus() + ", content=" + response.content().toString(CharsetUtil.UTF_8) + ')'); } ctx.fireChannelRead(o); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); if (!handshakeFuture.isDone()) { handshakeFuture.setFailure(cause); } ctx.close(); } } } undertow-2.2.16.Final/core/src/test/resources/000077500000000000000000000000001420065311100212025ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/resources/ajp-apache-site000066400000000000000000000007741420065311100240700ustar00rootroot00000000000000Listen 9080 NameVirtualHost *:9080 ProxyPass / ajp://localhost:8888/ ProxyPassReverse / ajp://localhost:8888/ Listen 9443 NameVirtualHost *:9443 SSLEngine on SSLCertificateFile /etc/apache2/ssl/server.pem SSLCACertificateFile /etc/apache2/ssl/ca.crt SSLVerifyClient optional SSLVerifyDepth 2 SSLOptions +ExportCertData +StdEnvVars ProxyPass / ajp://localhost:8888/ ProxyPassReverse / ajp://localhost:8888/ undertow-2.2.16.Final/core/src/test/resources/byteman-netwok.btm000066400000000000000000000017211420065311100246530ustar00rootroot00000000000000 RULE handling NEED_WRAP CLASS org.xnio.ssl.JsseConnectedSslStreamChannel METHOD handleHandshake #AT INVOKE org.xnio.ssl.JsseConnectedSslStreamChannel.handleWrapResult AFTER INVOKE org.xnio.Pooled.getResource 1 IF TRUE DO debug("Read is trying to wrap as a result of NEED_WRAP... wait for the channel to be closed"), signalWake("handleHandshake at invoke handleWrapResult", true), waitFor("channel closed"), debug("Proceeding with handleWrapResult") ENDRULE RULE before close channel CLASS org.xnio.ssl.JsseConnectedSslStreamChannel METHOD closeAction AT ENTRY IF TRUE DO debug("Channel is closing... waiting for handleHandshake first"), waitFor("handleHandshake at invoke handleWrapResult"), debug("Proceeding with closeAction") ENDRULE RULE after close channel CLASS org.xnio.ssl.JsseConnectedSslStreamChannel METHOD closeAction AT EXIT IF TRUE DO debug("Channel is closed... waking read"), signalWake("channel closed", true) ENDRULE undertow-2.2.16.Final/core/src/test/resources/ca.crt000066400000000000000000000022241420065311100222770ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDNjCCAh6gAwIBAgIEUPqtwDANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJHQjEOMAwGA1UE CBMFU3RhdGUxDTALBgNVBAcTBENpdHkxDDAKBgNVBAoTA09yZzELMAkGA1UECxMCT1UxFDASBgNV BAMTC1Rlc3QgQ2xpZW50MB4XDTEzMDExOTE0MjkyMFoXDTIzMDExNzE0MjkyMFowXTELMAkGA1UE BhMCR0IxDjAMBgNVBAgTBVN0YXRlMQ0wCwYDVQQHEwRDaXR5MQwwCgYDVQQKEwNPcmcxCzAJBgNV BAsTAk9VMRQwEgYDVQQDEwtUZXN0IENsaWVudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBAIFcxcn1M4hmgoH33g3pSu0kHyD345Xs3Jk23xA8YvxJxUeYReZo94Pcfi6nky2mhR4+wGo8 +CZAMO3/kqxfBcBY/NIbLZtEKQ+sABZc/2Sqc1w1r2V4TsxibRLDpexbD9+S7aLAhTTpOwBFJIv3 eQU2jz+X4RVXM73zPRA1aofqxl5eU/P8Fj+p0JUzNBQvxd+FizrzPYUkRJSMIZPCBax1uRgJ8u0L 5DQ6AxXe+OgTCJ/ghDys9ZwLhBHZNeav8/ih2twwd45RokAw1h511+KKKcJyBpEfHyrFelYDecUk C0YphlPV7zTWrnBxJEtrLKyeKO+oH+rvUTCexO07DM0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA XJEQri5VU0Ly/fTwhOG4OvQYtihwCVAwgev+GK6/ob/DWR3s0xYVpmAs+nzaFjqiHL8YDM2u4Gns 5u7eY0+eyfq+jSwvEjhfmWG3cYDTNvbOOuG4TrXOb6AUrqSLxm6A7K6PhjBoipNUFLOyiWNCfrRi FMBgJHvLZcyv0IZQXLzimfgo6rZRpmXR85dq50UYaFOLxw2kqkncU8UCo04M4rL1f2T4XHaXdttm df3EZ5pobrANKUMXV79ihF9LYH+yrc602pVLsBsRLWvVRagymP8w6hGgMnyTAgVmBIUC7FNiEKyI w9/tpVofINLO+Khr0kVDtGZe9xHWSS4DdNcdUA== -----END CERTIFICATE-----undertow-2.2.16.Final/core/src/test/resources/client.keystore000066400000000000000000000042031420065311100242460ustar00rootroot00000000000000client+)W'l`bLr R߈nQ ٿuЮճ7T߂{n($Ez(f!LS@؃ 'WKVPO)ν#y* 2 &E*f +.Gjd "o'3_g\Uh:iޠߣruL uGII59zGV@3S%0 DZ(hފ3f?d  -'06m0x҃Fc^ğs!!`tܟ*^^ׄSn?*fg `t U,i;}_φڧC vocMN )eDG^ȼ|]K͖rwhtmc|ti*+*X.509:060P0  *H  0]1 0 UGB10 UState1 0 UCity1 0 U Org1 0 U OU10U Test Client0 130119142920Z 230117142920Z0]1 0 UGB10 UState1 0 UCity1 0 U Org1 0 U OU10U Test Client0"0  *H 0 \3f J$ ܙ6j<&@0_X-D)\ds\5exNbmå[ߒ4;E$y6?W3=5j^^S?Е34/߅:=$D!u 4:< 50wQ@0u)r*zVy$ F)S4֮pq$Kk,(Q0; 0  *H  \.USB:(p P0Y`,|: ͮicO,/8_aq6:Non쮏0hTcB~b`${e̯ІP\(QejEhS ISN d\vvfughn )CWb_K`δڕK-kE202|fSbZ kECf^I.tP$Ip}yJundertow-2.2.16.Final/core/src/test/resources/client.truststore000066400000000000000000000015651420065311100246470ustar00rootroot00000000000000serverW6i/$g)"~Rq>6"$~ \}kVJkƬT`)z拣4lMz50  *H  c;gS3;KmOn/TϜSJ;'Lm;hr=l,VꙜaqW?Mӧ!͓ 3$y\m9;kr&Iy )GS[}*'MH#oW΢ #j2(ɼAn lX-hǓ JmUWPtNSTY8os$С@"чo.`D,1hUDWA^@VG܅ q̗undertow-2.2.16.Final/core/src/test/resources/krb5.conf000066400000000000000000000006451420065311100227210ustar00rootroot00000000000000[libdefaults] default_realm = UNDERTOW.IO default_tgs_enctypes = aes128-cts-hmac-sha1-96,des-cbc-md5,des3-cbc-sha1-kd default_tkt_enctypes = aes128-cts-hmac-sha1-96,des-cbc-md5,des3-cbc-sha1-kd kdc_timeout = 5000 dns_lookup_realm = false dns_lookup_kdc = false allow_weak_crypto = yes forwardable = true [realms] UNDERTOW.IO = { kdc = localhost:6088 } [login] krb4_convert = true krb4_get_tickets = false undertow-2.2.16.Final/core/src/test/resources/ldif/000077500000000000000000000000001420065311100221205ustar00rootroot00000000000000undertow-2.2.16.Final/core/src/test/resources/ldif/krbtgt.ldif000066400000000000000000000004451420065311100242600ustar00rootroot00000000000000dn: uid=krbtgt,ou=users,dc=undertow,dc=io objectClass: top objectClass: person objectClass: inetOrgPerson objectClass: krb5principal objectClass: krb5kdcentry cn: KDC Service sn: Service uid: krbtgt userPassword: secret krb5PrincipalName: krbtgt/UNDERTOW.IO@UNDERTOW.IO krb5KeyVersionNumber: 0undertow-2.2.16.Final/core/src/test/resources/ldif/partition.ldif000066400000000000000000000001311420065311100247640ustar00rootroot00000000000000dn: ou=users,dc=undertow,dc=io objectClass: organizationalUnit objectClass: top ou: usersundertow-2.2.16.Final/core/src/test/resources/ldif/server.ldif000066400000000000000000000004421420065311100242660ustar00rootroot00000000000000dn: uid=Server,ou=users,dc=undertow,dc=io objectClass: top objectClass: person objectClass: inetOrgPerson objectClass: krb5principal objectClass: krb5kdcentry cn: Server sn: Service uid: Server userPassword: servicepwd krb5PrincipalName: HTTP/${hostname}@UNDERTOW.IO krb5KeyVersionNumber: 0undertow-2.2.16.Final/core/src/test/resources/ldif/user.ldif000066400000000000000000000004231420065311100237350ustar00rootroot00000000000000dn: uid=jduke,ou=users,dc=undertow,dc=io objectClass: top objectClass: person objectClass: inetOrgPerson objectClass: krb5principal objectClass: krb5kdcentry cn: Java Duke sn: duke uid: jduke userPassword: theduke krb5PrincipalName: jduke@UNDERTOW.IO krb5KeyVersionNumber: 0 undertow-2.2.16.Final/core/src/test/resources/logging.properties000066400000000000000000000036531420065311100247550ustar00rootroot00000000000000 # # JBoss, Home of Professional Open Source. # Copyright 2012 Red Hat, Inc., and individual contributors # as indicated by the @author tags. # # 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. # # Additional logger names to configure (root logger is always configured) loggers=org.xnio.listener,org.xnio.ssl,org.apache,io.undertow.util.TestHttpClient,io.undertow.server.handlers.proxy,io.undertow.request.io,io.undertow.client,io.undertow.request.error-response,io.undertow.request.security # Root logger configuration logger.level=${test.level:INFO} logger.handlers=CONSOLE # Console handler configuration handler.CONSOLE=org.jboss.logmanager.handlers.ConsoleHandler handler.CONSOLE.properties=autoFlush,target handler.CONSOLE.target=SYSTEM_ERR handler.CONSOLE.level=ALL handler.CONSOLE.autoFlush=true handler.CONSOLE.formatter=PATTERN # The log format pattern formatter.PATTERN=org.jboss.logmanager.formatters.PatternFormatter formatter.PATTERN.properties=pattern formatter.PATTERN.pattern=%d{HH:mm:ss,SSS} %-5p (%t) [%c] <%F:%L> %m%n logger.org.xnio.listener.level=DEBUG logger.org.xnio.ssl.level=DEBUG logger.io.undertow.request.io.level=DEBUG logger.org.apache.level=WARN logger.org.apache.useParentHandlers=false logger.io.undertow.util.TestHttpClient.level=WARN logger.io.undertow.server.handlers.proxy.level=DEBUG logger.io.undertow.client.level=DEBUG logger.io.undertow.request.error-response.level=DEBUG logger.io.undertow.request.security.level=DEBUG undertow-2.2.16.Final/core/src/test/resources/server.keystore000066400000000000000000000042001420065311100242730ustar00rootroot00000000000000server@&y,˪Ŕ ?^b+ɁZ1jGˠq'D{uѪ  ~,j6 9fjN% zjډ.7 bFg5 /dpB`hxLΤ<"[}NBq ײEՈӉjQW`Hr&hI[ȏ2'DSleBTGޙ$ԢX-A>kQ)|k,nK~6zsurǚG[3w%$A;3VN,sH1ͤbM !Ĵ@_Ao۫[0@QX "?gC]"Ϊ, ZFɰ| ?'0O\/څ?F)b6GN\9ld"8 l~Ŷ5bWquP[Ngnf1r'=EKOQnᲀX5½v\3=gUWѨaEWq^lf $7NG;:U}WHbpb|v K5/Fly(YADKO-]*s@ϕǗK¡7*1 b8'!]P'_")Ɲ*? QXsX%F&0}D?MdajGmZE>S|4} (p ct*X.5096020Ph0  *H  0[1 0 UGB10 UState1 0 UCity1 0 U Org1 0 U OU10U localhost0 130119142752Z 230117142752Z0[1 0 UGB10 UState1 0 UCity1 0 U Org1 0 U OU10U localhost0"0  *H 0 fДq./[(v툞zJCZdFGibD}ypC FqT =ҚN ;~:sY#qGHvEPCqڷ"Pؕ%Hd|}s-F~cNSk<mH?y\$$>W6i/$g)"~Rq>6"$~ \}kVJkƬT`)z拣4lMz50  *H  c;gS3;KmOn/TϜSJ;'Lm;hr=l,VꙜaqW?Mӧ!͓ 3$y\m9;kr&Iy )GS[}*'MH#oW΢ #j2(ɼAn lX-hǓ JmUWPtNSTY8os$С@"чo.`D,1hUDWA^@VONoEm/G-X䲧undertow-2.2.16.Final/core/src/test/resources/server.pem000066400000000000000000000054371420065311100232240ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAumYcFtCUib7X6HEuLwa9iNhbARofhSis73aoBe2IngR62/NK Q1oFsWRGR2liRH0XeXAOQwqqRnG7VBy8C+AMsT3Smk7Tfwk7/ReUfjrhc1kjcUf4 SBJ2/JRFUA5DB5nW8htxlo/qAdq3Iv6RUBnYlR4lSGR8sH1zLUZ+GQVj3k6nU3+E zsdr3+PHPBiEqtnvjpee/Idt76ZIoZkC2eg/ecxci9X086fl0ySdFrvoJD5XoZc2 aS+fDxebBSQElWcpIn5S7oGOtBd/ce8Yxj42gCIkkOp+IPua5Vy9pRW4Bwd9hGvb 5aBW1UprxqyZ1VRgKXr8vOaLozQebE2Deq61NQIDAQABAoIBAQCAsjmYowC7rlGi QmrRu0SntEH5G9FBfhkQ6QsPtLZL6+nr7SmMIR6nIQXJDoDzqq7HgM/ICBgStTnS 1Fgdlt8MjRPYyK4MGxMZJuu2z+6TVqs67qcFFAKlV7YXlRFAsT4QQVSG0OyPxTQG 7F7mQEIiiwLQ3didfrBERVSQ8ADJHrQOgT9Nwl3SzHAqEZFRm+u/WYs/sswPYmEJ A68WkJ09dKJSJbcZUIMDG+J0SXv7HASYelsinqMty6zxJzyMzAMMWb6tJ1MMzusE G+yFzElx0FUClwfdElpAvjh+vlEJTw3gKLJJAI+Qs7y/lrQgNSPOO3s1jIjRR1R3 59Njy2ftAoGBAPFrGxOPkYOfp5LVLd3LVXTtsDkqQ/uhk41PsmnI4/QZY0DO5RO7 b2c1bJthLr1/ESP0Chd9EW+LizQXYVnHvJia/VLA46EMyduu7VkYwQQQ1iOIyTm8 rNkeUuIkrw4iH4VJggMMnsnbOLHgkea2yPqg25LbHR97TB3hnxbN9f6fAoGBAMWo RfL2xdJQxsauf3SWZzdKWj+7rZniaCiq358c7u640oWPCPw+54y4+7aktYsR2PH0 5jlpSZ6499o1aacHkvkrgF9fjm/MwbS9cKrxuHhbM9GglxyvswqbZh1Chm0zpGfe ub5n5RWMpl2FHSDfDEa7UCMVMt9CrsnU4P74e7+rAoGBAMoN7ZyChbSXNEZlW70N SJnTsbE2ma2KPxd/g4CcHYWYlgSQ5RONxaCpCxxEyzzYk7z2rFeaWrR0I27WvqjI ziUfWzQesqWBMZVHI+l1GV7QxJj7DAfhzPzvL0mMkGMQ1jbVHhZ1QpUJgLsHjLV/ eFijtwKDly1ZIYzE4ETS3rdbAoGAadVPFugBRjqQJIP8pN1/iMBcEHIaYyIyaUwN DrI8UUBPIMpUolPAQb4usT4CIuO8iNl7iFQS4lTiCUm+N3w7uwUK6IZOyxgUxAUH VdC12GPlHCJjpy2ArXZFt/cN6VzUc/Vy+TvCEsbLsZl73kTv2tOi9hX8tkSLOHCu xHciM58CgYEA7GVuqPYynG9ftuMBNXLNz6D+tgDxgh3MGzFIbFGw2cPeOWRZ1l/S N/8DXoax/r8YoIriT+fj+AS5V9BAnM+Jg9Pt4WccbTHGFtdJUereE2zvrejBomhq 5kWFD740ocQ5Dn4tultNOtm/hVljuP3FCO5EmvjsdJliLEPaDU4KdpE= -----END RSA PRIVATE KEY----- -----BEGIN CERTIFICATE----- MIIDMjCCAhqgAwIBAgIEUPqtaDANBgkqhkiG9w0BAQsFADBbMQswCQYDVQQGEwJH QjEOMAwGA1UECBMFU3RhdGUxDTALBgNVBAcTBENpdHkxDDAKBgNVBAoTA09yZzEL MAkGA1UECxMCT1UxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xMzAxMTkxNDI3NTJa Fw0yMzAxMTcxNDI3NTJaMFsxCzAJBgNVBAYTAkdCMQ4wDAYDVQQIEwVTdGF0ZTEN MAsGA1UEBxMEQ2l0eTEMMAoGA1UEChMDT3JnMQswCQYDVQQLEwJPVTESMBAGA1UE AxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAumYc FtCUib7X6HEuLwa9iNhbARofhSis73aoBe2IngR62/NKQ1oFsWRGR2liRH0XeXAO QwqqRnG7VBy8C+AMsT3Smk7Tfwk7/ReUfjrhc1kjcUf4SBJ2/JRFUA5DB5nW8htx lo/qAdq3Iv6RUBnYlR4lSGR8sH1zLUZ+GQVj3k6nU3+Ezsdr3+PHPBiEqtnvjpee /Idt76ZIoZkC2eg/ecxci9X086fl0ySdFrvoJD5XoZc2aS+fDxebBSQElWcpIn5S 7oGOtBd/ce8Yxj42gCIkkOp+IPua5Vy9pRW4Bwd9hGvb5aBW1UprxqyZ1VRgKXr8 vOaLozQebE2Deq61NQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBjO2dTMzvq++zk Ee8AS5LXbahPbrUvGlSKGs+cU0o78ieJTBlt+8zTO8Gfh2gdcj1sLLEXAOSrVuqZ nB5hurlxVz9Nq9On3eEhzZMKiTOBJHm1XG05mb4WOwDK0u1rjcf/ctMmSXkaDSlH D1OX1NMXo1t9KifW8xdNSCPSbwgP4Ff3GMnOoiAjarcynd3X1CgeybwQQR39nIvK boEFB+kLwMbfbJ9Y76wtaJLHk5XYDRySFI8TE0ptt8NVHQNX3lDNdMwa3/OXTlMF 7lRZvzjib/yRc4EkjtChz0Ai0Ydv6gAWLrRg40ScLDFo2FXGRANXiPBBkvZeohdA oFalnAXq -----END CERTIFICATE----- undertow-2.2.16.Final/core/src/test/resources/server.truststore000066400000000000000000000016471420065311100247000ustar00rootroot000000000000004cn=test client, ou=ou, o=org, l=city, st=state, c=gb=> 3X.509:060P0  *H  0]1 0 UGB10 UState1 0 UCity1 0 U Org1 0 U OU10U Test Client0 130119142920Z 230117142920Z0]1 0 UGB10 UState1 0 UCity1 0 U Org1 0 U OU10U Test Client0"0  *H 0 \3f J$ ܙ6j<&@0_X-D)\ds\5exNbmå[ߒ4;E$y6?W3=5j^^S?Е34/߅:=$D!u 4:< 50wQ@0u)r*zVy$ F)S4֮pq$Kk,(Q0; 0  *H  \.USB:(p P0Y`,|: ͮicO,/8_aq6:Non쮏0hTcB~b`${e̯ІP\(QejEhS ISN d\vvfughn )CWb_K`δڕK-kE202|fSbZ kECf^I.tPe$SA7Ahundertow-2.2.16.Final/coverage-report/000077500000000000000000000000001420065311100175765ustar00rootroot00000000000000undertow-2.2.16.Final/coverage-report/pom.xml000066400000000000000000000155011420065311100211150ustar00rootroot00000000000000 4.0.0 io.undertow undertow-parent 2.2.16.Final undertow-coverage-report Undertow Test Coverage Report aggregates and compiles jacoco test coverage report pom org.jacoco jacoco-maven-plugin ${version.org.jacoco} merge-reports test-compile merge ${project.basedir}/../ **/target/jacoco.exec compile-report test report org.apache.maven.plugins maven-dependency-plugin jacoco-dependency-ant copy test-compile false org.jacoco org.jacoco.ant ${version.org.jacoco} true ${basedir}/target/jacoco-jars org.apache.maven.plugins maven-antrun-plugin test run org.jacoco org.jacoco.ant ${version.org.jacoco} undertow-2.2.16.Final/dist/000077500000000000000000000000001420065311100154355ustar00rootroot00000000000000undertow-2.2.16.Final/dist/assembly-src.xml000066400000000000000000000032611420065311100205650ustar00rootroot00000000000000 src zip tar.gz false .. undertow-${project.version}-src **/*.xml **/src/** **/*.txt **/*.sh **/*.bat **/*.md tools/** **/target/** **/.git **/.project **/.classpath **/.settings **/.metadata **/.iml **/.ipr **/.iws **/.idea .* nbactions.xml nb-configuration.xml catalog.xml undertow-2.2.16.Final/dist/assembly.xml000066400000000000000000000025461420065311100200050ustar00rootroot00000000000000 distro zip tar.gz false io.undertow:undertow-core io.undertow:undertow-servlet io.undertow:undertow-examples / org.jboss.xnio:xnio-api org.jboss.xnio:xnio-nio org.jboss.logging:jboss-logging org.jboss.spec.javax.servlet:jboss-servlet-api_3.1_spec lib ../README.md / true ../LICENSE.txt / undertow-2.2.16.Final/dist/pom.xml000066400000000000000000000105761420065311100167630ustar00rootroot00000000000000 4.0.0 io.undertow undertow-parent 2.2.16.Final io.undertow undertow-dist 2.2.16.Final Undertow: Distribution pom io.undertow undertow-core io.undertow undertow-servlet io.undertow undertow-examples release release org.apache.maven.plugins maven-assembly-plugin assemble package single assembly.xml undertow-${project.version} false target/ target/assembly/work gnu assemble-src package single assembly-src.xml undertow-${project.version} true target/ target/assembly-src/work gnu undertow-2.2.16.Final/examples/000077500000000000000000000000001420065311100163105ustar00rootroot00000000000000undertow-2.2.16.Final/examples/README000066400000000000000000000004001420065311100171620ustar00rootroot00000000000000Undertow Examples These provide some simple examples of how to run an embedded Undertow server. To run the examples simply run java -jar target/undertow-examples.jar or alternatively: mvn exec:exec And select the example you wish to run from the menuundertow-2.2.16.Final/examples/conf/000077500000000000000000000000001420065311100172355ustar00rootroot00000000000000undertow-2.2.16.Final/examples/conf/nodes.xml000066400000000000000000000002501420065311100210640ustar00rootroot00000000000000 localhost 8080 undertow-2.2.16.Final/examples/pom.xml000066400000000000000000000103051420065311100176240ustar00rootroot00000000000000 4.0.0 io.undertow undertow-parent 2.2.16.Final io.undertow undertow-examples 2.2.16.Final Undertow Examples io.undertow undertow-core io.undertow undertow-servlet io.undertow undertow-websockets-jsr org.jboss.logging jboss-logging-processor provided org.jboss.xnio xnio-nio org.jboss.threads jboss-threads org.jboss.logmanager jboss-logmanager undertow-examples src/main/java **/*.java src/main/resources org.apache.maven.plugins maven-shade-plugin package shade org.apache.maven.plugins maven-jar-plugin io.undertow.examples.Runner org.codehaus.mojo exec-maven-plugin 1.2.1 java -jar target/${project.build.finalName}.jar undertow-2.2.16.Final/examples/src/000077500000000000000000000000001420065311100170775ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/000077500000000000000000000000001420065311100200235ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/java/000077500000000000000000000000001420065311100207445ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/java/io/000077500000000000000000000000001420065311100213535ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/java/io/undertow/000077500000000000000000000000001420065311100232225ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/000077500000000000000000000000001420065311100250405ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/Runner.java000066400000000000000000000141711420065311100271600ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.examples; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.xnio.IoUtils; /** * Simple utility to make it easy to run the examples * * @author Stuart Douglas */ public class Runner { private static final String TARGET_CLASS = "target" + File.separatorChar + "classes" + File.separatorChar; public static void main(final String[] args) { System.setProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager"); URL url = Runner.class.getClassLoader().getResource(Runner.class.getPackage().getName().replace(".", "/")); if (url == null) { throw new RuntimeException("Could not locate examples package"); } final Map examples = new HashMap<>(); //hackz to discover all the example classes on the class path ZipInputStream in = null; boolean fromJarFile = false; try { String finalURIString = url.toString(); if(url.getPath().contains("!")) { fromJarFile = true; finalURIString = url.getPath().substring(0, url.getPath().indexOf("!")); } if(fromJarFile) { String zipPath = finalURIString.replace("file:", ""); in = new ZipInputStream(new FileInputStream(zipPath)); ZipEntry entry = in.getNextEntry(); while (entry != null) { if (entry.getName().endsWith(".class")) { String className = entry.getName().substring(0, entry.getName().length() - 6).replace("/", "."); try { Class clazz = Class.forName(className); UndertowExample example = clazz.getAnnotation(UndertowExample.class); if (example != null) { examples.put(example.value(), clazz); } } catch (Throwable e) { //ignore } } entry = in.getNextEntry(); } }else { try { try (Stream paths = Files.walk(Paths.get(url.toURI()))) { Map> annotationMapping = paths .filter(Files::isRegularFile) .filter(path -> path.toFile().getName().endsWith(".class")) .map(Runner::toFileName) .map(fileName -> fileName.replace(File.separator, ".")) .map(Runner::instance) .filter(Optional::isPresent) .filter(clazz -> clazz.get().getAnnotation(UndertowExample.class) != null) .collect(Collectors.toMap(clazz -> clazz.get().getAnnotation(UndertowExample.class).value(), Optional::get)); examples.putAll(annotationMapping); } } catch (URISyntaxException e) { e.printStackTrace(); } } final List names = new ArrayList<>(examples.keySet()); Collections.sort(names); System.out.println("Welcome to the Undertow Examples"); System.out.println("Please select an example:"); for (int i = 0; i < names.size(); ++i) { System.out.print((char) ('a' + i)); System.out.println(") " + names.get(i)); } byte[] data = new byte[1]; System.in.read(data); String example = names.get(data[0] - 'a'); System.out.println("Running example " + example); Class exampleClass = examples.get(example); UndertowExample annotation = (UndertowExample) exampleClass.getAnnotation(UndertowExample.class); System.out.println("Please point your web browser at " + annotation.location()); final Method main = exampleClass.getDeclaredMethod("main", String[].class); main.invoke(null, (Object)args); } catch (IOException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { throw new RuntimeException(e); } finally { IoUtils.safeClose(in); } } private static String toFileName(Path path) { String pathName = path.toFile().getAbsolutePath(); int index = pathName.indexOf(TARGET_CLASS) + TARGET_CLASS.length(); int classIndex = pathName.lastIndexOf(".class"); return pathName.substring(index, classIndex); } private static Optional> instance(String clazz) { try { return Optional.ofNullable(Class.forName(clazz)); } catch (ClassNotFoundException e) { return Optional.empty(); } } } undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/UndertowExample.java000066400000000000000000000021331420065311100310250ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.examples; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author Stuart Douglas */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface UndertowExample { String value(); String location() default "http://localhost:8080"; } undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/chat/000077500000000000000000000000001420065311100257575ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/chat/ChatServer.java000066400000000000000000000057631420065311100307030ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.examples.chat; import io.undertow.Undertow; import io.undertow.examples.UndertowExample; import io.undertow.server.handlers.resource.ClassPathResourceManager; import io.undertow.websockets.WebSocketConnectionCallback; import io.undertow.websockets.core.AbstractReceiveListener; import io.undertow.websockets.core.BufferedTextMessage; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSockets; import io.undertow.websockets.spi.WebSocketHttpExchange; import static io.undertow.Handlers.path; import static io.undertow.Handlers.resource; import static io.undertow.Handlers.websocket; /** * @author Stuart Douglas */ @UndertowExample("Chat") public class ChatServer { public static void main(final String[] args) { System.out.println("To see chat in action is to open two different browsers and point them at http://localhost:8080"); Undertow server = Undertow.builder() .addHttpListener(8080, "localhost") .setHandler(path() .addPrefixPath("/myapp", websocket(new WebSocketConnectionCallback() { @Override public void onConnect(WebSocketHttpExchange exchange, WebSocketChannel channel) { channel.getReceiveSetter().set(new AbstractReceiveListener() { @Override protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage message) { final String messageData = message.getData(); for (WebSocketChannel session : channel.getPeerConnections()) { WebSockets.sendText(messageData, session, null); } } }); channel.resumeReceives(); } })) .addPrefixPath("/", resource(new ClassPathResourceManager(ChatServer.class.getClassLoader(), ChatServer.class.getPackage())) .addWelcomeFiles("index.html"))) .build(); server.start(); } } undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/chat/index.html000066400000000000000000000063411420065311100277600ustar00rootroot00000000000000 Undertow Chat

    Web Socket Chat

    undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/fileserving/000077500000000000000000000000001420065311100273555ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/fileserving/FileServer.java000066400000000000000000000026421420065311100322720ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.examples.fileserving; import io.undertow.Undertow; import io.undertow.examples.UndertowExample; import io.undertow.server.handlers.resource.PathResourceManager; import java.nio.file.Paths; import static io.undertow.Handlers.resource; /** * @author Stuart Douglas */ @UndertowExample("File Serving") public class FileServer { public static void main(final String[] args) { Undertow server = Undertow.builder() .addHttpListener(8080, "localhost") .setHandler(resource(new PathResourceManager(Paths.get(System.getProperty("user.home")), 100)) .setDirectoryListingEnabled(true)) .build(); server.start(); } } undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/helloworld/000077500000000000000000000000001420065311100272135ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/helloworld/HelloWorldServer.java000066400000000000000000000031311420065311100333160ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.examples.helloworld; import io.undertow.Undertow; import io.undertow.examples.UndertowExample; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; /** * @author Stuart Douglas */ @UndertowExample("Hello World") public class HelloWorldServer { public static void main(final String[] args) { Undertow server = Undertow.builder() .addHttpListener(8080, "localhost") .setHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain"); exchange.getResponseSender().send("Hello World"); } }).build(); server.start(); } } undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/http2/000077500000000000000000000000001420065311100261015ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/http2/Http2Server.java000066400000000000000000000151351420065311100311410ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.examples.http2; import static io.undertow.Handlers.predicate; import static io.undertow.Handlers.resource; import static io.undertow.predicate.Predicates.secure; import java.io.InputStream; import java.net.URI; import java.nio.file.Files; import java.nio.file.Paths; import java.security.KeyStore; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import io.undertow.Handlers; import io.undertow.Undertow; import io.undertow.UndertowOptions; import io.undertow.attribute.ExchangeAttributes; import io.undertow.examples.UndertowExample; import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.LearningPushHandler; import io.undertow.server.handlers.proxy.LoadBalancingProxyClient; import io.undertow.server.handlers.proxy.ProxyHandler; import io.undertow.server.handlers.resource.PathResourceManager; import io.undertow.server.session.InMemorySessionManager; import io.undertow.server.session.SessionAttachmentHandler; import io.undertow.server.session.SessionCookieConfig; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import org.xnio.OptionMap; import org.xnio.Xnio; /** * @author Stuart Douglas */ @UndertowExample(value = "HTTP2", location = "https://localhost:8443") public class Http2Server { private static final char[] STORE_PASSWORD = "password".toCharArray(); public static void main(final String[] args) throws Exception { String version = System.getProperty("java.version"); System.out.println("Java version " + version); if(version.charAt(0) == '1' && Integer.parseInt(version.charAt(2) + "") < 8 ) { System.out.println("This example requires Java 1.8 or later"); System.out.println("The HTTP2 spec requires certain cyphers that are not present in older JVM's"); System.out.println("See section 9.2.2 of the HTTP2 specification for details"); System.exit(1); } String bindAddress = System.getProperty("bind.address", "localhost"); SSLContext sslContext = createSSLContext(loadKeyStore("server.keystore"), loadKeyStore("server.truststore")); Undertow server = Undertow.builder() .setServerOption(UndertowOptions.ENABLE_HTTP2, true) .addHttpListener(8080, bindAddress) .addHttpsListener(8443, bindAddress, sslContext) .setHandler(new SessionAttachmentHandler(new LearningPushHandler(100, -1, Handlers.header(predicate(secure(), resource(new PathResourceManager(Paths.get(System.getProperty("example.directory", System.getProperty("user.home"))), 100)) .setDirectoryListingEnabled(true), new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseHeaders().add(Headers.LOCATION, "https://" + exchange.getHostName() + ":" + (exchange.getHostPort() + 363) + exchange.getRelativePath()); exchange.setStatusCode(StatusCodes.TEMPORARY_REDIRECT); } }), "x-undertow-transport", ExchangeAttributes.transportProtocol())), new InMemorySessionManager("test"), new SessionCookieConfig())).build(); server.start(); SSLContext clientSslContext = createSSLContext(loadKeyStore("client.keystore"), loadKeyStore("client.truststore")); LoadBalancingProxyClient proxy = new LoadBalancingProxyClient() .addHost(new URI("https://localhost:8443"), null, new UndertowXnioSsl(Xnio.getInstance(), OptionMap.EMPTY, clientSslContext), OptionMap.create(UndertowOptions.ENABLE_HTTP2, true)) .setConnectionsPerThread(20); Undertow reverseProxy = Undertow.builder() .setServerOption(UndertowOptions.ENABLE_HTTP2, true) .addHttpListener(8081, bindAddress) .addHttpsListener(8444, bindAddress, sslContext) .setHandler(ProxyHandler.builder().setProxyClient(proxy).setMaxRequestTime( 30000).build()) .build(); reverseProxy.start(); } private static KeyStore loadKeyStore(String name) throws Exception { String storeLoc = System.getProperty(name); final InputStream stream; if(storeLoc == null) { stream = Http2Server.class.getResourceAsStream(name); } else { stream = Files.newInputStream(Paths.get(storeLoc)); } if(stream == null) { throw new RuntimeException("Could not load keystore"); } try(InputStream is = stream) { KeyStore loadedKeystore = KeyStore.getInstance("JKS"); loadedKeystore.load(is, password(name)); return loadedKeystore; } } static char[] password(String name) { String pw = System.getProperty(name + ".password"); return pw != null ? pw.toCharArray() : STORE_PASSWORD; } private static SSLContext createSSLContext(final KeyStore keyStore, final KeyStore trustStore) throws Exception { KeyManager[] keyManagers; KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, password("key")); keyManagers = keyManagerFactory.getKeyManagers(); TrustManager[] trustManagers; TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(trustStore); trustManagers = trustManagerFactory.getTrustManagers(); SSLContext sslContext; sslContext = SSLContext.getInstance("TLS"); sslContext.init(keyManagers, trustManagers, null); return sslContext; } } undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/http2/client.keystore000066400000000000000000000042031420065311100311450ustar00rootroot00000000000000client+)W'l`bLr R߈nQ ٿuЮճ7T߂{n($Ez(f!LS@؃ 'WKVPO)ν#y* 2 &E*f +.Gjd "o'3_g\Uh:iޠߣruL uGII59zGV@3S%0 DZ(hފ3f?d  -'06m0x҃Fc^ğs!!`tܟ*^^ׄSn?*fg `t U,i;}_φڧC vocMN )eDG^ȼ|]K͖rwhtmc|ti*+*X.509:060P0  *H  0]1 0 UGB10 UState1 0 UCity1 0 U Org1 0 U OU10U Test Client0 130119142920Z 230117142920Z0]1 0 UGB10 UState1 0 UCity1 0 U Org1 0 U OU10U Test Client0"0  *H 0 \3f J$ ܙ6j<&@0_X-D)\ds\5exNbmå[ߒ4;E$y6?W3=5j^^S?Е34/߅:=$D!u 4:< 50wQ@0u)r*zVy$ F)S4֮pq$Kk,(Q0; 0  *H  \.USB:(p P0Y`,|: ͮicO,/8_aq6:Non쮏0hTcB~b`${e̯ІP\(QejEhS ISN d\vvfughn )CWb_K`δڕK-kE202|fSbZ kECf^I.tP$Ip}yJundertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/http2/client.truststore000066400000000000000000000015651420065311100315460ustar00rootroot00000000000000serverW6i/$g)"~Rq>6"$~ \}kVJkƬT`)z拣4lMz50  *H  c;gS3;KmOn/TϜSJ;'Lm;hr=l,VꙜaqW?Mӧ!͓ 3$y\m9;kr&Iy )GS[}*'MH#oW΢ #j2(ɼAn lX-hǓ JmUWPtNSTY8os$С@"чo.`D,1hUDWA^@VG܅ q̗undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/http2/server.keystore000066400000000000000000000042001420065311100311720ustar00rootroot00000000000000server@&y,˪Ŕ ?^b+ɁZ1jGˠq'D{uѪ  ~,j6 9fjN% zjډ.7 bFg5 /dpB`hxLΤ<"[}NBq ײEՈӉjQW`Hr&hI[ȏ2'DSleBTGޙ$ԢX-A>kQ)|k,nK~6zsurǚG[3w%$A;3VN,sH1ͤbM !Ĵ@_Ao۫[0@QX "?gC]"Ϊ, ZFɰ| ?'0O\/څ?F)b6GN\9ld"8 l~Ŷ5bWquP[Ngnf1r'=EKOQnᲀX5½v\3=gUWѨaEWq^lf $7NG;:U}WHbpb|v K5/Fly(YADKO-]*s@ϕǗK¡7*1 b8'!]P'_")Ɲ*? QXsX%F&0}D?MdajGmZE>S|4} (p ct*X.5096020Ph0  *H  0[1 0 UGB10 UState1 0 UCity1 0 U Org1 0 U OU10U localhost0 130119142752Z 230117142752Z0[1 0 UGB10 UState1 0 UCity1 0 U Org1 0 U OU10U localhost0"0  *H 0 fДq./[(v툞zJCZdFGibD}ypC FqT =ҚN ;~:sY#qGHvEPCqڷ"Pؕ%Hd|}s-F~cNSk<mH?y\$$>W6i/$g)"~Rq>6"$~ \}kVJkƬT`)z拣4lMz50  *H  c;gS3;KmOn/TϜSJ;'Lm;hr=l,VꙜaqW?Mӧ!͓ 3$y\m9;kr&Iy )GS[}*'MH#oW΢ #j2(ɼAn lX-hǓ JmUWPtNSTY8os$С@"чo.`D,1hUDWA^@VONoEm/G-X䲧undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/http2/server.pem000066400000000000000000000054371420065311100301230ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAumYcFtCUib7X6HEuLwa9iNhbARofhSis73aoBe2IngR62/NK Q1oFsWRGR2liRH0XeXAOQwqqRnG7VBy8C+AMsT3Smk7Tfwk7/ReUfjrhc1kjcUf4 SBJ2/JRFUA5DB5nW8htxlo/qAdq3Iv6RUBnYlR4lSGR8sH1zLUZ+GQVj3k6nU3+E zsdr3+PHPBiEqtnvjpee/Idt76ZIoZkC2eg/ecxci9X086fl0ySdFrvoJD5XoZc2 aS+fDxebBSQElWcpIn5S7oGOtBd/ce8Yxj42gCIkkOp+IPua5Vy9pRW4Bwd9hGvb 5aBW1UprxqyZ1VRgKXr8vOaLozQebE2Deq61NQIDAQABAoIBAQCAsjmYowC7rlGi QmrRu0SntEH5G9FBfhkQ6QsPtLZL6+nr7SmMIR6nIQXJDoDzqq7HgM/ICBgStTnS 1Fgdlt8MjRPYyK4MGxMZJuu2z+6TVqs67qcFFAKlV7YXlRFAsT4QQVSG0OyPxTQG 7F7mQEIiiwLQ3didfrBERVSQ8ADJHrQOgT9Nwl3SzHAqEZFRm+u/WYs/sswPYmEJ A68WkJ09dKJSJbcZUIMDG+J0SXv7HASYelsinqMty6zxJzyMzAMMWb6tJ1MMzusE G+yFzElx0FUClwfdElpAvjh+vlEJTw3gKLJJAI+Qs7y/lrQgNSPOO3s1jIjRR1R3 59Njy2ftAoGBAPFrGxOPkYOfp5LVLd3LVXTtsDkqQ/uhk41PsmnI4/QZY0DO5RO7 b2c1bJthLr1/ESP0Chd9EW+LizQXYVnHvJia/VLA46EMyduu7VkYwQQQ1iOIyTm8 rNkeUuIkrw4iH4VJggMMnsnbOLHgkea2yPqg25LbHR97TB3hnxbN9f6fAoGBAMWo RfL2xdJQxsauf3SWZzdKWj+7rZniaCiq358c7u640oWPCPw+54y4+7aktYsR2PH0 5jlpSZ6499o1aacHkvkrgF9fjm/MwbS9cKrxuHhbM9GglxyvswqbZh1Chm0zpGfe ub5n5RWMpl2FHSDfDEa7UCMVMt9CrsnU4P74e7+rAoGBAMoN7ZyChbSXNEZlW70N SJnTsbE2ma2KPxd/g4CcHYWYlgSQ5RONxaCpCxxEyzzYk7z2rFeaWrR0I27WvqjI ziUfWzQesqWBMZVHI+l1GV7QxJj7DAfhzPzvL0mMkGMQ1jbVHhZ1QpUJgLsHjLV/ eFijtwKDly1ZIYzE4ETS3rdbAoGAadVPFugBRjqQJIP8pN1/iMBcEHIaYyIyaUwN DrI8UUBPIMpUolPAQb4usT4CIuO8iNl7iFQS4lTiCUm+N3w7uwUK6IZOyxgUxAUH VdC12GPlHCJjpy2ArXZFt/cN6VzUc/Vy+TvCEsbLsZl73kTv2tOi9hX8tkSLOHCu xHciM58CgYEA7GVuqPYynG9ftuMBNXLNz6D+tgDxgh3MGzFIbFGw2cPeOWRZ1l/S N/8DXoax/r8YoIriT+fj+AS5V9BAnM+Jg9Pt4WccbTHGFtdJUereE2zvrejBomhq 5kWFD740ocQ5Dn4tultNOtm/hVljuP3FCO5EmvjsdJliLEPaDU4KdpE= -----END RSA PRIVATE KEY----- -----BEGIN CERTIFICATE----- MIIDMjCCAhqgAwIBAgIEUPqtaDANBgkqhkiG9w0BAQsFADBbMQswCQYDVQQGEwJH QjEOMAwGA1UECBMFU3RhdGUxDTALBgNVBAcTBENpdHkxDDAKBgNVBAoTA09yZzEL MAkGA1UECxMCT1UxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xMzAxMTkxNDI3NTJa Fw0yMzAxMTcxNDI3NTJaMFsxCzAJBgNVBAYTAkdCMQ4wDAYDVQQIEwVTdGF0ZTEN MAsGA1UEBxMEQ2l0eTEMMAoGA1UEChMDT3JnMQswCQYDVQQLEwJPVTESMBAGA1UE AxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAumYc FtCUib7X6HEuLwa9iNhbARofhSis73aoBe2IngR62/NKQ1oFsWRGR2liRH0XeXAO QwqqRnG7VBy8C+AMsT3Smk7Tfwk7/ReUfjrhc1kjcUf4SBJ2/JRFUA5DB5nW8htx lo/qAdq3Iv6RUBnYlR4lSGR8sH1zLUZ+GQVj3k6nU3+Ezsdr3+PHPBiEqtnvjpee /Idt76ZIoZkC2eg/ecxci9X086fl0ySdFrvoJD5XoZc2aS+fDxebBSQElWcpIn5S 7oGOtBd/ce8Yxj42gCIkkOp+IPua5Vy9pRW4Bwd9hGvb5aBW1UprxqyZ1VRgKXr8 vOaLozQebE2Deq61NQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBjO2dTMzvq++zk Ee8AS5LXbahPbrUvGlSKGs+cU0o78ieJTBlt+8zTO8Gfh2gdcj1sLLEXAOSrVuqZ nB5hurlxVz9Nq9On3eEhzZMKiTOBJHm1XG05mb4WOwDK0u1rjcf/ctMmSXkaDSlH D1OX1NMXo1t9KifW8xdNSCPSbwgP4Ff3GMnOoiAjarcynd3X1CgeybwQQR39nIvK boEFB+kLwMbfbJ9Y76wtaJLHk5XYDRySFI8TE0ptt8NVHQNX3lDNdMwa3/OXTlMF 7lRZvzjib/yRc4EkjtChz0Ai0Ydv6gAWLrRg40ScLDFo2FXGRANXiPBBkvZeohdA oFalnAXq -----END CERTIFICATE----- undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/http2/server.truststore000066400000000000000000000016471420065311100315770ustar00rootroot000000000000004cn=test client, ou=ou, o=org, l=city, st=state, c=gb=> 3X.509:060P0  *H  0]1 0 UGB10 UState1 0 UCity1 0 U Org1 0 U OU10U Test Client0 130119142920Z 230117142920Z0]1 0 UGB10 UState1 0 UCity1 0 U Org1 0 U OU10U Test Client0"0  *H 0 \3f J$ ܙ6j<&@0_X-D)\ds\5exNbmå[ߒ4;E$y6?W3=5j^^S?Е34/߅:=$D!u 4:< 50wQ@0u)r*zVy$ F)S4֮pq$Kk,(Q0; 0  *H  \.USB:(p P0Y`,|: ͮicO,/8_aq6:Non쮏0hTcB~b`${e̯ІP\(QejEhS ISN d\vvfughn )CWb_K`δڕK-kE202|fSbZ kECf^I.tPe$SA7Ahundertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/jsrwebsockets/000077500000000000000000000000001420065311100277305ustar00rootroot00000000000000JSRWebSocketServer.java000066400000000000000000000052611420065311100341540ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/jsrwebsockets/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.examples.jsrwebsockets; import javax.servlet.ServletException; import io.undertow.server.DefaultByteBufferPool; import io.undertow.Handlers; import io.undertow.Undertow; import io.undertow.examples.UndertowExample; import io.undertow.server.handlers.PathHandler; import io.undertow.server.handlers.resource.ClassPathResourceManager; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.websockets.jsr.WebSocketDeploymentInfo; /** * @author Stuart Douglas */ @UndertowExample("JSR Web Sockets") public class JSRWebSocketServer { public static void main(final String[] args) { PathHandler path = Handlers.path(); Undertow server = Undertow.builder() .addHttpListener(8080, "localhost") .setHandler(path) .build(); server.start(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(JSRWebSocketServer.class.getClassLoader()) .setContextPath("/") .addWelcomePage("index.html") .setResourceManager(new ClassPathResourceManager(JSRWebSocketServer.class.getClassLoader(), JSRWebSocketServer.class.getPackage())) .addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, new WebSocketDeploymentInfo() .setBuffers(new DefaultByteBufferPool(true, 100)) .addEndpoint(JsrChatWebSocketEndpoint.class) ) .setDeploymentName("chat.war"); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); try { path.addPrefixPath("/", manager.start()); } catch (ServletException e) { throw new RuntimeException(e); } } } JsrChatWebSocketEndpoint.java000066400000000000000000000007101420065311100353600ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/jsrwebsocketspackage io.undertow.examples.jsrwebsockets; import javax.websocket.OnMessage; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; /** * @author Stuart Douglas */ @ServerEndpoint("/myapp") public class JsrChatWebSocketEndpoint { @OnMessage public void message(String message, Session session) { for (Session s : session.getOpenSessions()) { s.getAsyncRemote().sendText(message); } } } undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/jsrwebsockets/index.html000066400000000000000000000050151420065311100317260ustar00rootroot00000000000000 Undertow Chat

    JSR-356 Web Socket Chat

    undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/reverseproxy/000077500000000000000000000000001420065311100276155ustar00rootroot00000000000000ModClusterProxyServer.java000066400000000000000000000061461420065311100347220ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/reverseproxy/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.examples.reverseproxy; import java.io.IOException; import io.undertow.Undertow; import io.undertow.examples.UndertowExample; import io.undertow.server.HttpHandler; import io.undertow.server.handlers.proxy.mod_cluster.MCMPConfig; import io.undertow.server.handlers.proxy.mod_cluster.ModCluster; import org.xnio.OptionMap; import org.xnio.Xnio; import org.xnio.XnioWorker; /** * @author Jean-Frederic Clere */ @UndertowExample("ModCluster Proxy Server") public class ModClusterProxyServer { /* the address and port to receive the MCMP elements */ static String chost = System.getProperty("io.undertow.examples.proxy.CADDRESS", "localhost"); static final int cport = Integer.parseInt(System.getProperty("io.undertow.examples.proxy.CPORT", "6666")); /* the address and port to receive normal requests */ static String phost = System.getProperty("io.undertow.examples.proxy.ADDRESS", "localhost"); static final int pport = Integer.parseInt(System.getProperty("io.undertow.examples.proxy.PORT", "8000")); public static void main(final String[] args) throws IOException { final XnioWorker worker = Xnio.getInstance().createWorker(OptionMap.EMPTY); final Undertow server; final ModCluster modCluster = ModCluster.builder(worker).build(); try { if (chost == null) { // We are going to guess it. chost = java.net.InetAddress.getLocalHost().getHostName(); System.out.println("Using: " + chost + ":" + cport); } modCluster.start(); // Create the proxy and mgmt handler final HttpHandler proxy = modCluster.createProxyHandler(); final MCMPConfig config = MCMPConfig.webBuilder() .setManagementHost(chost) .setManagementPort(cport) .enableAdvertise() .getParent() .build(); final HttpHandler mcmp = config.create(modCluster, proxy); server = Undertow.builder() .addHttpListener(cport, chost) .addHttpListener(pport, phost) .setHandler(mcmp) .build(); server.start(); // Start advertising the mcmp handler modCluster.advertise(config); } catch (Exception e) { e.printStackTrace(); } } } ReverseProxyServer.java000066400000000000000000000073201420065311100342470ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/reverseproxy/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.examples.reverseproxy; import io.undertow.Undertow; import io.undertow.examples.UndertowExample; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.proxy.LoadBalancingProxyClient; import io.undertow.server.handlers.proxy.ProxyHandler; import io.undertow.util.Headers; import java.net.URI; import java.net.URISyntaxException; /** * @author Stuart Douglas */ @UndertowExample("Reverse Proxy") public class ReverseProxyServer { public static void main(final String[] args) { try { final Undertow server1 = Undertow.builder() .addHttpListener(8081, "localhost") .setHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain"); exchange.getResponseSender().send("Server1"); } }) .build(); server1.start(); final Undertow server2 = Undertow.builder() .addHttpListener(8082, "localhost") .setHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain"); exchange.getResponseSender().send("Server2"); } }) .build(); server2.start(); final Undertow server3 = Undertow.builder() .addHttpListener(8083, "localhost") .setHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain"); exchange.getResponseSender().send("Server3"); } }) .build(); server3.start(); LoadBalancingProxyClient loadBalancer = new LoadBalancingProxyClient() .addHost(new URI("http://localhost:8081")) .addHost(new URI("http://localhost:8082")) .addHost(new URI("http://localhost:8083")) .setConnectionsPerThread(20); Undertow reverseProxy = Undertow.builder() .addHttpListener(8080, "localhost") .setIoThreads(4) .setHandler(ProxyHandler.builder().setProxyClient(loadBalancer).setMaxRequestTime( 30000).build()) .build(); reverseProxy.start(); } catch (URISyntaxException e) { throw new RuntimeException(e); } } } undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/security/000077500000000000000000000000001420065311100267075ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/security/basic/000077500000000000000000000000001420065311100277705ustar00rootroot00000000000000BasicAuthServer.java000066400000000000000000000067721420065311100336220ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/security/basic/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.examples.security.basic; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import io.undertow.Undertow; import io.undertow.examples.UndertowExample; import io.undertow.io.IoCallback; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.AuthenticationMode; import io.undertow.security.api.SecurityContext; import io.undertow.security.handlers.AuthenticationCallHandler; import io.undertow.security.handlers.AuthenticationConstraintHandler; import io.undertow.security.handlers.AuthenticationMechanismsHandler; import io.undertow.security.handlers.SecurityInitialHandler; import io.undertow.security.idm.IdentityManager; import io.undertow.security.impl.BasicAuthenticationMechanism; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; /** * Example of HTTP Basic auth *

    * TODO: this needs to be cleaned up * * @author Stuart Douglas */ @UndertowExample("Basic Authentication") public class BasicAuthServer { public static void main(final String[] args) { System.out.println("You can login with the following credentials:"); System.out.println("User: userOne Password: passwordOne"); System.out.println("User: userTwo Password: passwordTwo"); final Map users = new HashMap<>(2); users.put("userOne", "passwordOne".toCharArray()); users.put("userTwo", "passwordTwo".toCharArray()); final IdentityManager identityManager = new MapIdentityManager(users); Undertow server = Undertow.builder() .addHttpListener(8080, "localhost") .setHandler(addSecurity(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final SecurityContext context = exchange.getSecurityContext(); exchange.getResponseSender().send("Hello " + context.getAuthenticatedAccount().getPrincipal().getName(), IoCallback.END_EXCHANGE); } }, identityManager)) .build(); server.start(); } private static HttpHandler addSecurity(final HttpHandler toWrap, final IdentityManager identityManager) { HttpHandler handler = toWrap; handler = new AuthenticationCallHandler(handler); handler = new AuthenticationConstraintHandler(handler); final List mechanisms = Collections.singletonList(new BasicAuthenticationMechanism("My Realm")); handler = new AuthenticationMechanismsHandler(handler, mechanisms); handler = new SecurityInitialHandler(AuthenticationMode.PRO_ACTIVE, identityManager, handler); return handler; } } MapIdentityManager.java000066400000000000000000000060431420065311100343010ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/security/basic/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.examples.security.basic; import io.undertow.security.idm.Account; import io.undertow.security.idm.Credential; import io.undertow.security.idm.IdentityManager; import io.undertow.security.idm.PasswordCredential; import java.security.Principal; import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.Set; /** * A simple {@link IdentityManager} implementation, that just takes a map of users to their * password. * * This is in now way suitable for real world production use. * * * @author Stuart Douglas */ class MapIdentityManager implements IdentityManager { private final Map users; MapIdentityManager(final Map users) { this.users = users; } @Override public Account verify(Account account) { // An existing account so for testing assume still valid. return account; } @Override public Account verify(String id, Credential credential) { Account account = getAccount(id); if (account != null && verifyCredential(account, credential)) { return account; } return null; } @Override public Account verify(Credential credential) { // TODO Auto-generated method stub return null; } private boolean verifyCredential(Account account, Credential credential) { if (credential instanceof PasswordCredential) { char[] password = ((PasswordCredential) credential).getPassword(); char[] expectedPassword = users.get(account.getPrincipal().getName()); return Arrays.equals(password, expectedPassword); } return false; } private Account getAccount(final String id) { if (users.containsKey(id)) { return new Account() { private final Principal principal = new Principal() { @Override public String getName() { return id; } }; @Override public Principal getPrincipal() { return principal; } @Override public Set getRoles() { return Collections.emptySet(); } }; } return null; } } undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/servlet/000077500000000000000000000000001420065311100265245ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/servlet/MessageServlet.java000066400000000000000000000033701420065311100323230ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.examples.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class MessageServlet extends HttpServlet { public static final String MESSAGE = "message"; private String message; @Override public void init(final ServletConfig config) throws ServletException { super.init(config); message = config.getInitParameter(MESSAGE); } @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { PrintWriter writer = resp.getWriter(); writer.write(message); writer.close(); } @Override protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } } undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/servlet/ServletServer.java000066400000000000000000000052701420065311100322060ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.examples.servlet; import javax.servlet.ServletException; import io.undertow.Handlers; import io.undertow.Undertow; import io.undertow.examples.UndertowExample; import io.undertow.server.HttpHandler; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import static io.undertow.servlet.Servlets.defaultContainer; import static io.undertow.servlet.Servlets.deployment; import static io.undertow.servlet.Servlets.servlet; /** * @author Stuart Douglas */ @UndertowExample("Servlet") public class ServletServer { public static final String MYAPP = "/myapp"; public static void main(final String[] args) { try { DeploymentInfo servletBuilder = deployment() .setClassLoader(ServletServer.class.getClassLoader()) .setContextPath(MYAPP) .setDeploymentName("test.war") .addServlets( servlet("MessageServlet", MessageServlet.class) .addInitParam("message", "Hello World") .addMapping("/*"), servlet("MyServlet", MessageServlet.class) .addInitParam("message", "MyServlet") .addMapping("/myservlet")); DeploymentManager manager = defaultContainer().addDeployment(servletBuilder); manager.deploy(); HttpHandler servletHandler = manager.start(); PathHandler path = Handlers.path(Handlers.redirect(MYAPP)) .addPrefixPath(MYAPP, servletHandler); Undertow server = Undertow.builder() .addHttpListener(8080, "localhost") .setHandler(path) .build(); server.start(); } catch (ServletException e) { throw new RuntimeException(e); } } } undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/sessionhandling/000077500000000000000000000000001420065311100302305ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/sessionhandling/SessionServer.java000066400000000000000000000126141420065311100337110ustar00rootroot00000000000000package io.undertow.examples.sessionhandling; import io.undertow.Undertow; import io.undertow.examples.UndertowExample; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.PathHandler; import io.undertow.server.session.InMemorySessionManager; import io.undertow.server.session.Session; import io.undertow.server.session.SessionAttachmentHandler; import io.undertow.server.session.SessionConfig; import io.undertow.server.session.SessionCookieConfig; import io.undertow.server.session.SessionManager; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import java.util.Deque; import java.util.Map; @UndertowExample("Session Handling") public class SessionServer { public static void main(String[] args) { PathHandler pathHandler = new PathHandler(); pathHandler.addPrefixPath("/", new HttpHandler() { public void handleRequest(HttpServerExchange exchange) throws Exception { StringBuilder sb = new StringBuilder(); sb.append("

    "); sb.append(""); sb.append(""); sb.append(""); sb.append(""); sb.append(""); // To retrive the SessionManager use the attachmentKey SessionManager sm = exchange .getAttachment(SessionManager.ATTACHMENT_KEY); // same goes to SessionConfig SessionConfig sessionConfig = exchange .getAttachment(SessionConfig.ATTACHMENT_KEY); sb.append("
    "); sb.append("Destroy Session"); sb.append("
    "); Session session = sm.getSession(exchange, sessionConfig); if (session == null) session = sm.createSession(exchange, sessionConfig); sb.append("
      "); for (String string : session.getAttributeNames()) { sb.append("
    • " + string + " : " + session.getAttribute(string) + "
    • "); } sb.append("
    "); exchange.getResponseHeaders().add(Headers.CONTENT_TYPE, "text/html;"); exchange.getResponseSender().send(sb.toString()); } }); pathHandler.addPrefixPath("/addToSession", new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { SessionManager sm = exchange .getAttachment(SessionManager.ATTACHMENT_KEY); SessionConfig sessionConfig = exchange .getAttachment(SessionConfig.ATTACHMENT_KEY); Map> reqParams = exchange .getQueryParameters(); Session session = sm.getSession(exchange, sessionConfig); if (session == null) session = sm.createSession(exchange, sessionConfig); Deque deque = reqParams.get("attrName"); Deque dequeVal = reqParams.get("value"); session.setAttribute(deque.getLast(), dequeVal.getLast()); exchange.setStatusCode(StatusCodes.TEMPORARY_REDIRECT); exchange.getResponseHeaders().put(Headers.LOCATION, "/"); exchange.getResponseSender().close(); } }); pathHandler.addPrefixPath("/destroySession", new HttpHandler() { public void handleRequest(HttpServerExchange exchange) throws Exception { SessionManager sm = exchange .getAttachment(SessionManager.ATTACHMENT_KEY); SessionConfig sessionConfig = exchange .getAttachment(SessionConfig.ATTACHMENT_KEY); Session session = sm.getSession(exchange, sessionConfig); if (session == null) session = sm.createSession(exchange, sessionConfig); session.invalidate(exchange); exchange.setStatusCode(StatusCodes.TEMPORARY_REDIRECT); exchange.getResponseHeaders().put(Headers.LOCATION, "/"); exchange.getResponseSender().close(); } }); SessionManager sessionManager = new InMemorySessionManager( "SESSION_MANAGER"); SessionCookieConfig sessionConfig = new SessionCookieConfig(); /* * Use the sessionAttachmentHandler to add the sessionManager and * sessionCofing to the exchange of every request */ SessionAttachmentHandler sessionAttachmentHandler = new SessionAttachmentHandler( sessionManager, sessionConfig); // set as next handler your root handler sessionAttachmentHandler.setNext(pathHandler); System.out .println("Open the url and fill the form to add attributes into the session"); Undertow server = Undertow.builder().addHttpListener(8080, "localhost") .setHandler(sessionAttachmentHandler).build(); server.start(); } } undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/sse/000077500000000000000000000000001420065311100256325ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/sse/ServerSentEventsServer.java000066400000000000000000000053601420065311100331550ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.examples.sse; import io.undertow.Undertow; import io.undertow.examples.UndertowExample; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.resource.ClassPathResourceManager; import io.undertow.server.handlers.sse.ServerSentEventConnection; import io.undertow.server.handlers.sse.ServerSentEventHandler; import io.undertow.util.StringReadChannelListener; import java.io.IOException; import static io.undertow.Handlers.path; import static io.undertow.Handlers.resource; import static io.undertow.Handlers.serverSentEvents; /** * @author Stuart Douglas */ @UndertowExample("Server Sent Events") public class ServerSentEventsServer { public static void main(final String[] args) { final ServerSentEventHandler sseHandler = serverSentEvents(); HttpHandler chatHandler = new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { new StringReadChannelListener(exchange.getConnection().getByteBufferPool()) { @Override protected void stringDone(String string) { for(ServerSentEventConnection h : sseHandler.getConnections()) { h.send(string); } } @Override protected void error(IOException e) { } }.setup(exchange.getRequestChannel()); } }; Undertow server = Undertow.builder() .addHttpListener(8080, "localhost") .setHandler(path() .addPrefixPath("/sse", sseHandler) .addPrefixPath("/send", chatHandler) .addPrefixPath("/", resource(new ClassPathResourceManager(ServerSentEventsServer.class.getClassLoader(), ServerSentEventsServer.class.getPackage())).addWelcomeFiles("index.html"))) .build(); server.start(); } } undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/sse/index.html000066400000000000000000000070511420065311100276320ustar00rootroot00000000000000 Undertow Chat

    Server Sent Events Chat

    undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/websockets/000077500000000000000000000000001420065311100272115ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/websockets/WebSocketServer.java000066400000000000000000000052051420065311100331330ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.examples.websockets; import io.undertow.Undertow; import io.undertow.examples.UndertowExample; import io.undertow.server.handlers.resource.ClassPathResourceManager; import io.undertow.websockets.core.AbstractReceiveListener; import io.undertow.websockets.core.BufferedTextMessage; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSockets; import io.undertow.websockets.WebSocketConnectionCallback; import io.undertow.websockets.spi.WebSocketHttpExchange; import static io.undertow.Handlers.path; import static io.undertow.Handlers.resource; import static io.undertow.Handlers.websocket; /** * @author Stuart Douglas */ @UndertowExample("Web Sockets") public class WebSocketServer { public static void main(final String[] args) { Undertow server = Undertow.builder() .addHttpListener(8080, "localhost") .setHandler(path() .addPrefixPath("/myapp", websocket(new WebSocketConnectionCallback() { @Override public void onConnect(WebSocketHttpExchange exchange, WebSocketChannel channel) { channel.getReceiveSetter().set(new AbstractReceiveListener() { @Override protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage message) { WebSockets.sendText(message.getData(), channel, null); } }); channel.resumeReceives(); } })) .addPrefixPath("/", resource(new ClassPathResourceManager(WebSocketServer.class.getClassLoader(), WebSocketServer.class.getPackage())).addWelcomeFiles("index.html"))) .build(); server.start(); } } undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/websockets/index.html000066400000000000000000000033021420065311100312040ustar00rootroot00000000000000 Web Socket Test
    undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/websockets_extension/000077500000000000000000000000001420065311100313055ustar00rootroot00000000000000WebSocketServer.java000066400000000000000000000056761420065311100351640ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/websockets_extension/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.examples.websockets_extension; import io.undertow.Undertow; import io.undertow.examples.UndertowExample; import io.undertow.server.handlers.resource.ClassPathResourceManager; import io.undertow.websockets.WebSocketConnectionCallback; import io.undertow.websockets.WebSocketProtocolHandshakeHandler; import io.undertow.websockets.core.AbstractReceiveListener; import io.undertow.websockets.core.BufferedTextMessage; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSockets; import io.undertow.websockets.extensions.PerMessageDeflateHandshake; import io.undertow.websockets.spi.WebSocketHttpExchange; import static io.undertow.Handlers.path; import static io.undertow.Handlers.resource; /** * @author Stuart Douglas */ @UndertowExample("Web Socket Extensions") public class WebSocketServer { public static void main(final String[] args) { // Demonstrates how to use Websocket Protocol Handshake to enable Per-message deflate Undertow server = Undertow.builder() .addHttpListener(8080, "localhost") .setHandler(path() .addPrefixPath("/myapp", new WebSocketProtocolHandshakeHandler(new WebSocketConnectionCallback() { @Override public void onConnect(WebSocketHttpExchange exchange, WebSocketChannel channel) { channel.getReceiveSetter().set(new AbstractReceiveListener() { @Override protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage message) { WebSockets.sendText(message.getData(), channel, null); } }); channel.resumeReceives(); } }).addExtension(new PerMessageDeflateHandshake(false, 6))) .addPrefixPath("/", resource(new ClassPathResourceManager(WebSocketServer.class.getClassLoader(), WebSocketServer.class.getPackage())).addWelcomeFiles("index.html"))) .build(); server.start(); } } undertow-2.2.16.Final/examples/src/main/java/io/undertow/examples/websockets_extension/index.html000066400000000000000000000033021420065311100333000ustar00rootroot00000000000000 Web Socket Test
    undertow-2.2.16.Final/examples/src/main/resources/000077500000000000000000000000001420065311100220355ustar00rootroot00000000000000undertow-2.2.16.Final/examples/src/main/resources/logging.properties000066400000000000000000000030661420065311100256060ustar00rootroot00000000000000 # # JBoss, Home of Professional Open Source. # Copyright 2012 Red Hat, Inc., and individual contributors # as indicated by the @author tags. # # 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. # # Additional logger names to configure (root logger is always configured) loggers=org.xnio.listener,org.xnio.ssl,org.apache,io.undertow.util.TestHttpClient # Root logger configuration logger.level=${test.level:ERROR} logger.handlers=CONSOLE # Console handler configuration handler.CONSOLE=org.jboss.logmanager.handlers.ConsoleHandler handler.CONSOLE.properties=autoFlush,target handler.CONSOLE.target=SYSTEM_ERR handler.CONSOLE.level=ALL handler.CONSOLE.autoFlush=true handler.CONSOLE.formatter=PATTERN # The log format pattern formatter.PATTERN=org.jboss.logmanager.formatters.PatternFormatter formatter.PATTERN.properties=pattern formatter.PATTERN.pattern=%d{HH:mm:ss,SSS} %-5p (%t) [%c] <%F:%L> %m%n logger.org.xnio.listener.level=DEBUG logger.org.xnio.ssl.level=DEBUG logger.org.apache.level=WARN logger.org.apache.useParentHandlers=false logger.io.undertow.util.TestHttpClient.level=WARN undertow-2.2.16.Final/jakarta/000077500000000000000000000000001420065311100161075ustar00rootroot00000000000000undertow-2.2.16.Final/jakarta/examples/000077500000000000000000000000001420065311100177255ustar00rootroot00000000000000undertow-2.2.16.Final/jakarta/examples/pom.xml000066400000000000000000000110121420065311100212350ustar00rootroot00000000000000 4.0.0 io.undertow undertow-parent-jakarta 2.2.16.Final ../pom.xml undertow-examples-jakarta Undertow Examples - Jakarta Variant ${project.basedir}/../../examples io.undertow undertow-core io.undertow undertow-servlet-jakarta io.undertow undertow-websockets-jsr-jakarta org.jboss.logging jboss-logging-processor provided org.jboss.xnio xnio-nio org.jboss.threads jboss-threads org.jboss.logmanager jboss-logmanager undertow-examples src/main/java **/*.java src/main/resources org.apache.maven.plugins maven-shade-plugin package shade org.apache.maven.plugins maven-jar-plugin io.undertow.examples.Runner org.codehaus.mojo exec-maven-plugin 1.2.1 java -jar target/${project.build.finalName}.jar org.wildfly.extras.batavia transformer-tools-mvn undertow-2.2.16.Final/jakarta/pom.xml000066400000000000000000000031461420065311100174300ustar00rootroot00000000000000 4.0.0 io.undertow undertow-parent 2.2.16.Final ../pom.xml undertow-parent-jakarta Undertow Jakarta Parent pom ../../spotbugs-exclude.xml examples servlet websockets-jsr undertow-2.2.16.Final/jakarta/servlet/000077500000000000000000000000001420065311100175735ustar00rootroot00000000000000undertow-2.2.16.Final/jakarta/servlet/pom.xml000066400000000000000000000457321420065311100211230ustar00rootroot00000000000000 4.0.0 io.undertow undertow-parent-jakarta 2.2.16.Final ../pom.xml undertow-servlet-jakarta 2.2.16.Final Undertow Servlet - Jakarta Variant ${project.basedir}/../../servlet INFO false false false false 8192 io.undertow undertow-core org.jboss.logging jboss-logging-processor provided jakarta.servlet jakarta.servlet-api jakarta.annotation jakarta.annotation-api io.netty netty-all test io.undertow undertow-core test-jar test org.jboss.xnio xnio-nio test junit junit test org.apache.httpcomponents httpclient test org.jboss.logmanager jboss-logmanager test org.apache.httpcomponents httpmime test org.wildfly.openssl wildfly-openssl ${version.org.wildfly.openssl} test target/generated-test-resources/transformed target/generated-test-sources/transformed **/*.java org.bitstrings.maven.plugins dependencypath-maven-plugin 1.1.1 set-all set org.apache.felix maven-bundle-plugin generate-manifest manifest io.undertow.servlet*;version=${project.version};-noimport:=true javax.annotation.security;version="[1.2,3)", !sun.*, * org.apache.maven.plugins maven-jar-plugin test-jar test-jar tests ${project.build.outputDirectory}/META-INF/MANIFEST.MF org.apache.maven.plugins maven-surefire-plugin true reversealphabetical ${ajp} ${proxy} false ${openssl} ${dump} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${org.wildfly.openssl.path} ${jacoco.agent.argLine} ${surefire.system.args} org.wildfly.extras.batavia transformer-tools-mvn mac mac /usr/local/opt/openssl/lib proxy org.apache.maven.plugins maven-surefire-plugin proxy test test true reversealphabetical true ${dump} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${project.build.directory}/surefire-proxy-reports proxy-ajp test test true reversealphabetical true ${dump} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${project.build.directory}/surefire-ajp-reports proxy-https test test true reversealphabetical true ${dump} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${project.build.directory}/surefire-https-reports proxy-h2 test test true reversealphabetical true ${dump} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${project.build.directory}/surefire-h2c-reports proxy-h2-upgrade test test true reversealphabetical true ${dump} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${project.build.directory}/surefire-h2c-upgrade-reports proxy-h2c test test true reversealphabetical true ${dump} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${project.build.directory}/surefire-h2c-reports undertow-2.2.16.Final/jakarta/websockets-jsr/000077500000000000000000000000001420065311100210545ustar00rootroot00000000000000undertow-2.2.16.Final/jakarta/websockets-jsr/pom.xml000066400000000000000000000256241420065311100224020ustar00rootroot00000000000000 4.0.0 io.undertow undertow-parent-jakarta 2.2.16.Final ../pom.xml undertow-websockets-jsr-jakarta Undertow WebSockets JSR356 implementations - Jakarta Variant ${project.basedir}/../../websockets-jsr INFO 7777 false false io.undertow undertow-core io.undertow undertow-servlet-jakarta jakarta.websocket jakarta.websocket-api org.jboss.logging jboss-logging-processor provided io.undertow undertow-core test-jar test io.undertow undertow-servlet-jakarta test-jar test org.jboss.xnio xnio-nio test junit junit test io.netty netty-all test org.jboss.logmanager jboss-logmanager test org.apache.httpcomponents httpclient test org.wildfly.openssl wildfly-openssl ${version.org.wildfly.openssl} test target/generated-test-resources/transformed target/generated-test-sources/transformed **/*.java org.apache.felix maven-bundle-plugin generate-manifest manifest io.undertow.websockets.jsr*;version=${project.version};-noimport:=true !sun.*, * org.apache.maven.plugins maven-jar-plugin ${project.build.outputDirectory}/META-INF/MANIFEST.MF org.apache.maven.plugins maven-surefire-plugin true reversealphabetical ${skipWebSocketTests} true ${proxy} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${org.wildfly.openssl.path} ${surefire.system.args} ${jacoco.agent.argLine} org.wildfly.extras.batavia transformer-tools-mvn org.apache.maven.plugins maven-antrun-plugin generate-resources run mac mac /usr/local/opt/openssl/lib spdy-no-tests test.spdy true true h2-no-tests test.h2 true true autobahn autobahn me.normanmaurer.maven.autobahntestsuite autobahntestsuite-maven-plugin 0.1.3 127.0.0.1 7777 io.undertow.websockets.jsr.test.autobahn.AnnotatedAutobahnServer * false true test fuzzingclient undertow-2.2.16.Final/karaf/000077500000000000000000000000001420065311100155565ustar00rootroot00000000000000undertow-2.2.16.Final/karaf/pom.xml000066400000000000000000000125501420065311100170760ustar00rootroot00000000000000 4.0.0 io.undertow undertow-parent 2.2.16.Final io.undertow karaf 2.2.16.Final Undertow Karaf feature pom 4.2.10 org.apache.karaf.features framework kar provided ${version.karaf.plugin} * * src/main/resources true org.apache.karaf.tooling karaf-maven-plugin ${version.karaf.plugin} org.apache.maven.plugins maven-resources-plugin default generate-resources resources org.apache.karaf.tooling karaf-maven-plugin verify process-resources verify mvn:org.apache.karaf.features/framework/${version.karaf.plugin}/xml/features mvn:org.apache.karaf.features/standard/${version.karaf.plugin}/xml/features file:${project.build.directory}/classes/features.xml org.apache.karaf.features:framework 1.8 framework undertow org.codehaus.mojo build-helper-maven-plugin attach-artifacts package attach-artifact target/classes/features.xml xml features undertow-2.2.16.Final/karaf/src/000077500000000000000000000000001420065311100163455ustar00rootroot00000000000000undertow-2.2.16.Final/karaf/src/main/000077500000000000000000000000001420065311100172715ustar00rootroot00000000000000undertow-2.2.16.Final/karaf/src/main/resources/000077500000000000000000000000001420065311100213035ustar00rootroot00000000000000undertow-2.2.16.Final/karaf/src/main/resources/features.xml000066400000000000000000000043521420065311100236470ustar00rootroot00000000000000 mvn:org.jboss.spec.javax.annotation/jboss-annotations-api_1.3_spec/${version.org.jboss.spec.javax.annotation.jboss-annotations-api_1.3_spec} mvn:org.jboss.spec.javax.servlet/jboss-servlet-api_4.0_spec/${version.org.jboss.spec.javax.servlet.jboss-servlet-api_4.0_spec} mvn:org.jboss.spec.javax.websocket/jboss-websocket-api_1.1_spec/${version.org.jboss.spec.javax.websockets} mvn:org.jboss.xnio/xnio-api/${version.xnio} mvn:org.jboss.xnio/xnio-nio/${version.xnio} mvn:io.undertow/undertow-core/${project.version} mvn:io.undertow/undertow-servlet/${project.version} mvn:io.undertow/undertow-websockets-jsr/${project.version} wrap:mvn:org.jboss.threads/jboss-threads/${version.org.jboss.threads} wrap:mvn:org.wildfly.common/wildfly-common/${version.org.wildfly.common}$Export-Package=org.wildfly.common.*;-noimport:=true;version="${version.org.wildfly.common}" mvn:org.wildfly.client/wildfly-client-config/${version.org.wildfly.client-config} undertow-2.2.16.Final/parser-generator/000077500000000000000000000000001420065311100177525ustar00rootroot00000000000000undertow-2.2.16.Final/parser-generator/pom.xml000066400000000000000000000042021420065311100212650ustar00rootroot00000000000000 4.0.0 io.undertow undertow-parent 2.2.16.Final io.undertow undertow-parser-generator 2.2.16.Final Undertow Parser Generator An annotation processor that is used to generate the HTTP parser org.jboss.classfilewriter jboss-classfilewriter junit junit test org.apache.maven.plugins maven-compiler-plugin -proc:none undertow-2.2.16.Final/parser-generator/src/000077500000000000000000000000001420065311100205415ustar00rootroot00000000000000undertow-2.2.16.Final/parser-generator/src/main/000077500000000000000000000000001420065311100214655ustar00rootroot00000000000000undertow-2.2.16.Final/parser-generator/src/main/java/000077500000000000000000000000001420065311100224065ustar00rootroot00000000000000undertow-2.2.16.Final/parser-generator/src/main/java/io/000077500000000000000000000000001420065311100230155ustar00rootroot00000000000000undertow-2.2.16.Final/parser-generator/src/main/java/io/undertow/000077500000000000000000000000001420065311100246645ustar00rootroot00000000000000undertow-2.2.16.Final/parser-generator/src/main/java/io/undertow/annotationprocessor/000077500000000000000000000000001420065311100307765ustar00rootroot00000000000000AbstractParserGenerator.java000066400000000000000000000755601420065311100363660ustar00rootroot00000000000000undertow-2.2.16.Final/parser-generator/src/main/java/io/undertow/annotationprocessor/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.annotationprocessor; import org.jboss.classfilewriter.AccessFlag; import org.jboss.classfilewriter.ClassFile; import org.jboss.classfilewriter.ClassMethod; import org.jboss.classfilewriter.code.BranchEnd; import org.jboss.classfilewriter.code.CodeAttribute; import org.jboss.classfilewriter.code.CodeLocation; import org.jboss.classfilewriter.code.TableSwitchBuilder; import org.jboss.classfilewriter.util.DescriptorUtils; import java.lang.reflect.Modifier; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; /** * @author Stuart Douglas */ public abstract class AbstractParserGenerator { public static final String BAD_REQUEST_EXCEPTION = "io.undertow.util.BadRequestException"; //class names protected final String parseStateClass; protected String resultClass; protected final String constructorDescriptor; private final String parseStateDescriptor; private final String httpExchangeDescriptor; private final String existingClassName; public static final String HTTP_STRING_CLASS = "io.undertow.util.HttpString"; public static final String HTTP_STRING_DESCRIPTOR = DescriptorUtils.makeDescriptor(HTTP_STRING_CLASS); //state machine states public static final int NO_STATE = -1; public static final int PREFIX_MATCH = -2; private static final int CONSTRUCTOR_HTTP_STRING_MAP_VAR = 1; protected static final int BYTE_BUFFER_VAR = 1; protected static final int PARSE_STATE_VAR = 2; protected static final int HTTP_RESULT = 3; protected static final int CURRENT_STATE_VAR = 4; protected static final int STATE_POS_VAR = 5; protected static final int STATE_CURRENT_VAR = 6; protected static final int STATE_STRING_BUILDER_VAR = 7; protected static final int STATE_CURRENT_BYTES_VAR = 8; public static final String HANDLE_HTTP_VERB = "handleHttpVerb"; public static final String HANDLE_PATH = "handlePath"; public static final String HANDLE_HTTP_VERSION = "handleHttpVersion"; public static final String HANDLE_AFTER_VERSION = "handleAfterVersion"; public static final String HANDLE_HEADER = "handleHeader"; public static final String HANDLE_HEADER_VALUE = "handleHeaderValue"; public static final String CLASS_NAME_SUFFIX = "$$generated"; public AbstractParserGenerator(final String parseStateClass, final String resultClass, final String constructorDescriptor, String existingClassName) { this.parseStateClass = parseStateClass; this.resultClass = resultClass; this.existingClassName = existingClassName; parseStateDescriptor = DescriptorUtils.makeDescriptor(parseStateClass); httpExchangeDescriptor = DescriptorUtils.makeDescriptor(resultClass); this.constructorDescriptor = constructorDescriptor; } public byte[] createTokenizer(final String[] httpVerbs, String[] httpVersions, String[] standardHeaders) { final String className = existingClassName + CLASS_NAME_SUFFIX; final ClassFile file = new ClassFile(className, existingClassName); final ClassMethod ctor = file.addMethod(AccessFlag.PUBLIC, "", "V", DescriptorUtils.parameterDescriptors(constructorDescriptor)); ctor.getCodeAttribute().aload(0); ctor.getCodeAttribute().loadMethodParameters(); ctor.getCodeAttribute().invokespecial(existingClassName, "", constructorDescriptor); ctor.getCodeAttribute().returnInstruction(); final ClassMethod sctor = file.addMethod(AccessFlag.PUBLIC | AccessFlag.STATIC, "", "V"); final AtomicInteger fieldCounter = new AtomicInteger(1); sctor.getCodeAttribute().invokestatic(existingClassName, "httpStrings", "()" + DescriptorUtils.makeDescriptor(Map.class)); sctor.getCodeAttribute().astore(CONSTRUCTOR_HTTP_STRING_MAP_VAR); createStateMachines(httpVerbs, httpVersions, standardHeaders, className, file, sctor, fieldCounter); sctor.getCodeAttribute().returnInstruction(); return file.toBytecode(); } protected abstract void createStateMachines(String[] httpVerbs, String[] httpVersions, String[] standardHeaders, String className, ClassFile file, ClassMethod sctor, AtomicInteger fieldCounter); protected void createStateMachine(final String[] originalItems, final String className, final ClassFile file, final ClassMethod sctor, final AtomicInteger fieldCounter, final String methodName, final CustomStateMachine stateMachine, boolean expectNewline) { //list of all states except the initial final List allStates = new ArrayList(); final State initial = new State((byte) 0, ""); for (String value : originalItems) { addStates(initial, value, allStates); } //we want initial to be number 0 final AtomicInteger stateCounter = new AtomicInteger(-1); setupStateNo(initial, stateCounter, fieldCounter); for (State state : allStates) { setupStateNo(state, stateCounter, fieldCounter); createStateField(state, file, sctor.getCodeAttribute()); } final int noStates = stateCounter.get(); final ClassMethod handle = file.addMethod(Modifier.PROTECTED | Modifier.FINAL, methodName, "V", DescriptorUtils.makeDescriptor(ByteBuffer.class), parseStateDescriptor, httpExchangeDescriptor); handle.addCheckedExceptions(BAD_REQUEST_EXCEPTION); writeStateMachine(className, file, handle.getCodeAttribute(), initial, allStates, noStates, stateMachine, expectNewline); } private void createStateField(final State state, final ClassFile file, final CodeAttribute sc) { if (state.fieldName != null) { file.addField(AccessFlag.STATIC | AccessFlag.FINAL | AccessFlag.PRIVATE, state.fieldName, "[B"); sc.ldc(state.terminalState); sc.ldc("ISO-8859-1"); sc.invokevirtual(String.class.getName(), "getBytes", "(Ljava/lang/String;)[B"); sc.putstatic(file.getName(), state.fieldName, "[B"); } if (state.httpStringFieldName != null) { file.addField(AccessFlag.STATIC | AccessFlag.FINAL | AccessFlag.PRIVATE, state.httpStringFieldName, HTTP_STRING_DESCRIPTOR); //first we try and get the string from the map of known HTTP strings //this means that the result we store will be the same object as the //constants that are referenced in the handlers //if this fails we just create a new http string sc.aload(CONSTRUCTOR_HTTP_STRING_MAP_VAR); if (state.terminalState != null) { sc.ldc(state.terminalState); } else { sc.ldc(state.soFar); } sc.invokeinterface(Map.class.getName(), "get", "(Ljava/lang/Object;)Ljava/lang/Object;"); sc.dup(); BranchEnd end = sc.ifnull(); sc.checkcast(HTTP_STRING_CLASS); sc.putstatic(file.getName(), state.httpStringFieldName, HTTP_STRING_DESCRIPTOR); BranchEnd done = sc.gotoInstruction(); sc.branchEnd(end); sc.pop(); sc.newInstruction(HTTP_STRING_CLASS); sc.dup(); if (state.terminalState != null) { sc.ldc(state.terminalState); } else { sc.ldc(state.soFar); } sc.invokespecial(HTTP_STRING_CLASS, "", "(Ljava/lang/String;)V"); sc.putstatic(file.getName(), state.httpStringFieldName, HTTP_STRING_DESCRIPTOR); sc.branchEnd(done); } } private void setupStateNo(final State state, final AtomicInteger stateCounter, final AtomicInteger fieldCounter) { if (state.next.isEmpty()) { state.stateno = PREFIX_MATCH; state.terminalState = state.soFar; state.fieldName = "STATE_BYTES_" + fieldCounter.incrementAndGet(); } else if (state.next.size() == 1) { String terminal = null; State s = state.next.values().iterator().next(); while (true) { if (s.next.size() > 1) { break; } else if (s.next.isEmpty()) { terminal = s.soFar; break; } s = s.next.values().iterator().next(); } if (terminal != null) { state.stateno = PREFIX_MATCH; state.terminalState = terminal; state.fieldName = "STATE_BYTES_" + fieldCounter.incrementAndGet(); } else { state.stateno = stateCounter.incrementAndGet(); } } else { state.stateno = stateCounter.incrementAndGet(); } state.httpStringFieldName = "HTTP_STRING_" + fieldCounter.incrementAndGet(); } private void writeStateMachine(final String className, final ClassFile file, final CodeAttribute c, final State initial, final List allStates, int noStates, final CustomStateMachine stateMachine, boolean expectNewline) { //initial hasRemaining check c.aload(BYTE_BUFFER_VAR); c.invokevirtual(ByteBuffer.class.getName(), "hasRemaining", "()Z"); final BranchEnd nonZero = c.ifne(); //we have run out of bytes, return 0 c.iconst(0); c.returnInstruction(); c.branchEnd(nonZero); final List states = new ArrayList(); states.add(initial); states.addAll(allStates); Collections.sort(states); //store the current state in a local variable c.aload(PARSE_STATE_VAR); c.dup(); c.getfield(parseStateClass, "stringBuilder", DescriptorUtils.makeDescriptor(StringBuilder.class)); c.astore(STATE_STRING_BUILDER_VAR); c.dup(); c.getfield(parseStateClass, "parseState", "I"); c.dup(); c.istore(CURRENT_STATE_VAR); //if this is state 0 there is a lot of stuff can ignore BranchEnd optimizationEnd = c.ifeq(); c.dup(); c.getfield(parseStateClass, "pos", "I"); c.istore(STATE_POS_VAR); c.dup(); c.getfield(parseStateClass, "current", HTTP_STRING_DESCRIPTOR); c.astore(STATE_CURRENT_VAR); c.getfield(parseStateClass, "currentBytes", "[B"); c.astore(STATE_CURRENT_BYTES_VAR); //load the current state c.iload(CURRENT_STATE_VAR); //switch on the current state TableSwitchBuilder builder = new TableSwitchBuilder(-2, noStates); final IdentityHashMap> ends = new IdentityHashMap>(); final AtomicReference prefixMatch = builder.add(); final AtomicReference noState = builder.add(); ends.put(initial, builder.add()); for (final State s : states) { if (s.stateno > 0) { ends.put(s, builder.add()); } } c.tableswitch(builder); stateNotFound(c, builder); //return code //code that synchronizes the state object and returns setupLocalVariables(c); final CodeLocation returnIncompleteCode = c.mark(); c.aload(PARSE_STATE_VAR); c.dup(); c.dup(); c.dup(); c.dup(); c.iload(STATE_POS_VAR); c.putfield(parseStateClass, "pos", "I"); c.aload(STATE_CURRENT_VAR); c.putfield(parseStateClass, "current", HTTP_STRING_DESCRIPTOR); c.aload(STATE_CURRENT_BYTES_VAR); c.putfield(parseStateClass, "currentBytes", "[B"); c.iload(CURRENT_STATE_VAR); c.putfield(parseStateClass, "parseState", "I"); c.returnInstruction(); setupLocalVariables(c); final CodeLocation returnCompleteCode = c.mark(); c.aload(PARSE_STATE_VAR); c.dup(); c.dup(); c.dup(); c.dup(); c.iconst(0); c.putfield(parseStateClass, "pos", "I"); c.aconstNull(); c.putfield(parseStateClass, "current", HTTP_STRING_DESCRIPTOR); c.aconstNull(); c.putfield(parseStateClass, "currentBytes", "[B"); c.aload(STATE_STRING_BUILDER_VAR); c.iconst(0); c.invokevirtual(StringBuilder.class.getName(), "setLength", "(I)V"); c.iconst(0); c.putfield(parseStateClass, "parseState", "I"); c.returnInstruction(); //prefix c.branchEnd(prefixMatch.get()); final CodeLocation prefixLoop = c.mark(); //loop for when we are prefix matching handleReturnIfNoMoreBytes(c, returnIncompleteCode); //load 3 copies of the current byte into the stack c.aload(BYTE_BUFFER_VAR); c.invokevirtual(ByteBuffer.class.getName(), "get", "()B"); c.dup(); c.dup(); final Set prefixHandleSpace = new LinkedHashSet(); final Set badPrefixHandleSpace = new LinkedHashSet(); if (stateMachine.isHeader()) { c.iconst(':'); prefixHandleSpace.add(c.ifIcmpeq()); c.dup(); c.iconst(' '); badPrefixHandleSpace.add(c.ifIcmpeq()); c.dup(); c.iconst('\r'); prefixHandleSpace.add(c.ifIcmpeq()); c.dup(); c.iconst('\n'); prefixHandleSpace.add(c.ifIcmpeq()); }else if(!expectNewline) { c.iconst(' '); prefixHandleSpace.add(c.ifIcmpeq()); c.dup(); c.iconst('\r'); badPrefixHandleSpace.add(c.ifIcmpeq()); c.dup(); c.iconst('\n'); badPrefixHandleSpace.add(c.ifIcmpeq()); } else { c.iconst('\r'); prefixHandleSpace.add(c.ifIcmpeq()); c.dup(); c.iconst('\n'); prefixHandleSpace.add(c.ifIcmpeq()); } //check if we have overrun c.aload(STATE_CURRENT_BYTES_VAR); c.arraylength(); c.iload(STATE_POS_VAR); BranchEnd overrun = c.ifIcmpeq(); //so we have not overrun //now check if the character matches c.dup(); c.aload(STATE_CURRENT_BYTES_VAR); c.iload(STATE_POS_VAR); c.baload(); c.isub(); BranchEnd noMatch = c.ifne(); //so they match c.pop2(); //pop our extra bytes off the stack, we do not need it c.iinc(STATE_POS_VAR, 1); handleReturnIfNoMoreBytes(c, returnIncompleteCode); c.gotoInstruction(prefixLoop); c.branchEnd(overrun); //overrun and not match use the same code path c.branchEnd(noMatch); //the current character did not match c.iconst(NO_STATE); c.istore(CURRENT_STATE_VAR); //create the string builder c.aload(STATE_STRING_BUILDER_VAR); c.aload(STATE_CURRENT_VAR); c.invokevirtual(HTTP_STRING_CLASS, "toString", "()Ljava/lang/String;"); c.iconst(0); c.iload(STATE_POS_VAR); c.invokevirtual(String.class.getName(), "substring", "(II)Ljava/lang/String;"); c.invokevirtual(StringBuilder.class.getName(), "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;"); c.swap(); c.invokevirtual(StringBuilder.class.getName(), "append", "(C)Ljava/lang/StringBuilder;"); c.pop2(); BranchEnd prefixToNoState = c.gotoInstruction(); if(!badPrefixHandleSpace.isEmpty()) { //handle the space case for (BranchEnd b : badPrefixHandleSpace) { c.branchEnd(b); } c.newInstruction(BAD_REQUEST_EXCEPTION); c.dup(); c.invokespecial(BAD_REQUEST_EXCEPTION, "", "()V"); c.athrow(); } //handle the space case for (BranchEnd b : prefixHandleSpace) { c.branchEnd(b); } //new state will be 0 c.iconst(0); c.istore(CURRENT_STATE_VAR); c.aload(STATE_CURRENT_BYTES_VAR); c.arraylength(); c.iload(STATE_POS_VAR); BranchEnd correctLength = c.ifIcmpeq(); c.newInstruction(HTTP_STRING_CLASS); c.dup(); c.aload(STATE_CURRENT_BYTES_VAR); c.iconst(0); c.iload(STATE_POS_VAR); c.invokespecial(HTTP_STRING_CLASS, "", "([BII)V"); stateMachine.handleOtherToken(c); //TODO: exit if it returns null //decrease the available bytes c.pop(); tokenDone(c, returnCompleteCode, stateMachine); c.branchEnd(correctLength); c.aload(STATE_CURRENT_VAR); stateMachine.handleStateMachineMatchedToken(c); //TODO: exit if it returns null c.pop(); tokenDone(c, returnCompleteCode, stateMachine); //nostate c.branchEnd(noState.get()); c.branchEnd(prefixToNoState); CodeLocation noStateLoop = c.mark(); handleReturnIfNoMoreBytes(c, returnIncompleteCode); //load 2 copies of the current byte into the stack c.aload(BYTE_BUFFER_VAR); c.invokevirtual(ByteBuffer.class.getName(), "get", "()B"); c.dup(); final Set nostateHandleSpace = new LinkedHashSet(); final Set badNostateHandleSpace = new LinkedHashSet(); if (stateMachine.isHeader()) { c.iconst(':'); nostateHandleSpace.add(c.ifIcmpeq()); c.dup(); c.iconst(' '); nostateHandleSpace.add(c.ifIcmpeq()); c.dup(); c.iconst('\r'); nostateHandleSpace.add(c.ifIcmpeq()); c.dup(); c.iconst('\n'); nostateHandleSpace.add(c.ifIcmpeq()); } else if(!expectNewline) { c.iconst(' '); nostateHandleSpace.add(c.ifIcmpeq()); c.dup(); c.iconst('\r'); badNostateHandleSpace.add(c.ifIcmpeq()); c.dup(); c.iconst('\n'); badNostateHandleSpace.add(c.ifIcmpeq()); } else { c.iconst('\r'); nostateHandleSpace.add(c.ifIcmpeq()); c.dup(); c.iconst('\n'); nostateHandleSpace.add(c.ifIcmpeq()); } c.aload(STATE_STRING_BUILDER_VAR); c.swap(); c.invokevirtual(StringBuilder.class.getName(), "append", "(C)Ljava/lang/StringBuilder;"); c.pop(); c.aload(BYTE_BUFFER_VAR); c.invokevirtual(ByteBuffer.class.getName(), "hasRemaining", "()Z"); c.ifne(noStateLoop); //go back to the start if we have not run out of bytes //we have run out of bytes, so we need to write back the current state c.aload(PARSE_STATE_VAR); c.iload(CURRENT_STATE_VAR); c.putfield(parseStateClass, "parseState", "I"); c.iconst(0); c.returnInstruction(); if(!badNostateHandleSpace.isEmpty()) { //handle the space case for (BranchEnd b : badNostateHandleSpace) { c.branchEnd(b); } c.newInstruction(BAD_REQUEST_EXCEPTION); c.dup(); c.invokespecial(BAD_REQUEST_EXCEPTION, "", "()V"); c.athrow(); } for (BranchEnd b : nostateHandleSpace) { c.branchEnd(b); } c.aload(STATE_STRING_BUILDER_VAR); c.invokevirtual(StringBuilder.class.getName(), "toString", "()Ljava/lang/String;"); c.newInstruction(HTTP_STRING_CLASS); c.dupX1(); c.swap(); c.invokespecial(HTTP_STRING_CLASS, "", "(Ljava/lang/String;)V"); stateMachine.handleOtherToken(c); //TODO: exit if it returns null tokenDone(c, returnCompleteCode, stateMachine); c.branchEnd(optimizationEnd); c.pop(); c.iconst(0); c.istore(STATE_POS_VAR); c.aconstNull(); c.astore(STATE_CURRENT_VAR); c.aconstNull(); c.astore(STATE_CURRENT_BYTES_VAR); c.branchEnd(ends.get(initial).get()); invokeState(className, file, c, initial, initial, noStateLoop, prefixLoop, returnIncompleteCode, returnCompleteCode, stateMachine, expectNewline); for (final State s : allStates) { if (s.stateno >= 0) { c.branchEnd(ends.get(s).get()); invokeState(className, file, c, s, initial, noStateLoop, prefixLoop, returnIncompleteCode, returnCompleteCode, stateMachine, expectNewline); } } } private void setupLocalVariables(final CodeAttribute c) { c.setupFrame(DescriptorUtils.makeDescriptor(existingClassName + CLASS_NAME_SUFFIX), DescriptorUtils.makeDescriptor(ByteBuffer.class), parseStateDescriptor, httpExchangeDescriptor, "I", "I", HTTP_STRING_DESCRIPTOR, DescriptorUtils.makeDescriptor(StringBuilder.class), "[B"); } private void handleReturnIfNoMoreBytes(final CodeAttribute c, final CodeLocation returnCode) { c.aload(BYTE_BUFFER_VAR); c.invokevirtual(ByteBuffer.class.getName(), "hasRemaining", "()Z"); c.ifEq(returnCode); //go back to the start if we have not run out of bytes } private void tokenDone(final CodeAttribute c, final CodeLocation returnCode, final CustomStateMachine stateMachine) { stateMachine.updateParseState(c); c.gotoInstruction(returnCode); } private void invokeState(final String className, final ClassFile file, final CodeAttribute c, final State currentState, final State initialState, final CodeLocation noStateStart, final CodeLocation prefixStart, final CodeLocation returnIncompleteCode, final CodeLocation returnCompleteCode, final CustomStateMachine stateMachine, boolean expectNewline) { currentState.mark(c); BranchEnd parseDone = null; if (currentState == initialState) { //if this is the initial state there is a possibility that we need to deal with a left over character first //we need to see if we start with a left over character c.aload(PARSE_STATE_VAR); c.getfield(parseStateClass, "leftOver", "B"); c.dup(); final BranchEnd end = c.ifne(); c.pop(); //load 2 copies of the current byte into the stack handleReturnIfNoMoreBytes(c, returnIncompleteCode); c.aload(BYTE_BUFFER_VAR); c.invokevirtual(ByteBuffer.class.getName(), "get", "()B"); BranchEnd cont = c.gotoInstruction(); c.branchEnd(end); c.aload(PARSE_STATE_VAR); c.iconst(0); c.putfield(parseStateClass, "leftOver", "B"); c.branchEnd(cont); } else { handleReturnIfNoMoreBytes(c, returnIncompleteCode); //load 2 copies of the current byte into the stack c.aload(BYTE_BUFFER_VAR); c.invokevirtual(ByteBuffer.class.getName(), "get", "()B"); } c.dup(); final Set tokenEnds = new LinkedHashSet<>(); final Set badTokenEnds = new LinkedHashSet<>(); final Map ends = new IdentityHashMap(); for (State state : currentState.next.values()) { c.iconst(state.value); ends.put(state, c.ifIcmpeq()); c.dup(); } if (stateMachine.isHeader()) { c.iconst(':'); tokenEnds.add(c.ifIcmpeq()); c.dup(); c.iconst('\r'); tokenEnds.add(c.ifIcmpeq()); c.dup(); c.iconst('\n'); tokenEnds.add(c.ifIcmpeq()); c.dup(); c.iconst(' '); tokenEnds.add(c.ifIcmpeq()); }else if (expectNewline) { c.iconst('\r'); tokenEnds.add(c.ifIcmpeq()); c.dup(); c.iconst('\n'); tokenEnds.add(c.ifIcmpeq()); } else { c.iconst(' '); tokenEnds.add(c.ifIcmpeq()); c.dup(); c.iconst('\r'); badTokenEnds.add(c.ifIcmpeq()); c.dup(); c.iconst('\n'); badTokenEnds.add(c.ifIcmpeq()); } c.iconst(NO_STATE); c.istore(CURRENT_STATE_VAR); //create the string builder c.aload(STATE_STRING_BUILDER_VAR); c.ldc(currentState.soFar); c.invokevirtual(StringBuilder.class.getName(), "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;"); c.swap(); c.invokevirtual(StringBuilder.class.getName(), "append", "(C)Ljava/lang/StringBuilder;"); c.pop(); c.gotoInstruction(noStateStart); if(!badTokenEnds.isEmpty()) { //handle the space case for (BranchEnd b : badTokenEnds) { c.branchEnd(b); } c.newInstruction(BAD_REQUEST_EXCEPTION); c.dup(); c.invokespecial(BAD_REQUEST_EXCEPTION, "", "()V"); c.athrow(); } //now we write out tokenEnd for (BranchEnd tokenEnd : tokenEnds) { c.branchEnd(tokenEnd); } if (!currentState.soFar.isEmpty()) { c.getstatic(file.getName(), currentState.httpStringFieldName, HTTP_STRING_DESCRIPTOR); stateMachine.handleStateMachineMatchedToken(c); //TODO: exit if it returns null tokenDone(c, returnCompleteCode, stateMachine); } else { if (stateMachine.initialNewlineMeansRequestDone()) { c.iconst('\n'); parseDone = c.ifIcmpeq(); } else { c.pop(); } setupLocalVariables(c); handleReturnIfNoMoreBytes(c, returnIncompleteCode); initialState.jumpTo(c); } for (Map.Entry e : ends.entrySet()) { c.branchEnd(e.getValue()); c.pop(); final State state = e.getKey(); if (state.stateno < 0) { //prefix match c.iconst(state.stateno); c.istore(CURRENT_STATE_VAR); c.getstatic(className, state.httpStringFieldName, HTTP_STRING_DESCRIPTOR); c.astore(STATE_CURRENT_VAR); c.getstatic(className, state.fieldName, "[B"); c.astore(STATE_CURRENT_BYTES_VAR); c.iconst(state.soFar.length()); c.istore(STATE_POS_VAR); c.gotoInstruction(prefixStart); } else { c.iconst(state.stateno); c.istore(CURRENT_STATE_VAR); state.jumpTo(c); } } if (parseDone != null) { c.branchEnd(parseDone); c.aload(PARSE_STATE_VAR); c.invokevirtual(parseStateClass, "parseComplete", "()V"); c.iconst(0); c.returnInstruction(); } } /** * Throws an exception when an invalid state is hit in a tableswitch */ private static void stateNotFound(final CodeAttribute c, final TableSwitchBuilder builder) { c.branchEnd(builder.getDefaultBranchEnd().get()); c.newInstruction(RuntimeException.class); c.dup(); c.ldc("Invalid character"); c.invokespecial(RuntimeException.class.getName(), "", "(Ljava/lang/String;)V"); c.athrow(); } private static void addStates(final State initial, final String value, final List allStates) { addStates(initial, value, 0, allStates); } private static void addStates(final State current, final String value, final int i, final List allStates) { if (i == value.length()) { return; } byte[] bytes = value.getBytes(StandardCharsets.UTF_8); final byte currentByte = bytes[i]; State newState = current.next.get(currentByte); if (newState == null) { current.next.put(currentByte, newState = new State(currentByte, value.substring(0, i + 1))); allStates.add(newState); } addStates(newState, value, i + 1, allStates); } private static class State implements Comparable { Integer stateno; String terminalState; String fieldName; String httpStringFieldName; final byte value; final String soFar; final Map next = new LinkedHashMap(); private final Set branchEnds = new LinkedHashSet(); private CodeLocation location; private State(final byte value, final String soFar) { this.value = value; this.soFar = soFar; } @Override public int hashCode() { return stateno.hashCode(); } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (!(obj instanceof State)) { return false; } State other = (State) obj; return stateno.equals(other.stateno); } @Override public int compareTo(final State o) { return stateno.compareTo(o.stateno); } void mark(final CodeAttribute ca) { location = ca.mark(); for (BranchEnd br : branchEnds) { ca.branchEnd(br); } } void jumpTo(final CodeAttribute ca) { if (location == null) { branchEnds.add(ca.gotoInstruction()); } else { ca.gotoInstruction(location); } } void ifne(final CodeAttribute ca) { if (location == null) { branchEnds.add(ca.ifne()); } else { ca.ifne(location); } } } /** * A class that separates out the different behaviour of the three state machines (VERB, VERSION and HEADER) */ public interface CustomStateMachine { boolean isHeader(); void handleStateMachineMatchedToken(CodeAttribute c); void handleOtherToken(CodeAttribute c); void updateParseState(CodeAttribute c); boolean initialNewlineMeansRequestDone(); } } HttpParserAnnotationProcessor.java000066400000000000000000000102031420065311100376050ustar00rootroot00000000000000undertow-2.2.16.Final/parser-generator/src/main/java/io/undertow/annotationprocessor/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.annotationprocessor; import java.io.IOException; import java.io.OutputStream; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Filer; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedOptions; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.tools.JavaFileObject; /** * @author Stuart Douglas */ @SupportedAnnotationTypes("io.undertow.annotationprocessor.HttpParserConfig") @SupportedOptions({ }) @SupportedSourceVersion(SourceVersion.RELEASE_8) public class HttpParserAnnotationProcessor extends AbstractProcessor { private Filer filer; @Override public void init(final ProcessingEnvironment processingEnv) { super.init(processingEnv); filer = processingEnv.getFiler(); } @Override public boolean process(final Set annotations, final RoundEnvironment roundEnv) { for (Element element : roundEnv.getElementsAnnotatedWith(HttpParserConfig.class)) { final HttpParserConfig parser = element.getAnnotation(HttpParserConfig.class); if (parser == null) { continue; } final RequestParserGenerator requestGenerator = new RequestParserGenerator(((TypeElement) element).getQualifiedName().toString()); final byte[] newClass = requestGenerator.createTokenizer(parser.methods(), parser.protocols(), parser.headers()); try { JavaFileObject file = filer.createClassFile(((TypeElement) element).getQualifiedName() + AbstractParserGenerator.CLASS_NAME_SUFFIX, element); final OutputStream out = file.openOutputStream(); try { out.write(newClass); } finally { try { out.close(); } catch (IOException e) { } } } catch (Exception e) { throw new RuntimeException(e); } } for (Element element : roundEnv.getElementsAnnotatedWith(HttpResponseParserConfig.class)) { ResponseParserGenerator responseGenerator = new ResponseParserGenerator(((TypeElement) element).getQualifiedName().toString()); final HttpResponseParserConfig parser = element.getAnnotation(HttpResponseParserConfig.class); if (parser == null) { continue; } final byte[] newClass = responseGenerator.createTokenizer(new String[0], parser.protocols(), parser.headers()); try { JavaFileObject file = filer.createClassFile(((TypeElement) element).getQualifiedName() + AbstractParserGenerator.CLASS_NAME_SUFFIX, element); final OutputStream out = file.openOutputStream(); try { out.write(newClass); } finally { try { out.close(); } catch (IOException e) { } } } catch (Exception e) { throw new RuntimeException(e); } } return true; } } HttpParserConfig.java000066400000000000000000000023111420065311100350010ustar00rootroot00000000000000undertow-2.2.16.Final/parser-generator/src/main/java/io/undertow/annotationprocessor/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.annotationprocessor; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * * If this annotation is applied to a class it will be replaced with a generated HTTP parser. * * @author Stuart Douglas */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface HttpParserConfig { String[] methods(); String[] protocols(); String[] headers(); } HttpResponseParserConfig.java000066400000000000000000000023301420065311100365210ustar00rootroot00000000000000undertow-2.2.16.Final/parser-generator/src/main/java/io/undertow/annotationprocessor/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.annotationprocessor; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * * If this annotation is applied to a class it will be replaced with a generated HTTP parser. * * @author Stuart Douglas * @author Emanuel Muckenhuber */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface HttpResponseParserConfig { String[] protocols(); String[] headers(); } RequestParserGenerator.java000066400000000000000000000140211420065311100362340ustar00rootroot00000000000000undertow-2.2.16.Final/parser-generator/src/main/java/io/undertow/annotationprocessor/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.annotationprocessor; import java.util.concurrent.atomic.AtomicInteger; import org.jboss.classfilewriter.ClassFile; import org.jboss.classfilewriter.ClassMethod; import org.jboss.classfilewriter.code.CodeAttribute; /** * @author Stuart Douglas */ public class RequestParserGenerator extends AbstractParserGenerator { public static final String PARSE_STATE_CLASS = "io.undertow.server.protocol.http.ParseState"; public static final String HTTP_EXCHANGE_CLASS = "io.undertow.server.HttpServerExchange"; public static final String HTTP_EXCHANGE_DESCRIPTOR = "Lio/undertow/server/HttpServerExchange;"; private static final String CONNECTORS_CLASS = "io.undertow.server.Connectors"; //parsing states public static final int VERB = 0; public static final int PATH = 1; public static final int PATH_PARAMETERS = 2; public static final int QUERY_STRING = 3; public static final int VERSION = 4; public static final int AFTER_VERSION = 5; public static final int HEADER = 6; public static final int HEADER_VALUE = 7; public RequestParserGenerator(String existingClassName) { super(PARSE_STATE_CLASS, HTTP_EXCHANGE_CLASS, "(Lorg/xnio/OptionMap;)V", existingClassName); } protected void createStateMachines(final String[] httpVerbs, final String[] httpVersions, final String[] standardHeaders, final String className, final ClassFile file, final ClassMethod sctor, final AtomicInteger fieldCounter) { createStateMachine(httpVerbs, className, file, sctor, fieldCounter, HANDLE_HTTP_VERB, new VerbStateMachine(), false); createStateMachine(httpVersions, className, file, sctor, fieldCounter, HANDLE_HTTP_VERSION, new VersionStateMachine(), true); createStateMachine(standardHeaders, className, file, sctor, fieldCounter, HANDLE_HEADER, new HeaderStateMachine(), false); } protected class HeaderStateMachine implements CustomStateMachine { @Override public boolean isHeader() { return true; } @Override public void handleOtherToken(final CodeAttribute c) { c.aload(PARSE_STATE_VAR); c.swap(); c.dup(); c.invokestatic(CONNECTORS_CLASS, "verifyToken", "(" + HTTP_STRING_DESCRIPTOR + ")V"); c.putfield(parseStateClass, "nextHeader", HTTP_STRING_DESCRIPTOR); } @Override public void handleStateMachineMatchedToken(final CodeAttribute c) { c.aload(PARSE_STATE_VAR); c.swap(); c.putfield(parseStateClass, "nextHeader", HTTP_STRING_DESCRIPTOR); } @Override public void updateParseState(final CodeAttribute c) { c.pop(); c.aload(PARSE_STATE_VAR); c.iconst(HEADER_VALUE); c.putfield(parseStateClass, "state", "I"); } @Override public boolean initialNewlineMeansRequestDone() { return true; } } protected class VersionStateMachine implements CustomStateMachine { @Override public boolean isHeader() { return false; } @Override public void handleOtherToken(final CodeAttribute c) { c.aload(HTTP_RESULT); c.swap(); c.invokevirtual(resultClass, "setProtocol", "(" + HTTP_STRING_DESCRIPTOR + ")"+ HTTP_EXCHANGE_DESCRIPTOR); c.pop(); } @Override public void handleStateMachineMatchedToken(final CodeAttribute c) { c.aload(HTTP_RESULT); c.swap(); c.invokevirtual(resultClass, "setProtocol", "(" + HTTP_STRING_DESCRIPTOR + ")" + HTTP_EXCHANGE_DESCRIPTOR); c.pop(); } @Override public void updateParseState(final CodeAttribute c) { c.aload(PARSE_STATE_VAR); c.swap(); c.putfield(parseStateClass, "leftOver", "B"); c.aload(PARSE_STATE_VAR); c.iconst(AFTER_VERSION); c.putfield(parseStateClass, "state", "I"); } @Override public boolean initialNewlineMeansRequestDone() { return false; } } private class VerbStateMachine implements CustomStateMachine { @Override public boolean isHeader() { return false; } @Override public void handleStateMachineMatchedToken(final CodeAttribute c) { c.aload(HTTP_RESULT); c.swap(); c.invokevirtual(resultClass, "setRequestMethod", "(" + HTTP_STRING_DESCRIPTOR + ")" + HTTP_EXCHANGE_DESCRIPTOR); c.pop(); } @Override public void handleOtherToken(final CodeAttribute c) { c.aload(HTTP_RESULT); c.swap(); c.dup(); c.invokestatic(CONNECTORS_CLASS, "verifyToken", "(" + HTTP_STRING_DESCRIPTOR + ")V"); c.invokevirtual(resultClass, "setRequestMethod", "(" + HTTP_STRING_DESCRIPTOR + ")" + HTTP_EXCHANGE_DESCRIPTOR); c.pop(); } @Override public void updateParseState(final CodeAttribute c) { c.pop(); c.aload(PARSE_STATE_VAR); c.iconst(PATH); c.putfield(parseStateClass, "state", "I"); } @Override public boolean initialNewlineMeansRequestDone() { return false; } } } ResponseParserGenerator.java000066400000000000000000000105641420065311100364120ustar00rootroot00000000000000undertow-2.2.16.Final/parser-generator/src/main/java/io/undertow/annotationprocessor/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.annotationprocessor; import org.jboss.classfilewriter.ClassFile; import org.jboss.classfilewriter.ClassMethod; import org.jboss.classfilewriter.code.CodeAttribute; import java.util.concurrent.atomic.AtomicInteger; /** * @author Stuart Douglas * @author Emanuel Muckenhuber */ public class ResponseParserGenerator extends AbstractParserGenerator { //class names public static final String PARSE_STATE_CLASS = "io.undertow.client.http.ResponseParseState"; public static final String HTTP_RESPONSE_CLASS = "io.undertow.client.http.HttpResponseBuilder"; //parsing states public static final int VERSION = 0; public static final int STATUS_CODE = 1; public static final int REASON_PHRASE = 2; public static final int AFTER_REASON_PHRASE = 3; public static final int HEADER = 4; public static final int HEADER_VALUE = 5; public static final int PARSE_COMPLETE = 6; public ResponseParserGenerator(String existingClassName) { super(PARSE_STATE_CLASS, HTTP_RESPONSE_CLASS, "()V", existingClassName); } @Override protected void createStateMachines(final String[] httpVerbs, final String[] httpVersions, final String[] standardHeaders, final String className, final ClassFile file, final ClassMethod sctor, final AtomicInteger fieldCounter) { createStateMachine(httpVersions, className, file, sctor, fieldCounter, HANDLE_HTTP_VERSION, new VersionStateMachine(), false); createStateMachine(standardHeaders, className, file, sctor, fieldCounter, HANDLE_HEADER, new HeaderStateMachine(), false); } private static class HeaderStateMachine implements CustomStateMachine { @Override public boolean isHeader() { return true; } @Override public void handleOtherToken(final CodeAttribute c) { c.aload(PARSE_STATE_VAR); c.swap(); c.putfield(PARSE_STATE_CLASS, "nextHeader", HTTP_STRING_DESCRIPTOR); } @Override public void handleStateMachineMatchedToken(final CodeAttribute c) { c.aload(PARSE_STATE_VAR); c.swap(); c.putfield(PARSE_STATE_CLASS, "nextHeader", HTTP_STRING_DESCRIPTOR); } @Override public void updateParseState(final CodeAttribute c) { c.pop(); c.aload(PARSE_STATE_VAR); c.iconst(HEADER_VALUE); c.putfield(PARSE_STATE_CLASS, "state", "I"); } @Override public boolean initialNewlineMeansRequestDone() { return true; } } private static class VersionStateMachine implements CustomStateMachine { @Override public boolean isHeader() { return false; } @Override public void handleOtherToken(final CodeAttribute c) { c.aload(HTTP_RESULT); c.swap(); c.invokevirtual(HTTP_RESPONSE_CLASS, "setProtocol", "(" + HTTP_STRING_DESCRIPTOR + ")V"); } @Override public void handleStateMachineMatchedToken(final CodeAttribute c) { c.aload(HTTP_RESULT); c.swap(); c.invokevirtual(HTTP_RESPONSE_CLASS, "setProtocol", "(" + HTTP_STRING_DESCRIPTOR + ")V"); } @Override public void updateParseState(final CodeAttribute c) { c.aload(PARSE_STATE_VAR); c.swap(); c.putfield(PARSE_STATE_CLASS, "leftOver", "B"); c.aload(PARSE_STATE_VAR); c.iconst(STATUS_CODE); c.putfield(PARSE_STATE_CLASS, "state", "I"); } @Override public boolean initialNewlineMeansRequestDone() { return false; } } } undertow-2.2.16.Final/parser-generator/src/main/resources/000077500000000000000000000000001420065311100234775ustar00rootroot00000000000000undertow-2.2.16.Final/parser-generator/src/main/resources/META-INF/000077500000000000000000000000001420065311100246375ustar00rootroot00000000000000undertow-2.2.16.Final/parser-generator/src/main/resources/META-INF/services/000077500000000000000000000000001420065311100264625ustar00rootroot00000000000000javax.annotation.processing.Processor000066400000000000000000000000751420065311100357430ustar00rootroot00000000000000undertow-2.2.16.Final/parser-generator/src/main/resources/META-INF/servicesio.undertow.annotationprocessor.HttpParserAnnotationProcessorundertow-2.2.16.Final/pom.xml000066400000000000000000000740331420065311100160160ustar00rootroot00000000000000 4.0.0 org.jboss jboss-parent 35 io.undertow undertow-parent 2.2.16.Final Undertow Undertow pom Apache License Version 2.0 http://repository.jboss.org/licenses/apache-2.0.txt repo scm:git://github.com/undertow-io/undertow.git scm:git://github.com/undertow-io/undertow.git https://github.com/undertow-io/undertow 1.4.200 4.2 2.0.0 5.0.0 2.0.0 1.0.11.Final 4.13 4.1.50.Final 2.0.0-M15 4.5.12 1.2.4.Final 3.4.1.Final 2.2.1.Final 2.1.14.Final 2.0.1.Final 2.0.0.Final 2.0.0.Final 3.8.6.Final 3.1.0.Final 1.5.4.Final 1.0.1.Final 0.7.9 ../spotbugs-exclude.xml -ea ${surefire.jpda.args} -Xmx1024m io.undertow.testutils.category.UnitTest OR NOT io.undertow.testutils.category.UnitTest false 1.0.1.Final 3.1.7 1.1.3.v20160715 1.0.2 1.0.4.Final 7.1 5.1.1 1.21 parser-generator core servlet examples websockets-jsr benchmarks jakarta org.apache.maven.plugins maven-checkstyle-plugin org.apache.maven.plugins maven-javadoc-plugin none implNote a Implementation Note: 1.8 attach-javadocs jar org.zanata zanata-maven-plugin ${version.zanata.plugin} true ${session.executionRootDirectory}/zanata.xml target/classes src/main/resources **/*.i18n.properties,**/LocalDescriptions.properties org.apache.maven.plugins maven-checkstyle-plugin ${version.checkstyle.plugin} undertow-checkstyle/checkstyle.xml true true true ${project.build.sourceDirectory} io.undertow.build undertow-checkstyle-config ${version.io.undertow.build.checkstyle-config} check-style compile checkstyle com.github.spotbugs spotbugs-maven-plugin ${version.com.github.spotbugs-maven-plugin} ${spotbugs.exclude.filter.file} find-bugs verify check org.apache.maven.plugins maven-surefire-plugin ${version.surefire.plugin} ${test.categories} true org.eclipse.m2e lifecycle-mapping 1.0.0 org.apache.maven.plugins maven-checkstyle-plugin [2.5,) checkstyle org.apache.felix maven-bundle-plugin ${version.bundle.plugin} org.wildfly.extras.batavia transformer-tools-mvn ${version.org.wildfly.extras.batavia} transform-sources generate-sources transform-sources ${jakarta.transformer.input.dir} org.wildfly.extras.batavia transformer-impl-eclipse ${version.org.wildfly.extras.batavia} io.undertow undertow-core ${project.version} io.undertow undertow-core ${project.version} test-jar test io.undertow undertow-examples ${project.version} io.undertow undertow-examples-jakarta ${project.version} io.undertow undertow-parser-generator ${project.version} io.undertow undertow-servlet ${project.version} io.undertow undertow-servlet-jakarta ${project.version} io.undertow undertow-servlet-jakarta ${project.version} test-jar io.undertow undertow-servlet ${project.version} test-jar test io.undertow undertow-websockets-jsr ${project.version} io.undertow undertow-websockets-jsr-jakarta ${project.version} io.undertow undertow-benchmarks ${project.version} io.undertow.build undertow-checkstyle-config ${version.io.undertow.build.checkstyle-config} jakarta.servlet jakarta.servlet-api ${version.jakarta.servlet.jakarta-servlet-api} jakarta.annotation jakarta.annotation-api ${version.jakarta.annotation.jakarta-annotation-api} jakarta.websocket jakarta.websocket-api ${version.jakarta.websocket.jakarta-websocket-api} org.jboss.classfilewriter jboss-classfilewriter ${version.org.jboss.classfilewriter} junit junit ${version.junit} test org.eclipse.jetty.alpn alpn-api ${version.org.eclipse.jetty.alpn} provided org.easymock easymock ${version.easymock} test io.netty netty-all ${version.netty} test com.twitter hpack ${version.com.twitter.hpack} test org.apache.directory.server apacheds-all ${version.org.apache.directory.server} test org.apache.httpcomponents httpclient ${version.org.apache.httpcomponents} test org.apache.httpcomponents httpmime ${version.org.apache.httpcomponents} test org.jboss.logging jboss-logging ${version.org.jboss.logging} org.jboss.logging jboss-logging-processor ${version.org.jboss.logging.processor} org.jboss.logmanager jboss-logmanager ${version.org.jboss.logmanager} org.jboss.spec.javax.annotation jboss-annotations-api_1.3_spec ${version.org.jboss.spec.javax.annotation.jboss-annotations-api_1.3_spec} org.jboss.spec.javax.servlet jboss-servlet-api_4.0_spec ${version.org.jboss.spec.javax.servlet.jboss-servlet-api_4.0_spec} org.jboss.spec.javax.websocket jboss-websocket-api_1.1_spec ${version.org.jboss.spec.javax.websockets} org.jboss.xnio xnio-api ${version.xnio} org.jboss.threads jboss-threads org.jboss.xnio xnio-nio ${version.xnio} org.wildfly.common wildfly-common org.jboss.threads jboss-threads ${version.org.jboss.threads} org.wildfly.common wildfly-common org.wildfly.common wildfly-common ${version.org.wildfly.common} com.h2database h2 ${version.com.h2database} test org.wildfly.openssl wildfly-openssl ${version.org.wildfly.openssl} test org.openjdk.jmh jmh-core ${version.jmh} org.openjdk.jmh jmh-generator-annprocess ${version.jmh} provided org.wildfly.extras.batavia transformer-api ${version.org.wildfly.extras.batavia} org.wildfly.extras.batavia transformer-impl-eclipse ${version.org.wildfly.extras.batavia} spotbugs findbugs com.github.spotbugs spotbugs-maven-plugin spotbugs-jdk11 11 findbugs com.github.spotbugs spotbugs-maven-plugin false jdk9 9 org.apache.maven.plugins maven-compiler-plugin ${version.compiler.plugin} true false -J--add-opens=java.base/java.lang=ALL-UNNAMED dist release dist test-coverage coverage-report ${jacoco.activated.agent.argLine} org.jacoco jacoco-maven-plugin ${version.org.jacoco} agent prepare-agent io.undertow* jacoco.activated.agent.argLine only-unit-tests onlyUnitTests io.undertow.testutils.category.UnitTest skip-unit-tests skipUnitTests NOT io.undertow.testutils.category.UnitTest osgi osgi karaf undertow-2.2.16.Final/servlet/000077500000000000000000000000001420065311100161565ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/pom.xml000066400000000000000000000452431420065311100175030ustar00rootroot00000000000000 4.0.0 io.undertow undertow-parent 2.2.16.Final io.undertow undertow-servlet 2.2.16.Final Undertow Servlet INFO false false false false 8192 io.undertow undertow-core org.jboss.logging jboss-logging-processor provided org.jboss.spec.javax.servlet jboss-servlet-api_4.0_spec org.jboss.spec.javax.annotation jboss-annotations-api_1.3_spec io.netty netty-all test io.undertow undertow-core test-jar test org.jboss.xnio xnio-nio test junit junit test org.apache.httpcomponents httpclient test org.jboss.logmanager jboss-logmanager test org.apache.httpcomponents httpmime test org.wildfly.openssl wildfly-openssl ${version.org.wildfly.openssl} test src/test/resources src/test/java **/*.java org.bitstrings.maven.plugins dependencypath-maven-plugin 1.1.1 set-all set org.apache.felix maven-bundle-plugin generate-manifest manifest io.undertow.servlet*;version=${project.version};-noimport:=true javax.annotation.security;version="[1.2,3)", !sun.*, * org.apache.maven.plugins maven-jar-plugin test-jar test-jar tests ${project.build.outputDirectory}/META-INF/MANIFEST.MF org.apache.maven.plugins maven-surefire-plugin true reversealphabetical ${ajp} ${proxy} false ${openssl} ${dump} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${org.wildfly.openssl.path} ${jacoco.agent.argLine} ${surefire.system.args} mac mac /usr/local/opt/openssl/lib proxy org.apache.maven.plugins maven-surefire-plugin proxy test test true reversealphabetical true ${dump} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${project.build.directory}/surefire-proxy-reports proxy-ajp test test true reversealphabetical true ${dump} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${project.build.directory}/surefire-ajp-reports proxy-https test test true reversealphabetical true ${dump} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${project.build.directory}/surefire-https-reports proxy-h2 test test true reversealphabetical true ${dump} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${project.build.directory}/surefire-h2c-reports proxy-h2-upgrade test test true reversealphabetical true ${dump} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${project.build.directory}/surefire-h2c-upgrade-reports proxy-h2c test test true reversealphabetical true ${dump} ${bufferSize} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${project.build.directory}/surefire-h2c-reports undertow-2.2.16.Final/servlet/src/000077500000000000000000000000001420065311100167455ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/000077500000000000000000000000001420065311100176715ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/000077500000000000000000000000001420065311100206125ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/000077500000000000000000000000001420065311100212215ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/000077500000000000000000000000001420065311100230705ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/000077500000000000000000000000001420065311100245545ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/ExceptionLog.java000066400000000000000000000020221420065311100300130ustar00rootroot00000000000000package io.undertow.servlet; import org.jboss.logging.Logger; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Annotation that can be applied to exceptions to control how they are logged by Undertow. * * Note that this will only take effect if the deployments error handler has not been changed. * * @author Stuart Douglas */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited public @interface ExceptionLog { /** * The default log level for this exception. */ Logger.Level value() default Logger.Level.ERROR; /** * The level at which to log stack traces. If this is a higher level * than the default then they will be logged by default at the default level. */ Logger.Level stackTraceLevel() default Logger.Level.FATAL; /** * The category to log this exception under */ String category(); } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/ServletExtension.java000066400000000000000000000027011420065311100307400ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet; import io.undertow.servlet.api.DeploymentInfo; import javax.servlet.ServletContext; /** * * Interface that allows the servlet deployment to be modified before it is deployed. * * These extensions are loaded using a {@link java.util.ServiceLoader} from the deployment * class loader, and are the first things run after the servlet context is created. * * There are many possible use cases for these extensions. Some obvious ones are: * * - Adding additional handlers * - Adding new authentication mechanisms * - Adding and removing servlets * * * @author Stuart Douglas */ public interface ServletExtension { void handleDeployment(final DeploymentInfo deploymentInfo, final ServletContext servletContext); } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/Servlets.java000066400000000000000000000174551420065311100272420ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet; import javax.servlet.Filter; import javax.servlet.MultipartConfigElement; import javax.servlet.Servlet; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.ErrorPage; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.InstanceFactory; import io.undertow.servlet.api.ListenerInfo; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.WebResourceCollection; import io.undertow.servlet.core.ServletContainerImpl; import java.util.EventListener; /** * Utility class for building servlet deployments. * * @author Stuart Douglas */ public class Servlets { private static volatile ServletContainer container; /** * Returns the default servlet container. For most embedded use * cases this will be sufficient. * * @return The default servlet container */ public static ServletContainer defaultContainer() { if (container != null) { return container; } synchronized (Servlets.class) { if (container != null) { return container; } return container = ServletContainer.Factory.newInstance(); } } /** * Creates a new servlet container. * * @return A new servlet container */ public static ServletContainer newContainer() { return new ServletContainerImpl(); } /** * Creates a new servlet deployment info structure * * @return A new deployment info structure */ public static DeploymentInfo deployment() { return new DeploymentInfo(); } /** * Creates a new servlet description with the given class. The servlet name is inferred from the simple name of the class. * * @param servletClass The servlet class * @return A new servlet description */ public static ServletInfo servlet(final Class servletClass) { return servlet(servletClass.getSimpleName(), servletClass); } /** * Creates a new servlet description with the given name and class * * @param name The servlet name * @param servletClass The servlet class * @return A new servlet description */ public static ServletInfo servlet(final String name, final Class servletClass) { return new ServletInfo(name, servletClass); } /** * Creates a new servlet description with the given name and class * * @param name The servlet name * @param servletClass The servlet class * @return A new servlet description */ public static ServletInfo servlet(final String name, final Class servletClass, final InstanceFactory servlet) { return new ServletInfo(name, servletClass, servlet); } /** * Creates a new filter description with the given class. The filter name is inferred from the simple name of the class. * * @param filterClass The filter class * @return A new filter description */ public static FilterInfo filter(final Class filterClass) { return filter(filterClass.getSimpleName(), filterClass); } /** * Creates a new filter description with the given name and class * * @param name The filter name * @param filterClass The filter class * @return A new filter description */ public static FilterInfo filter(final String name, final Class filterClass) { return new FilterInfo(name, filterClass); } /** * Creates a new filter description with the given name and class * * @param name The filter name * @param filterClass The filter class * @return A new filter description */ public static FilterInfo filter(final String name, final Class filterClass, final InstanceFactory filter) { return new FilterInfo(name, filterClass, filter); } /** * Creates a new multipart config element * * @param location the directory location where files will be stored * @param maxFileSize the maximum size allowed for uploaded files * @param maxRequestSize the maximum size allowed for * multipart/form-data requests * @param fileSizeThreshold the size threshold after which files will * be written to disk */ public static MultipartConfigElement multipartConfig(String location, long maxFileSize, long maxRequestSize, int fileSizeThreshold) { return new MultipartConfigElement(location, maxFileSize, maxRequestSize, fileSizeThreshold); } public static ListenerInfo listener(final Class listenerClass, final InstanceFactory instanceFactory) { return new ListenerInfo(listenerClass, instanceFactory); } public static ListenerInfo listener(final Class listenerClass) { return new ListenerInfo(listenerClass); } public static SecurityConstraint securityConstraint() { return new SecurityConstraint(); } public static WebResourceCollection webResourceCollection() { return new WebResourceCollection(); } private Servlets() { } public static LoginConfig loginConfig(String realmName, String loginPage, String errorPage) { return new LoginConfig(realmName, loginPage, errorPage); } public static LoginConfig loginConfig(final String realmName) { return new LoginConfig(realmName); } public static LoginConfig loginConfig(String mechanismName, String realmName, String loginPage, String errorPage) { return new LoginConfig(mechanismName, realmName, loginPage, errorPage); } public static LoginConfig loginConfig(String mechanismName, final String realmName) { return new LoginConfig(mechanismName, realmName); } /** * Create an ErrorPage instance for a given exception type * @param location The location to redirect to * @param exceptionType The exception type * @return The error page definition */ public static ErrorPage errorPage(String location, Class exceptionType) { return new ErrorPage(location, exceptionType); } /** * Create an ErrorPage instance for a given response code * @param location The location to redirect to * @param statusCode The status code * @return The error page definition */ public static ErrorPage errorPage(String location, int statusCode) { return new ErrorPage(location, statusCode); } /** * Create an ErrorPage that corresponds to the default error page * * @param location The error page location * @return The error page instance */ public static ErrorPage errorPage(String location) { return new ErrorPage(location); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/UndertowServletLogger.java000066400000000000000000000140331420065311100317340ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet; import java.io.IOException; import java.util.Date; import java.util.Set; import javax.servlet.UnavailableException; import org.jboss.logging.BasicLogger; import org.jboss.logging.Logger; import org.jboss.logging.annotations.Cause; import org.jboss.logging.annotations.LogMessage; import org.jboss.logging.annotations.Message; import org.jboss.logging.annotations.MessageLogger; import static org.jboss.logging.Logger.Level.ERROR; import static org.jboss.logging.Logger.Level.WARN; /** * log messages start at 15000 * * @author Stuart Douglas */ @MessageLogger(projectCode = "UT") public interface UndertowServletLogger extends BasicLogger { UndertowServletLogger ROOT_LOGGER = Logger.getMessageLogger(UndertowServletLogger.class, UndertowServletLogger.class.getPackage().getName()); UndertowServletLogger REQUEST_LOGGER = Logger.getMessageLogger(UndertowServletLogger.class, UndertowServletLogger.class.getPackage().getName() + ".request"); // // @LogMessage(level = ERROR) // @Message(id = 15000, value = "IOException handling request") // void ioExceptionHandingRequest(@Cause IOException e); // // @LogMessage(level = ERROR) // @Message(id = 15001, value = "ServletException handling request") // void servletExceptionHandlingRequest(@Cause ServletException e); @LogMessage(level = ERROR) @Message(id = 15002, value = "Stopping servlet %s due to permanent unavailability") void stoppingServletDueToPermanentUnavailability(final String servlet, @Cause UnavailableException e); @LogMessage(level = ERROR) @Message(id = 15003, value = "Stopping servlet %s till %s due to temporary unavailability") void stoppingServletUntilDueToTemporaryUnavailability(String name, Date till, @Cause UnavailableException e); // // @LogMessage(level = ERROR) // @Message(id = 15004, value = "Malformed URL exception reading resource %s") // void malformedUrlException(String relativePath, @Cause MalformedURLException e); @LogMessage(level = ERROR) @Message(id = 15005, value = "Error invoking method %s on listener %s") void errorInvokingListener(final String method, Class listenerClass, @Cause Throwable t); @LogMessage(level = ERROR) @Message(id = 15006, value = "IOException dispatching async event") void ioExceptionDispatchingAsyncEvent(@Cause IOException e); @LogMessage(level = Logger.Level.WARN) @Message(id = 15007, value = "Stack trace on error enabled for deployment %s, please do not enable for production use") void servletStackTracesAll(String deploymentName); @LogMessage(level = Logger.Level.WARN) @Message(id = 15008, value = "Failed to load development mode persistent sessions") void failedtoLoadPersistentSessions(@Cause Exception e); @LogMessage(level = Logger.Level.WARN) @Message(id = 15009, value = "Failed to persist session attribute %s with value %s for session %s") void failedToPersistSessionAttribute(String attributeName, Object value, String sessionID, @Cause Exception e); @LogMessage(level = Logger.Level.WARN) @Message(id = 15010, value = "Failed to persist sessions") void failedToPersistSessions(@Cause Exception e); // // @LogMessage(level = Logger.Level.WARN) // @Message(id = 15011, value = "Non standard filter mapping '*' for filter %s. Portable application should use '/*' instead.") // void nonStandardFilterMapping(String filterName); @LogMessage(level = ERROR) @Message(id = 15012, value = "Failed to generate error page %s for original exception: %s. Generating error page resulted in a %s.") void errorGeneratingErrorPage(String originalErrorPage, Object originalException, int code, @Cause Throwable cause); // // @Message(id = 15013, value = "Error opening rewrite configuration") // String errorOpeningRewriteConfiguration(); @Message(id = 15014, value = "Error reading rewrite configuration") @LogMessage(level = ERROR) void errorReadingRewriteConfiguration(@Cause IOException e); @Message(id = 15015, value = "Error reading rewrite configuration: %s") IllegalArgumentException invalidRewriteConfiguration(String line); @Message(id = 15016, value = "Invalid rewrite map class: %s") IllegalArgumentException invalidRewriteMap(String className); @Message(id = 15017, value = "Error reading rewrite flags in line %s as %s") IllegalArgumentException invalidRewriteFlags(String line, String flags); @Message(id = 15018, value = "Error reading rewrite flags in line %s") IllegalArgumentException invalidRewriteFlags(String line); @LogMessage(level = ERROR) @Message(id = 15019, value = "Failed to destroy %s") void failedToDestroy(Object object, @Cause Throwable t); @LogMessage(level = WARN) @Message(id = 15020, value = "Path %s is secured for some HTTP methods, however it is not secured for %s") void unsecuredMethodsOnPath(String path, Set missing); @LogMessage(level = ERROR) @Message(id = 15021, value = "Failure dispatching async event") void failureDispatchingAsyncEvent(@Cause Throwable t); @LogMessage(level = WARN) @Message(id = 15022, value = "Requested resource at %s does not exist for include method") void requestedResourceDoesNotExistForIncludeMethod(String path); @Message(id = 15023, value = "This Context has been already destroyed") IllegalStateException contextDestroyed(); } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/UndertowServletMessages.java000066400000000000000000000261041420065311100322660ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet; import java.io.IOException; import java.net.MalformedURLException; import java.nio.file.Path; import javax.servlet.Filter; import javax.servlet.Servlet; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import io.undertow.servlet.api.DeploymentManager; import org.jboss.logging.annotations.Cause; import org.jboss.logging.annotations.Message; import org.jboss.logging.annotations.MessageBundle; import org.jboss.logging.Messages; /** * messages start at 10000 * * @author Stuart Douglas */ @MessageBundle(projectCode = "UT") public interface UndertowServletMessages { UndertowServletMessages MESSAGES = Messages.getBundle(UndertowServletMessages.class); @Message(id = 10000, value = "%s cannot be null") IllegalArgumentException paramCannotBeNull(String param); @Message(id = 10001, value = "%s cannot be null for %s named %s") IllegalArgumentException paramCannotBeNull(String param, String componentType, String name); @Message(id = 10002, value = "Deployments can only be removed when in undeployed state, but state was %s") IllegalStateException canOnlyRemoveDeploymentsWhenUndeployed(DeploymentManager.State state); @Message(id = 10003, value = "Cannot call getInputStream(), getReader() already called") IllegalStateException getReaderAlreadyCalled(); @Message(id = 10004, value = "Cannot call getReader(), getInputStream() already called") IllegalStateException getInputStreamAlreadyCalled(); @Message(id = 10005, value = "Cannot call getOutputStream(), getWriter() already called") IllegalStateException getWriterAlreadyCalled(); @Message(id = 10006, value = "Cannot call getWriter(), getOutputStream() already called") IllegalStateException getOutputStreamAlreadyCalled(); @Message(id = 10007, value = "Two servlets specified with same mapping %s") IllegalArgumentException twoServletsWithSameMapping(String path); @Message(id = 10008, value = "Header %s cannot be converted to a date") IllegalArgumentException headerCannotBeConvertedToDate(String header); @Message(id = 10009, value = "Servlet %s of type %s does not implement javax.servlet.Servlet") IllegalArgumentException servletMustImplementServlet(String name, Class servletClass); @Message(id = 10010, value = "%s of type %s must have a default constructor") IllegalArgumentException componentMustHaveDefaultConstructor(String componentType, Class componentClass); @Message(id = 10011, value = "Filter %s of type %s does not implement javax.servlet.Filter") IllegalArgumentException filterMustImplementFilter(String name, Class filterClass); @Message(id = 10012, value = "Listener class %s must implement at least one listener interface") IllegalArgumentException listenerMustImplementListenerClass(Class listenerClass); @Message(id = 10013, value = "Could not instantiate %s") ServletException couldNotInstantiateComponent(String name, @Cause Exception e); @Message(id = 10014, value = "Could not load class %s") RuntimeException cannotLoadClass(String className, @Cause Exception e); @Message(id = 10015, value = "Could not delete file %s") IOException deleteFailed(Path file); @Message(id = 10016, value = "Not a multi part request") ServletException notAMultiPartRequest(); // // @Message(id = 10017, value = "Request was neither the original request object or a ServletRequestWrapper") // IllegalArgumentException requestNoOfCorrectType(); @Message(id = 10018, value = "Async not started") IllegalStateException asyncNotStarted(); @Message(id = 10019, value = "Response already commited") IllegalStateException responseAlreadyCommited(); @Message(id = 10020, value = "Content has been written") IllegalStateException contentHasBeenWritten(); @Message(id = 10021, value = "Path %s must start with a /") MalformedURLException pathMustStartWithSlash(String path); @Message(id = 10022, value = "Session is invalid") IllegalStateException sessionIsInvalid(); @Message(id = 10023, value = "Request %s was not original or a wrapper") IllegalArgumentException requestWasNotOriginalOrWrapper(ServletRequest request); @Message(id = 10024, value = "Response %s was not original or a wrapper") IllegalArgumentException responseWasNotOriginalOrWrapper(ServletResponse response); @Message(id = 10025, value = "Async request already dispatched") IllegalStateException asyncRequestAlreadyDispatched(); @Message(id = 10026, value = "Async is not supported for this request, as not all filters or Servlets were marked as supporting async") IllegalStateException startAsyncNotAllowed(); @Message(id = 10027, value = "Not implemented") IllegalStateException notImplemented(); @Message(id = 10028, value = "Async processing already started") IllegalStateException asyncAlreadyStarted(); @Message(id = 10029, value = "Stream is closed") IOException streamIsClosed(); @Message(id = 10030, value = "User already logged in") ServletException userAlreadyLoggedIn(); @Message(id = 10031, value = "Login failed") ServletException loginFailed(); @Message(id = 10032, value = "Authenticationfailed") ServletException authenticationFailed(); @Message(id = 10033, value = "No session") IllegalStateException noSession(); @Message(id = 10034, value = "Stream not in async mode") IllegalStateException streamNotInAsyncMode(); @Message(id = 10035, value = "Stream in async mode was not ready for IO operation") IllegalStateException streamNotReady(); @Message(id = 10036, value = "Listener has already been set") IllegalStateException listenerAlreadySet(); // // @Message(id = 10037, value = "When stream is in async mode a write can only be made from the listener callback") // IllegalStateException writeCanOnlyBeMadeFromListenerCallback(); @Message(id = 10038, value = "No web socket handler was provided to the web socket servlet") ServletException noWebSocketHandler(); @Message(id = 10039, value = "Unknown authentication mechanism %s") RuntimeException unknownAuthenticationMechanism(String mechName); @Message(id = 10040, value = "More than one default error page %s and %s") IllegalStateException moreThanOneDefaultErrorPage(String defaultErrorPage, String location); @Message(id = 10041, value = "The servlet context has already been initialized, you can only call this method from a ServletContainerInitializer or a ServletContextListener") IllegalStateException servletContextAlreadyInitialized(); @Message(id = 10042, value = "This method cannot be called from a servlet context listener that has been added programatically") UnsupportedOperationException cannotCallFromProgramaticListener(); @Message(id = 10043, value = "Cannot add servlet context listener from a programatically added listener") IllegalArgumentException cannotAddServletContextListener(); @Message(id = 10044, value = "listener cannot be null") NullPointerException listenerCannotBeNull(); @Message(id = 10045, value = "SSL cannot be combined with any other method") IllegalArgumentException sslCannotBeCombinedWithAnyOtherMethod(); @Message(id = 10046, value = "No servlet context at %s to dispatch to") IllegalArgumentException couldNotFindContextToDispatchTo(String originalContextPath); @Message(id = 10047, value = "Name was null") NullPointerException nullName(); @Message(id = 10048, value = "Can only handle HTTP type of request / response: %s / %s") IllegalArgumentException invalidRequestResponseType(ServletRequest request, ServletResponse response); @Message(id = 10049, value = "Async request already returned to container") IllegalStateException asyncRequestAlreadyReturnedToContainer(); @Message(id = 10050, value = "Filter %s used in filter mapping %s not found") IllegalStateException filterNotFound(String filterName, String mapping); @Message(id = 10051, value = "Deployment %s has stopped") ServletException deploymentStopped(String deployment); @Message(id = 10052, value = "Header name was null") NullPointerException headerNameWasNull(); @Message(id = 10053, value = "No confidential port is available to redirect the current request.") IllegalStateException noConfidentialPortAvailable(); @Message(id = 10054, value = "Unable to create an instance factory for %s") RuntimeException couldNotCreateFactory(String className, @Cause Exception e); @Message(id = 10055, value = "Listener is not started") IllegalStateException listenerIsNotStarted(); @Message(id = 10056, value = "path was not set") IllegalStateException pathWasNotSet(); @Message(id = 10057, value = "multipart config was not present on Servlet") IllegalStateException multipartConfigNotPresent(); @Message(id = 10058, value = "Servlet name cannot be null") IllegalArgumentException servletNameNull(); @Message(id = 10059, value = "Param %s cannot be null") NullPointerException paramCannotBeNullNPE(String name); @Message(id = 10060, value = "Trailers not supported for this request due to %s") IllegalStateException trailersNotSupported(String reason); @Message(id = 10061, value = "Invalid method for push request %s") IllegalArgumentException invalidMethodForPushRequest(String method); @Message(id = 10062, value = "No SecurityContext available") ServletException noSecurityContextAvailable(); @Message(id = 10063, value = "Path %s must start with a / to get the request dispatcher") IllegalArgumentException pathMustStartWithSlashForRequestDispatcher(String path); @Message(id = 10064, value = "Servlet context for context path '%s' in deployment '%s' has already been initialized, can not declare roles.") IllegalStateException servletAlreadyInitialize(String deploymentName, String contextPath); @Message(id = 10065, value = "Can not set empty/null role in servlet context for context path '%s' in deployment '%s' ") IllegalArgumentException roleMustNotBeEmpty(String deploymentName, String contextPath); @Message(id = 10066, value = "Can not set invoke 'declareRoles' from dynamic listener in servlet context for context path '%s' in deployment '%s' ") UnsupportedOperationException cantCallFromDynamicListener(String deploymentName, String contextPath); } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/000077500000000000000000000000001420065311100253255ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/AuthMethodConfig.java000066400000000000000000000027361420065311100313700ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import java.util.HashMap; import java.util.Map; /** * @author Stuart Douglas */ public class AuthMethodConfig implements Cloneable { private final String name; private final Map properties; public AuthMethodConfig(String name, Map properties) { this.name = name; this.properties = new HashMap<>(properties); } public AuthMethodConfig(String name) { this.name = name; this.properties = new HashMap<>(); } public String getName() { return name; } public Map getProperties() { return properties; } @Override public AuthMethodConfig clone() { return new AuthMethodConfig(name, properties); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/AuthorizationManager.java000066400000000000000000000051071420065311100323260ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import io.undertow.security.idm.Account; import javax.servlet.http.HttpServletRequest; import java.util.List; /** * Authorization manager. The servlet implementation delegates all authorization checks to this interface. * * @author Stuart Douglas */ public interface AuthorizationManager { /** * Tests if a user is in a given role * @param roleName The role name * @param account The user account * @param servletInfo The servlet info for the target servlet * @param request The servlet request * @param deployment The deployment * @return true if the user is in the role */ boolean isUserInRole(String roleName, final Account account, final ServletInfo servletInfo, final HttpServletRequest request, Deployment deployment); /** * Tests if a user can access a given resource * * @param mappedConstraints The constraints * @param account The users account * @param servletInfo The servlet info for the target servlet * @param request The servlet request * @param deployment The deployment * @return true if the user can access the resource */ boolean canAccessResource(List mappedConstraints, final Account account, final ServletInfo servletInfo, final HttpServletRequest request, Deployment deployment); /** * Determines the transport guarantee type * * @param currentConnectionGuarantee The current connections transport guarantee type * @param configuredRequiredGuarantee The transport guarantee type specified in the deployment descriptor/annotations * @param request The request * @return The transport guarantee type */ TransportGuaranteeType transportGuarantee(TransportGuaranteeType currentConnectionGuarantee, TransportGuaranteeType configuredRequiredGuarantee, final HttpServletRequest request); } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/ClassIntrospecter.java000066400000000000000000000023001420065311100316320ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; /** * * Interface that is provided by the container to create a servlet / filter / listener * definition from a given class, based on the annotations present on the class. * * This is needed to allow for annotations to be taken into account when servlets etc are * added programatically. * * @author Stuart Douglas */ public interface ClassIntrospecter { InstanceFactory createInstanceFactory(final Class clazz) throws NoSuchMethodException; } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/ConfidentialPortManager.java000066400000000000000000000026651420065311100327400ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import io.undertow.server.HttpServerExchange; /** * A utility to take the {@link HttpServerExchange} of the current request and obtain the number of the port number to use in * https redirects. * * @author Darran Lofthouse */ public interface ConfidentialPortManager { /** * Obtain the port number to redirect the current request to to provide the transport guarantee of CONDIFENTIAL. * * @param exchange The current {@link HttpServerExchange} being redirected. * @return The port to use in the redirection URI or {@code -1} if no configured port is available. */ int getConfidentialPort(final HttpServerExchange exchange); } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/CrawlerSessionManagerConfig.java000066400000000000000000000033531420065311100335600ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; /** * @author Stuart Douglas */ public class CrawlerSessionManagerConfig { public static final String DEFAULT_CRAWLER_REGEX = ".*[bB]ot.*|.*Yahoo! Slurp.*|.*Feedfetcher-Google.*"; private final String crawlerUserAgents; private final int sessionInactiveInterval; public CrawlerSessionManagerConfig() { this(60, DEFAULT_CRAWLER_REGEX); } public CrawlerSessionManagerConfig(int sessionInactiveInterval) { this(sessionInactiveInterval, DEFAULT_CRAWLER_REGEX); } public CrawlerSessionManagerConfig(final String crawlerUserAgents) { this(60, crawlerUserAgents); } public CrawlerSessionManagerConfig(int sessionInactiveInterval, String crawlerUserAgents) { this.sessionInactiveInterval = sessionInactiveInterval; this.crawlerUserAgents = crawlerUserAgents; } public String getCrawlerUserAgents() { return crawlerUserAgents; } public int getSessionInactiveInterval() { return sessionInactiveInterval; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/DefaultServletConfig.java000066400000000000000000000054221420065311100322520ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; /** * The default servlet config. By default this has quite a restrictive configuration, only allowing * extensions in common use in the web to be served. * * This class is deprecated, the default servlet should be configured via context params. * * @author Stuart Douglas */ @Deprecated public class DefaultServletConfig { private static final String[] DEFAULT_ALLOWED_EXTENSIONS = {"js", "css", "png", "jpg", "gif", "html", "htm", "txt", "pdf"}; private static final String[] DEFAULT_DISALLOWED_EXTENSIONS = {"class", "jar", "war", "zip", "xml"}; private final boolean defaultAllowed; private final Set allowed; private final Set disallowed; public DefaultServletConfig(final boolean defaultAllowed, final Set exceptions) { this.defaultAllowed = defaultAllowed; if(defaultAllowed) { disallowed = Collections.unmodifiableSet(new HashSet<>(exceptions)); allowed = null; } else { allowed = Collections.unmodifiableSet(new HashSet<>(exceptions)); disallowed = null; } } public DefaultServletConfig(final boolean defaultAllowed) { this.defaultAllowed = defaultAllowed; this.allowed = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(DEFAULT_ALLOWED_EXTENSIONS))); this.disallowed = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(DEFAULT_DISALLOWED_EXTENSIONS))); } public DefaultServletConfig() { this.defaultAllowed = false; this.allowed = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(DEFAULT_ALLOWED_EXTENSIONS))); this.disallowed = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(DEFAULT_DISALLOWED_EXTENSIONS))); } public boolean isDefaultAllowed() { return defaultAllowed; } public Set getAllowed() { return allowed; } public Set getDisallowed() { return disallowed; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/Deployment.java000066400000000000000000000060031420065311100303070ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import java.nio.charset.Charset; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.server.HttpHandler; import io.undertow.server.session.SessionManager; import io.undertow.servlet.core.ManagedFilters; import io.undertow.servlet.core.ApplicationListeners; import io.undertow.servlet.core.ManagedServlets; import io.undertow.servlet.core.ErrorPages; import io.undertow.servlet.handlers.ServletPathMatches; import io.undertow.servlet.spec.ServletContextImpl; /** * Runtime representation of a deployment. * * @author Stuart Douglas */ public interface Deployment { DeploymentInfo getDeploymentInfo(); ServletContainer getServletContainer(); ApplicationListeners getApplicationListeners(); ManagedServlets getServlets(); ManagedFilters getFilters(); ServletContextImpl getServletContext(); HttpHandler getHandler(); ServletPathMatches getServletPaths(); ThreadSetupHandler.Action createThreadSetupAction(ThreadSetupHandler.Action target); ErrorPages getErrorPages(); Map getMimeExtensionMappings(); ServletDispatcher getServletDispatcher(); /** * * @return The session manager */ SessionManager getSessionManager(); /** * * @return The executor used for servlet requests. May be null in which case the XNIO worker is used */ Executor getExecutor(); /** * * @return The executor used for async request dispatches. May be null in which case the XNIO worker is used */ Executor getAsyncExecutor(); @Deprecated Charset getDefaultCharset(); Charset getDefaultRequestCharset(); Charset getDefaultResponseCharset(); /** * * @return The list of authentication mechanisms configured for this deployment */ List getAuthenticationMechanisms(); DeploymentManager.State getDeploymentState(); /** * Attempts to add a servlet mapping using {@link javax.servlet.ServletRegistration#addMapping(String...)} * * @return true if the addition was sucessful */ Set tryAddServletMappings(ServletInfo servletInfo, String... urlPatterns); } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/DeploymentInfo.java000077500000000000000000001546011420065311100311360ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; import javax.servlet.DispatcherType; import javax.servlet.MultipartConfigElement; import javax.servlet.ServletContextListener; import javax.servlet.descriptor.JspConfigDescriptor; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.AuthenticationMechanismFactory; import io.undertow.security.api.AuthenticationMode; import io.undertow.security.api.NotificationReceiver; import io.undertow.security.api.SecurityContextFactory; import io.undertow.security.idm.IdentityManager; import io.undertow.server.HandlerWrapper; import io.undertow.server.handlers.resource.ResourceManager; import io.undertow.server.session.SecureRandomSessionIdGenerator; import io.undertow.server.session.SessionIdGenerator; import io.undertow.server.session.SessionListener; import io.undertow.servlet.ServletExtension; import io.undertow.servlet.UndertowServletMessages; import io.undertow.servlet.core.DefaultAuthorizationManager; import io.undertow.servlet.core.InMemorySessionManagerFactory; import io.undertow.servlet.util.DefaultClassIntrospector; import io.undertow.util.ImmediateAuthenticationMechanismFactory; /** * Represents a servlet deployment. * * @author Stuart Douglas */ public class DeploymentInfo implements Cloneable { private static final int DEFAULT_MAJOR_VERSION; static { // UNDERTOW-1810. It is possible at runtime that the class executing this logic has been bytecode // transformed to use a different variant of the Servlet API than it was compiled against, // i.e. EE 9's Servlet 5 instead of EE 8's Servlet 4. Since 4 and 5 are functionally equivalent // except for the package rename, support such a scenario by setting the default major spec // version that is supported based on the package name of a Servlet API class. Package servletPackage = ServletContextListener.class.getPackage(); DEFAULT_MAJOR_VERSION = servletPackage.getName().startsWith("jakarta.") ? 5 : 4; } private String deploymentName; private String displayName; private String contextPath; private ClassLoader classLoader; private ResourceManager resourceManager = ResourceManager.EMPTY_RESOURCE_MANAGER; private ClassIntrospecter classIntrospecter = DefaultClassIntrospector.INSTANCE; private int majorVersion = DEFAULT_MAJOR_VERSION; private int minorVersion = 0; private int containerMajorVersion = DEFAULT_MAJOR_VERSION; private int containerMinorVersion = 0; private Executor executor; private Executor asyncExecutor; private Path tempDir; private JspConfigDescriptor jspConfigDescriptor; private DefaultServletConfig defaultServletConfig; private SessionManagerFactory sessionManagerFactory = new InMemorySessionManagerFactory(); private LoginConfig loginConfig; private IdentityManager identityManager; private ConfidentialPortManager confidentialPortManager; private boolean allowNonStandardWrappers = false; private int defaultSessionTimeout = 60 * 30; private ConcurrentMap servletContextAttributeBackingMap; private ServletSessionConfig servletSessionConfig; private String hostName = "localhost"; private boolean denyUncoveredHttpMethods = false; private ServletStackTraces servletStackTraces = ServletStackTraces.LOCAL_ONLY; private boolean invalidateSessionOnLogout = false; private int defaultCookieVersion = 0; private SessionPersistenceManager sessionPersistenceManager; private String defaultEncoding; private String defaultRequestEncoding; private String defaultResponseEncoding; private String urlEncoding = null; private boolean ignoreFlush = false; private AuthorizationManager authorizationManager = DefaultAuthorizationManager.INSTANCE; private AuthenticationMechanism jaspiAuthenticationMechanism; private SecurityContextFactory securityContextFactory; private String serverName = "Undertow"; private MetricsCollector metricsCollector = null; private SessionConfigWrapper sessionConfigWrapper = null; private boolean eagerFilterInit = false; private boolean disableCachingForSecuredPages = true; private boolean escapeErrorMessage = true; private boolean sendCustomReasonPhraseOnError = false; private boolean useCachedAuthenticationMechanism = true; private boolean preservePathOnForward = true; private AuthenticationMode authenticationMode = AuthenticationMode.PRO_ACTIVE; private ExceptionHandler exceptionHandler; private final Map servlets = new HashMap<>(); private final Map filters = new HashMap<>(); private final List filterServletNameMappings = new ArrayList<>(); private final List filterUrlMappings = new ArrayList<>(); private final List listeners = new ArrayList<>(); private final List servletContainerInitializers = new ArrayList<>(); private final List threadSetupActions = new ArrayList<>(); private final Map initParameters = new HashMap<>(); private final Map servletContextAttributes = new HashMap<>(); private final Map localeCharsetMapping = new HashMap<>(); private final List welcomePages = new ArrayList<>(); private final List errorPages = new ArrayList<>(); private final List mimeMappings = new ArrayList<>(); private final List securityConstraints = new ArrayList<>(); private final Set securityRoles = new HashSet<>(); private final List notificationReceivers = new ArrayList<>(); private final Map authenticationMechanisms = new HashMap<>(); private final List lifecycleInterceptors = new ArrayList<>(); private final List sessionListeners = new ArrayList<>(); /** * additional servlet extensions */ private final List servletExtensions = new ArrayList<>(); /** * map of additional roles that should be applied to the given principal. */ private final Map> principalVersusRolesMap = new HashMap<>(); /** * Wrappers that are applied before the servlet initial handler, and before any servlet related object have been * created. If a wrapper wants to bypass servlet entirely it should register itself here. */ private final List initialHandlerChainWrappers = new ArrayList<>(); /** * Handler chain wrappers that are applied outside all other handlers, including security but after the initial * servlet handler. */ private final List outerHandlerChainWrappers = new ArrayList<>(); /** * Handler chain wrappers that are applied just before the servlet request is dispatched. At this point the security * handlers have run, and any security information is attached to the request. */ private final List innerHandlerChainWrappers = new ArrayList<>(); /** * A handler chain wrapper to wrap the initial stages of the security handlers, if this is set it is assumed it * is taking over the responsibility of setting the {@link io.undertow.security.api.SecurityContext} that can handle authentication and the * remaining Undertow handlers specific to authentication will be skipped. */ private HandlerWrapper initialSecurityWrapper = null; /** * Handler chain wrappers that are applied just before the authentication mechanism is called. Theses handlers are * always called, even if authentication is not required */ private final List securityWrappers = new ArrayList<>(); /** * Multipart config that will be applied to all servlets that do not have an explicit config */ private MultipartConfigElement defaultMultipartConfig; /** * Cache of common content types, to prevent allocations when parsing the charset */ private int contentTypeCacheSize = 100; private boolean changeSessionIdOnLogin = true; private SessionIdGenerator sessionIdGenerator = new SecureRandomSessionIdGenerator(); /** * Config for the {@link io.undertow.servlet.handlers.CrawlerSessionManagerHandler} */ private CrawlerSessionManagerConfig crawlerSessionManagerConfig; private boolean securityDisabled; private boolean checkOtherSessionManagers = true; private final List deploymentCompleteListeners = new ArrayList<>(); /** * A map of content encoding to file extension for pre compressed resource (e.g. gzip -> .gz) */ private final Map preCompressedResources = new HashMap<>(); public void validate() { if (deploymentName == null) { throw UndertowServletMessages.MESSAGES.paramCannotBeNull("deploymentName"); } if (contextPath == null) { throw UndertowServletMessages.MESSAGES.paramCannotBeNull("contextName"); } if (classLoader == null) { throw UndertowServletMessages.MESSAGES.paramCannotBeNull("classLoader"); } if (resourceManager == null) { throw UndertowServletMessages.MESSAGES.paramCannotBeNull("resourceManager"); } if (classIntrospecter == null) { throw UndertowServletMessages.MESSAGES.paramCannotBeNull("classIntrospecter"); } for (final ServletInfo servlet : this.servlets.values()) { servlet.validate(); } for (final FilterInfo filter : this.filters.values()) { filter.validate(); } for (FilterMappingInfo mapping : this.filterServletNameMappings) { if (!this.filters.containsKey(mapping.getFilterName())) { throw UndertowServletMessages.MESSAGES.filterNotFound(mapping.getFilterName(), mapping.getMappingType() + " - " + mapping.getMapping()); } } for (FilterMappingInfo mapping : this.filterUrlMappings) { if (!this.filters.containsKey(mapping.getFilterName())) { throw UndertowServletMessages.MESSAGES.filterNotFound(mapping.getFilterName(), mapping.getMappingType() + " - " + mapping.getMapping()); } } } public String getDeploymentName() { return deploymentName; } public DeploymentInfo setDeploymentName(final String deploymentName) { this.deploymentName = deploymentName; return this; } public String getDisplayName() { return displayName; } public DeploymentInfo setDisplayName(final String displayName) { this.displayName = displayName; return this; } public String getContextPath() { return contextPath; } public DeploymentInfo setContextPath(final String contextPath) { if(contextPath != null && contextPath.isEmpty()) { this.contextPath = "/"; //we represent the root context as / instead of "", but both work } else { this.contextPath = contextPath; } return this; } public ClassLoader getClassLoader() { return classLoader; } public DeploymentInfo setClassLoader(final ClassLoader classLoader) { this.classLoader = classLoader; return this; } public ResourceManager getResourceManager() { return resourceManager; } public DeploymentInfo setResourceManager(final ResourceManager resourceManager) { this.resourceManager = resourceManager; return this; } public ClassIntrospecter getClassIntrospecter() { return classIntrospecter; } public DeploymentInfo setClassIntrospecter(final ClassIntrospecter classIntrospecter) { this.classIntrospecter = classIntrospecter; return this; } public boolean isAllowNonStandardWrappers() { return allowNonStandardWrappers; } public DeploymentInfo setAllowNonStandardWrappers(final boolean allowNonStandardWrappers) { this.allowNonStandardWrappers = allowNonStandardWrappers; return this; } public int getDefaultSessionTimeout() { return defaultSessionTimeout; } /** * @param defaultSessionTimeout The default session timeout, in seconds */ public DeploymentInfo setDefaultSessionTimeout(final int defaultSessionTimeout) { this.defaultSessionTimeout = defaultSessionTimeout; return this; } public String getDefaultEncoding() { return defaultEncoding; } /** * Sets the default encoding that will be used for servlet responses * * @param defaultEncoding The default encoding */ public DeploymentInfo setDefaultEncoding(String defaultEncoding) { this.defaultEncoding = defaultEncoding; return this; } public String getUrlEncoding() { return urlEncoding; } /** * Sets the URL encoding. This will only take effect if the {@link io.undertow.UndertowOptions#DECODE_URL} * parameter has been set to false. This allows multiple deployments in the same server to use a different URL encoding * * @param urlEncoding The encoding to use */ public DeploymentInfo setUrlEncoding(String urlEncoding) { this.urlEncoding = urlEncoding; return this; } public DeploymentInfo addServlet(final ServletInfo servlet) { servlets.put(servlet.getName(), servlet); return this; } public DeploymentInfo addServlets(final ServletInfo... servlets) { for (final ServletInfo servlet : servlets) { addServlet(servlet); } return this; } public DeploymentInfo addServlets(final Collection servlets) { for (final ServletInfo servlet : servlets) { addServlet(servlet); } return this; } public Map getServlets() { return servlets; } public DeploymentInfo addFilter(final FilterInfo filter) { filters.put(filter.getName(), filter); return this; } public DeploymentInfo addFilters(final FilterInfo... filters) { for (final FilterInfo filter : filters) { addFilter(filter); } return this; } public DeploymentInfo addFilters(final Collection filters) { for (final FilterInfo filter : filters) { addFilter(filter); } return this; } public Map getFilters() { return filters; } public DeploymentInfo addFilterUrlMapping(final String filterName, final String mapping, DispatcherType dispatcher) { filterUrlMappings.add(new FilterMappingInfo(filterName, FilterMappingInfo.MappingType.URL, mapping, dispatcher)); return this; } public DeploymentInfo addFilterServletNameMapping(final String filterName, final String mapping, DispatcherType dispatcher) { filterServletNameMappings.add(new FilterMappingInfo(filterName, FilterMappingInfo.MappingType.SERVLET, mapping, dispatcher)); return this; } public DeploymentInfo insertFilterUrlMapping(final int pos, final String filterName, final String mapping, DispatcherType dispatcher) { filterUrlMappings.add(pos, new FilterMappingInfo(filterName, FilterMappingInfo.MappingType.URL, mapping, dispatcher)); return this; } public DeploymentInfo insertFilterServletNameMapping(final int pos, final String filterName, final String mapping, DispatcherType dispatcher) { filterServletNameMappings.add(pos, new FilterMappingInfo(filterName, FilterMappingInfo.MappingType.SERVLET, mapping, dispatcher)); return this; } public List getFilterMappings() { final ArrayList ret = new ArrayList<>(filterUrlMappings); ret.addAll(filterServletNameMappings); return ret; } public DeploymentInfo addListener(final ListenerInfo listener) { listeners.add(listener); return this; } public DeploymentInfo addListeners(final ListenerInfo... listeners) { this.listeners.addAll(Arrays.asList(listeners)); return this; } public DeploymentInfo addListeners(final Collection listeners) { this.listeners.addAll(listeners); return this; } public List getListeners() { return listeners; } public int getMajorVersion() { return majorVersion; } public DeploymentInfo setMajorVersion(final int majorVersion) { this.majorVersion = majorVersion; return this; } public int getMinorVersion() { return minorVersion; } public DeploymentInfo setMinorVersion(final int minorVersion) { this.minorVersion = minorVersion; return this; } public DeploymentInfo addServletContainerInitializer(final ServletContainerInitializerInfo servletContainerInitializer) { servletContainerInitializers.add(servletContainerInitializer); return this; } @Deprecated // UNDERTOW-1375 Method name is misspelled public DeploymentInfo addServletContainerInitalizer(final ServletContainerInitializerInfo servletContainerInitializer) { return addServletContainerInitializer(servletContainerInitializer); } public DeploymentInfo addServletContainerInitializers(final ServletContainerInitializerInfo... servletContainerInitializer) { servletContainerInitializers.addAll(Arrays.asList(servletContainerInitializer)); return this; } @Deprecated // UNDERTOW-1375 Method name is misspelled public DeploymentInfo addServletContainerInitalizers(final ServletContainerInitializerInfo... servletContainerInitializer) { return addServletContainerInitializers(servletContainerInitializer); } public DeploymentInfo addServletContainerInitializers(final List servletContainerInitializer) { servletContainerInitializers.addAll(servletContainerInitializer); return this; } @Deprecated // UNDERTOW-1375 Method name is misspelled public DeploymentInfo addServletContainerInitalizers(final List servletContainerInitializers) { return addServletContainerInitializers(servletContainerInitializers); } public List getServletContainerInitializers() { return servletContainerInitializers; } @Deprecated public DeploymentInfo addThreadSetupAction(final ThreadSetupAction action) { threadSetupActions.add(new LegacyThreadSetupActionWrapper(action)); return this; } public DeploymentInfo addThreadSetupAction(final ThreadSetupHandler action) { threadSetupActions.add(action); return this; } public List getThreadSetupActions() { return threadSetupActions; } public boolean isEagerFilterInit() { return eagerFilterInit; } public DeploymentInfo setEagerFilterInit(boolean eagerFilterInit) { this.eagerFilterInit = eagerFilterInit; return this; } public DeploymentInfo addInitParameter(final String name, final String value) { initParameters.put(name, value); return this; } public Map getInitParameters() { return initParameters; } public DeploymentInfo addServletContextAttribute(final String name, final Object value) { servletContextAttributes.put(name, value); return this; } public Map getServletContextAttributes() { return servletContextAttributes; } public DeploymentInfo addWelcomePage(final String welcomePage) { this.welcomePages.add(welcomePage); return this; } public DeploymentInfo addWelcomePages(final String... welcomePages) { this.welcomePages.addAll(Arrays.asList(welcomePages)); return this; } public DeploymentInfo addWelcomePages(final Collection welcomePages) { this.welcomePages.addAll(welcomePages); return this; } public List getWelcomePages() { return welcomePages; } public DeploymentInfo addErrorPage(final ErrorPage errorPage) { this.errorPages.add(errorPage); return this; } public DeploymentInfo addErrorPages(final ErrorPage... errorPages) { this.errorPages.addAll(Arrays.asList(errorPages)); return this; } public DeploymentInfo addErrorPages(final Collection errorPages) { this.errorPages.addAll(errorPages); return this; } public List getErrorPages() { return errorPages; } public DeploymentInfo addMimeMapping(final MimeMapping mimeMappings) { this.mimeMappings.add(mimeMappings); return this; } public DeploymentInfo addMimeMappings(final MimeMapping... mimeMappings) { this.mimeMappings.addAll(Arrays.asList(mimeMappings)); return this; } public DeploymentInfo addMimeMappings(final Collection mimeMappings) { this.mimeMappings.addAll(mimeMappings); return this; } public List getMimeMappings() { return mimeMappings; } public DeploymentInfo addSecurityConstraint(final SecurityConstraint securityConstraint) { this.securityConstraints.add(securityConstraint); return this; } public DeploymentInfo addSecurityConstraints(final SecurityConstraint... securityConstraints) { this.securityConstraints.addAll(Arrays.asList(securityConstraints)); return this; } public DeploymentInfo addSecurityConstraints(final Collection securityConstraints) { this.securityConstraints.addAll(securityConstraints); return this; } public List getSecurityConstraints() { return securityConstraints; } public Executor getExecutor() { return executor; } /** * Sets the executor that will be used to run servlet invocations. If this is null then the XNIO worker pool will be * used. *

    * Individual servlets may use a different executor *

    * If this is null then the current executor is used, which is generally the XNIO worker pool * * @param executor The executor * @see ServletInfo#executor */ public DeploymentInfo setExecutor(final Executor executor) { this.executor = executor; return this; } public Executor getAsyncExecutor() { return asyncExecutor; } /** * Sets the executor that is used to run async tasks. *

    * If this is null then {@link #executor} is used, if this is also null then the default is used * * @param asyncExecutor The executor */ public DeploymentInfo setAsyncExecutor(final Executor asyncExecutor) { this.asyncExecutor = asyncExecutor; return this; } public File getTempDir() { if(tempDir == null) { return null; } return tempDir.toFile(); } public Path getTempPath() { return tempDir; } /** * @return Returns the {@link #getTempDir() temp directory path} if it's * not null, else returns the system level temporary directory path * pointed to by the Java system property {@code java.io.tmpdir} */ public Path requireTempPath() { if (tempDir != null) { return tempDir; } return Paths.get(SecurityActions.getSystemProperty("java.io.tmpdir")); } public DeploymentInfo setTempDir(final File tempDir) { this.tempDir = tempDir != null ? tempDir.toPath() : null; return this; } public DeploymentInfo setTempDir(final Path tempDir) { this.tempDir = tempDir; return this; } public boolean isIgnoreFlush() { return ignoreFlush; } public DeploymentInfo setIgnoreFlush(boolean ignoreFlush) { this.ignoreFlush = ignoreFlush; return this; } public JspConfigDescriptor getJspConfigDescriptor() { return jspConfigDescriptor; } public DeploymentInfo setJspConfigDescriptor(JspConfigDescriptor jspConfigDescriptor) { this.jspConfigDescriptor = jspConfigDescriptor; return this; } public DefaultServletConfig getDefaultServletConfig() { return defaultServletConfig; } public DeploymentInfo setDefaultServletConfig(final DefaultServletConfig defaultServletConfig) { this.defaultServletConfig = defaultServletConfig; return this; } public DeploymentInfo addLocaleCharsetMapping(final String locale, final String charset) { localeCharsetMapping.put(locale, charset); return this; } public Map getLocaleCharsetMapping() { return localeCharsetMapping; } public SessionManagerFactory getSessionManagerFactory() { return sessionManagerFactory; } public DeploymentInfo setSessionManagerFactory(final SessionManagerFactory sessionManagerFactory) { this.sessionManagerFactory = sessionManagerFactory; return this; } public LoginConfig getLoginConfig() { return loginConfig; } public DeploymentInfo setLoginConfig(LoginConfig loginConfig) { this.loginConfig = loginConfig; return this; } public IdentityManager getIdentityManager() { return identityManager; } public DeploymentInfo setIdentityManager(IdentityManager identityManager) { this.identityManager = identityManager; return this; } public ConfidentialPortManager getConfidentialPortManager() { return confidentialPortManager; } public DeploymentInfo setConfidentialPortManager(ConfidentialPortManager confidentialPortManager) { this.confidentialPortManager = confidentialPortManager; return this; } public DeploymentInfo addSecurityRole(final String role) { this.securityRoles.add(role); return this; } public DeploymentInfo addSecurityRoles(final String... roles) { this.securityRoles.addAll(Arrays.asList(roles)); return this; } public DeploymentInfo addSecurityRoles(final Collection roles) { this.securityRoles.addAll(roles); return this; } public Set getSecurityRoles() { return securityRoles; } /** * Adds an outer handler wrapper. This handler will be run after the servlet initial handler, * but before any other handlers. These are only run on REQUEST invocations, they * are not invoked on a FORWARD or INCLUDE. * * @param wrapper The wrapper */ public DeploymentInfo addOuterHandlerChainWrapper(final HandlerWrapper wrapper) { outerHandlerChainWrappers.add(wrapper); return this; } public List getOuterHandlerChainWrappers() { return outerHandlerChainWrappers; } /** * Adds an inner handler chain wrapper. This handler will be run after the security handler, * but before any other servlet handlers, and will be run for every request * * @param wrapper The wrapper */ public DeploymentInfo addInnerHandlerChainWrapper(final HandlerWrapper wrapper) { innerHandlerChainWrappers.add(wrapper); return this; } public List getInnerHandlerChainWrappers() { return innerHandlerChainWrappers; } public DeploymentInfo addInitialHandlerChainWrapper(final HandlerWrapper wrapper) { initialHandlerChainWrappers.add(wrapper); return this; } public List getInitialHandlerChainWrappers() { return initialHandlerChainWrappers; } /** * Sets the initial handler wrapper that will take over responsibility for establishing * a security context that will handle authentication for the request. * * Undertow specific authentication mechanisms will not be installed but Undertow handlers will * still make the decision as to if authentication is required and will subsequently * call {@link io.undertow.security.api.SecurityContext#authenticate()} as required. * * @param wrapper the {@link HandlerWrapper} to handle the initial security context installation. * @return {@code this} to allow chaining. */ public DeploymentInfo setInitialSecurityWrapper(final HandlerWrapper wrapper) { this.initialSecurityWrapper = wrapper; return this; } public HandlerWrapper getInitialSecurityWrapper() { return initialSecurityWrapper; } /** * Adds a security handler. These are invoked before the authentication mechanism, and are always invoked * even if authentication is not required. * @param wrapper * @return */ public DeploymentInfo addSecurityWrapper(final HandlerWrapper wrapper) { securityWrappers.add(wrapper); return this; } public List getSecurityWrappers() { return securityWrappers; } public DeploymentInfo addNotificationReceiver(final NotificationReceiver notificationReceiver) { this.notificationReceivers.add(notificationReceiver); return this; } public DeploymentInfo addNotificactionReceivers(final NotificationReceiver... notificationReceivers) { this.notificationReceivers.addAll(Arrays.asList(notificationReceivers)); return this; } public DeploymentInfo addNotificationReceivers(final Collection notificationReceivers) { this.notificationReceivers.addAll(notificationReceivers); return this; } public List getNotificationReceivers() { return notificationReceivers; } public ConcurrentMap getServletContextAttributeBackingMap() { return servletContextAttributeBackingMap; } /** * Sets the map that will be used by the ServletContext implementation to store attributes. *

    * This should usuablly be null, in which case Undertow will create a new map. This is only * used in situations where you want multiple deployments to share the same servlet context * attributes. * * @param servletContextAttributeBackingMap * The backing map */ public DeploymentInfo setServletContextAttributeBackingMap(final ConcurrentMap servletContextAttributeBackingMap) { this.servletContextAttributeBackingMap = servletContextAttributeBackingMap; return this; } public ServletSessionConfig getServletSessionConfig() { return servletSessionConfig; } public DeploymentInfo setServletSessionConfig(final ServletSessionConfig servletSessionConfig) { this.servletSessionConfig = servletSessionConfig; return this; } /** * @return the host name */ public String getHostName() { return hostName; } public DeploymentInfo setHostName(final String hostName) { this.hostName = hostName; return this; } public boolean isDenyUncoveredHttpMethods() { return denyUncoveredHttpMethods; } public DeploymentInfo setDenyUncoveredHttpMethods(final boolean denyUncoveredHttpMethods) { this.denyUncoveredHttpMethods = denyUncoveredHttpMethods; return this; } public ServletStackTraces getServletStackTraces() { return servletStackTraces; } public DeploymentInfo setServletStackTraces(ServletStackTraces servletStackTraces) { this.servletStackTraces = servletStackTraces; return this; } public boolean isInvalidateSessionOnLogout() { return invalidateSessionOnLogout; } public DeploymentInfo setInvalidateSessionOnLogout(boolean invalidateSessionOnLogout) { this.invalidateSessionOnLogout = invalidateSessionOnLogout; return this; } public int getDefaultCookieVersion() { return defaultCookieVersion; } public DeploymentInfo setDefaultCookieVersion(int defaultCookieVersion) { this.defaultCookieVersion = defaultCookieVersion; return this; } public SessionPersistenceManager getSessionPersistenceManager() { return sessionPersistenceManager; } public DeploymentInfo setSessionPersistenceManager(SessionPersistenceManager sessionPersistenceManager) { this.sessionPersistenceManager = sessionPersistenceManager; return this; } public AuthorizationManager getAuthorizationManager() { return authorizationManager; } public DeploymentInfo setAuthorizationManager(AuthorizationManager authorizationManager) { this.authorizationManager = authorizationManager; return this; } public DeploymentInfo addPrincipalVsRoleMapping(final String principal, final String mapping) { Set set = principalVersusRolesMap.get(principal); if (set == null) { principalVersusRolesMap.put(principal, set = new HashSet<>()); } set.add(mapping); return this; } public DeploymentInfo addPrincipalVsRoleMappings(final String principal, final String... mappings) { Set set = principalVersusRolesMap.get(principal); if (set == null) { principalVersusRolesMap.put(principal, set = new HashSet<>()); } set.addAll(Arrays.asList(mappings)); return this; } public DeploymentInfo addPrincipalVsRoleMappings(final String principal, final Collection mappings) { Set set = principalVersusRolesMap.get(principal); if (set == null) { principalVersusRolesMap.put(principal, set = new HashSet<>()); } set.addAll(mappings); return this; } public Map> getPrincipalVersusRolesMap() { return principalVersusRolesMap; } /** * Removes all configured authentication mechanisms from the deployment. * * @return this deployment info */ public DeploymentInfo clearLoginMethods() { if(loginConfig != null) { loginConfig.getAuthMethods().clear(); } return this; } /** * Adds an authentication mechanism directly to the deployment. This mechanism will be first in the list. * * In general you should just use {@link #addAuthenticationMechanism(String, io.undertow.security.api.AuthenticationMechanismFactory)} * and allow the user to configure the methods they want by name. * * This method is essentially a convenience method, if is the same as registering a factory under the provided name that returns * and authentication mechanism, and then adding it to the login config list. * * If you want your mechanism to be the only one in the deployment you should first invoke {@link #clearLoginMethods()}. * * @param name The authentication mechanism name * @param mechanism The mechanism * @return this deployment info */ public DeploymentInfo addFirstAuthenticationMechanism(final String name, final AuthenticationMechanism mechanism) { authenticationMechanisms.put(name, new ImmediateAuthenticationMechanismFactory(mechanism)); if(loginConfig == null) { loginConfig = new LoginConfig(null); } loginConfig.addFirstAuthMethod(new AuthMethodConfig(name)); return this; } /** * Adds an authentication mechanism directly to the deployment. This mechanism will be last in the list. * * In general you should just use {@link #addAuthenticationMechanism(String, io.undertow.security.api.AuthenticationMechanismFactory)} * and allow the user to configure the methods they want by name. * * This method is essentially a convenience method, if is the same as registering a factory under the provided name that returns * and authentication mechanism, and then adding it to the login config list. * * If you want your mechanism to be the only one in the deployment you should first invoke {@link #clearLoginMethods()}. * * @param name The authentication mechanism name * @param mechanism The mechanism * @return */ public DeploymentInfo addLastAuthenticationMechanism(final String name, final AuthenticationMechanism mechanism) { authenticationMechanisms.put(name, new ImmediateAuthenticationMechanismFactory(mechanism)); if(loginConfig == null) { loginConfig = new LoginConfig(null); } loginConfig.addLastAuthMethod(new AuthMethodConfig(name)); return this; } /** * Adds an authentication mechanism. The name is case insenstive, and will be converted to uppercase internally. * * @param name The name * @param factory The factory * @return */ public DeploymentInfo addAuthenticationMechanism(final String name, final AuthenticationMechanismFactory factory) { authenticationMechanisms.put(name.toUpperCase(Locale.US), factory); return this; } public Map getAuthenticationMechanisms() { return authenticationMechanisms; } /** * Returns true if the specified mechanism is present in the login config * @param mechanismName The mechanism name * @return true if the mechanism is enabled */ public boolean isAuthenticationMechanismPresent(final String mechanismName) { if(loginConfig != null) { for(AuthMethodConfig method : loginConfig.getAuthMethods()) { if(method.getName().equalsIgnoreCase(mechanismName)) { return true; } } } return false; } /** * Adds an additional servlet extension to the deployment. Servlet extensions are generally discovered * using META-INF/services entries, however this may not be practical in all environments. * @param servletExtension The servlet extension * @return this */ public DeploymentInfo addServletExtension(final ServletExtension servletExtension) { this.servletExtensions.add(servletExtension); return this; } public List getServletExtensions() { return servletExtensions; } public AuthenticationMechanism getJaspiAuthenticationMechanism() { return jaspiAuthenticationMechanism; } public DeploymentInfo setJaspiAuthenticationMechanism(AuthenticationMechanism jaspiAuthenticationMechanism) { this.jaspiAuthenticationMechanism = jaspiAuthenticationMechanism; return this; } public SecurityContextFactory getSecurityContextFactory() { return this.securityContextFactory; } public DeploymentInfo setSecurityContextFactory(final SecurityContextFactory securityContextFactory) { this.securityContextFactory = securityContextFactory; return this; } public String getServerName() { return serverName; } public DeploymentInfo setServerName(String serverName) { this.serverName = serverName; return this; } public DeploymentInfo setMetricsCollector(MetricsCollector metricsCollector){ this.metricsCollector = metricsCollector; return this; } public MetricsCollector getMetricsCollector() { return metricsCollector; } public SessionConfigWrapper getSessionConfigWrapper() { return sessionConfigWrapper; } public DeploymentInfo setSessionConfigWrapper(SessionConfigWrapper sessionConfigWrapper) { this.sessionConfigWrapper = sessionConfigWrapper; return this; } public boolean isDisableCachingForSecuredPages() { return disableCachingForSecuredPages; } public DeploymentInfo setDisableCachingForSecuredPages(boolean disableCachingForSecuredPages) { this.disableCachingForSecuredPages = disableCachingForSecuredPages; return this; } public DeploymentInfo addLifecycleInterceptor(final LifecycleInterceptor interceptor) { lifecycleInterceptors.add(interceptor); return this; } public List getLifecycleInterceptors() { return lifecycleInterceptors; } /** * Returns the exception handler that is used by this deployment. By default this will simply * log unhandled exceptions */ public ExceptionHandler getExceptionHandler() { return exceptionHandler; } /** * Sets the default exception handler for this deployment * @param exceptionHandler The exception handler * @return */ public DeploymentInfo setExceptionHandler(ExceptionHandler exceptionHandler) { this.exceptionHandler = exceptionHandler; return this; } public boolean isEscapeErrorMessage() { return escapeErrorMessage; } /** * Set if if the message passed to {@link javax.servlet.http.HttpServletResponse#sendError(int, String)} should be escaped. * * If this is false applications must be careful not to use user provided data (such as the URI) in the message * * @param escapeErrorMessage If the error message should be escaped */ public DeploymentInfo setEscapeErrorMessage(boolean escapeErrorMessage) { this.escapeErrorMessage = escapeErrorMessage; return this; } public DeploymentInfo addSessionListener(SessionListener sessionListener) { this.sessionListeners.add(sessionListener); return this; } public List getSessionListeners() { return sessionListeners; } public AuthenticationMode getAuthenticationMode() { return authenticationMode; } /** * Sets if this deployment should use pro-active authentication and always authenticate if the credentials are present * or constraint driven auth which will only call the authentication mechanisms for protected resources. * * Pro active auth means that requests for unprotected resources will still be associated with a user, which may be * useful for access logging. * * * @param authenticationMode The authentication mode to use * @return */ public DeploymentInfo setAuthenticationMode(AuthenticationMode authenticationMode) { this.authenticationMode = authenticationMode; return this; } public MultipartConfigElement getDefaultMultipartConfig() { return defaultMultipartConfig; } public DeploymentInfo setDefaultMultipartConfig(MultipartConfigElement defaultMultipartConfig) { this.defaultMultipartConfig = defaultMultipartConfig; return this; } public int getContentTypeCacheSize() { return contentTypeCacheSize; } public DeploymentInfo setContentTypeCacheSize(int contentTypeCacheSize) { this.contentTypeCacheSize = contentTypeCacheSize; return this; } public SessionIdGenerator getSessionIdGenerator() { return sessionIdGenerator; } public DeploymentInfo setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) { this.sessionIdGenerator = sessionIdGenerator; return this; } public boolean isSendCustomReasonPhraseOnError() { return sendCustomReasonPhraseOnError; } public CrawlerSessionManagerConfig getCrawlerSessionManagerConfig() { return crawlerSessionManagerConfig; } public DeploymentInfo setCrawlerSessionManagerConfig(CrawlerSessionManagerConfig crawlerSessionManagerConfig) { this.crawlerSessionManagerConfig = crawlerSessionManagerConfig; return this; } /** * If this is true then the message parameter of {@link javax.servlet.http.HttpServletResponse#sendError(int, String)} and * {@link javax.servlet.http.HttpServletResponse#setStatus(int, String)} will be used as the HTTP reason phrase in * the response. * * @param sendCustomReasonPhraseOnError If the parameter to sendError should be used as a HTTP reason phrase * @return this */ public DeploymentInfo setSendCustomReasonPhraseOnError(boolean sendCustomReasonPhraseOnError) { this.sendCustomReasonPhraseOnError = sendCustomReasonPhraseOnError; return this; } public boolean isChangeSessionIdOnLogin() { return changeSessionIdOnLogin; } public DeploymentInfo setChangeSessionIdOnLogin(boolean changeSessionIdOnLogin) { this.changeSessionIdOnLogin = changeSessionIdOnLogin; return this; } public boolean isUseCachedAuthenticationMechanism() { return useCachedAuthenticationMechanism; } /** * If this is set to false the the cached authenticated session mechanism won't be installed. If you want FORM and * other auth methods that require caching to work then you need to install another caching based auth method (such * as SSO). * @param useCachedAuthenticationMechanism If Undertow should use its internal authentication cache mechanism * @return this */ public DeploymentInfo setUseCachedAuthenticationMechanism(boolean useCachedAuthenticationMechanism) { this.useCachedAuthenticationMechanism = useCachedAuthenticationMechanism; return this; } public boolean isSecurityDisabled() { return securityDisabled; } public DeploymentInfo setSecurityDisabled(boolean securityDisabled) { this.securityDisabled = securityDisabled; return this; } public boolean isCheckOtherSessionManagers() { return checkOtherSessionManagers; } /** * If this is true then when an existing invalid session id is found all other deployments in the container will have their * session managers checked to see if it represents a valid session. If it does then the session id will be re-used. */ public DeploymentInfo setCheckOtherSessionManagers(boolean checkOtherSessionManagers) { this.checkOtherSessionManagers = checkOtherSessionManagers; return this; } public String getDefaultRequestEncoding() { return defaultRequestEncoding; } public DeploymentInfo setDefaultRequestEncoding(String defaultRequestEncoding) { this.defaultRequestEncoding = defaultRequestEncoding; return this; } public String getDefaultResponseEncoding() { return defaultResponseEncoding; } public DeploymentInfo setDefaultResponseEncoding(String defaultResponseEncoding) { this.defaultResponseEncoding = defaultResponseEncoding; return this; } /** * Adds a pre compressed resource encoding and maps it to a file extension * * * @param encoding The content encoding * @param extension The file extension * @return this builder */ public DeploymentInfo addPreCompressedResourceEncoding(String encoding, String extension) { preCompressedResources.put(encoding, extension); return this; } public Map getPreCompressedResources() { return preCompressedResources; } public int getContainerMajorVersion() { return containerMajorVersion; } public DeploymentInfo setContainerMajorVersion(int containerMajorVersion) { this.containerMajorVersion = containerMajorVersion; return this; } public int getContainerMinorVersion() { return containerMinorVersion; } public DeploymentInfo setContainerMinorVersion(int containerMinorVersion) { this.containerMinorVersion = containerMinorVersion; return this; } public boolean isPreservePathOnForward() { return preservePathOnForward; } public void setPreservePathOnForward(boolean preservePathOnForward) { this.preservePathOnForward = preservePathOnForward; } /** * Add's a listener that is only invoked once all other deployment steps have been completed * * The listeners contextDestroyed method will be called after all undeployment steps are undertaken * * @param servletContextListener * @return */ public DeploymentInfo addDeploymentCompleteListener(ServletContextListener servletContextListener) { deploymentCompleteListeners.add(servletContextListener); return this; } public List getDeploymentCompleteListeners() { return deploymentCompleteListeners; } @Override public DeploymentInfo clone() { final DeploymentInfo info = new DeploymentInfo() .setClassLoader(classLoader) .setContextPath(contextPath) .setResourceManager(resourceManager) .setMajorVersion(majorVersion) .setMinorVersion(minorVersion) .setDeploymentName(deploymentName) .setClassIntrospecter(classIntrospecter); for (Map.Entry e : servlets.entrySet()) { info.addServlet(e.getValue().clone()); } for (Map.Entry e : filters.entrySet()) { info.addFilter(e.getValue().clone()); } info.displayName = displayName; info.filterUrlMappings.addAll(filterUrlMappings); info.filterServletNameMappings.addAll(filterServletNameMappings); info.listeners.addAll(listeners); info.servletContainerInitializers.addAll(servletContainerInitializers); info.threadSetupActions.addAll(threadSetupActions); info.initParameters.putAll(initParameters); info.servletContextAttributes.putAll(servletContextAttributes); info.welcomePages.addAll(welcomePages); info.errorPages.addAll(errorPages); info.mimeMappings.addAll(mimeMappings); info.executor = executor; info.asyncExecutor = asyncExecutor; info.tempDir = tempDir; info.jspConfigDescriptor = jspConfigDescriptor; info.defaultServletConfig = defaultServletConfig; info.localeCharsetMapping.putAll(localeCharsetMapping); info.sessionManagerFactory = sessionManagerFactory; if (loginConfig != null) { info.loginConfig = loginConfig.clone(); } info.identityManager = identityManager; info.confidentialPortManager = confidentialPortManager; info.defaultEncoding = defaultEncoding; info.urlEncoding = urlEncoding; info.securityConstraints.addAll(securityConstraints); info.outerHandlerChainWrappers.addAll(outerHandlerChainWrappers); info.innerHandlerChainWrappers.addAll(innerHandlerChainWrappers); info.initialSecurityWrapper = initialSecurityWrapper; info.securityWrappers.addAll(securityWrappers); info.initialHandlerChainWrappers.addAll(initialHandlerChainWrappers); info.securityRoles.addAll(securityRoles); info.notificationReceivers.addAll(notificationReceivers); info.allowNonStandardWrappers = allowNonStandardWrappers; info.defaultSessionTimeout = defaultSessionTimeout; info.servletContextAttributeBackingMap = servletContextAttributeBackingMap; info.servletSessionConfig = servletSessionConfig; info.hostName = hostName; info.denyUncoveredHttpMethods = denyUncoveredHttpMethods; info.servletStackTraces = servletStackTraces; info.invalidateSessionOnLogout = invalidateSessionOnLogout; info.defaultCookieVersion = defaultCookieVersion; info.sessionPersistenceManager = sessionPersistenceManager; for (Map.Entry> e : principalVersusRolesMap.entrySet()) { info.principalVersusRolesMap.put(e.getKey(), new HashSet<>(e.getValue())); } info.ignoreFlush = ignoreFlush; info.authorizationManager = authorizationManager; info.authenticationMechanisms.putAll(authenticationMechanisms); info.servletExtensions.addAll(servletExtensions); info.jaspiAuthenticationMechanism = jaspiAuthenticationMechanism; info.securityContextFactory = securityContextFactory; info.serverName = serverName; info.metricsCollector = metricsCollector; info.sessionConfigWrapper = sessionConfigWrapper; info.eagerFilterInit = eagerFilterInit; info.disableCachingForSecuredPages = disableCachingForSecuredPages; info.exceptionHandler = exceptionHandler; info.escapeErrorMessage = escapeErrorMessage; info.sessionListeners.addAll(sessionListeners); info.lifecycleInterceptors.addAll(lifecycleInterceptors); info.authenticationMode = authenticationMode; info.defaultMultipartConfig = defaultMultipartConfig; info.contentTypeCacheSize = contentTypeCacheSize; info.sessionIdGenerator = sessionIdGenerator; info.sendCustomReasonPhraseOnError = sendCustomReasonPhraseOnError; info.changeSessionIdOnLogin = changeSessionIdOnLogin; info.crawlerSessionManagerConfig = crawlerSessionManagerConfig; info.securityDisabled = securityDisabled; info.useCachedAuthenticationMechanism = useCachedAuthenticationMechanism; info.checkOtherSessionManagers = checkOtherSessionManagers; info.defaultRequestEncoding = defaultRequestEncoding; info.defaultResponseEncoding = defaultResponseEncoding; info.preCompressedResources.putAll(preCompressedResources); info.containerMajorVersion = containerMajorVersion; info.containerMinorVersion = containerMinorVersion; info.deploymentCompleteListeners.addAll(deploymentCompleteListeners); info.preservePathOnForward = preservePathOnForward; return info; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/DeploymentManager.java000066400000000000000000000032561420065311100316110ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import javax.servlet.ServletException; import io.undertow.server.HttpHandler; /** * Manager that can be used to deploy and undeploy a servlet deployment. * * @author Stuart Douglas */ public interface DeploymentManager { /** * Perform the initial deployment. * * The builds all the internal metadata needed to support the servlet deployment, but will not actually start * any servlets * */ void deploy(); /** * Starts the container. Any Servlets with init on startup will be created here. This method returns the servlet * path handler, which must then be added into the appropriate place in the path handler tree. * */ HttpHandler start() throws ServletException; void stop() throws ServletException; void undeploy(); State getState(); /** * */ Deployment getDeployment(); enum State { UNDEPLOYED, DEPLOYED, STARTED; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/ErrorPage.java000066400000000000000000000033301420065311100300550ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; /** * A servlet error page mapping * * * @author Stuart Douglas */ public class ErrorPage { private final String location; private final Integer errorCode; private final Class exceptionType; public ErrorPage(final String location, final Class exceptionType) { this.location = location; this.errorCode = null; this.exceptionType = exceptionType; } public ErrorPage(final String location, final int errorCode) { this.location = location; this.errorCode = errorCode; this.exceptionType = null; } public ErrorPage(final String location) { this.location = location; this.errorCode = null; this.exceptionType = null; } public String getLocation() { return location; } public Integer getErrorCode() { return errorCode; } public Class getExceptionType() { return exceptionType; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/ExceptionHandler.java000066400000000000000000000044471420065311100314350ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import io.undertow.server.HttpServerExchange; /** * An exception handler allows you to perform custom actions when an exception propagates out of the servlet * handler chain. The default handler will simply log the exception, however it is possible to write custom * handlers to handle the error however you want. A common use for this would be to change the log format for * exceptions, or possibly suppress the logging for certain exceptions types. *

    * Implementations of this interface may also choose to suppress error page handler, and handle error page generation * internally by returning true * * @author Stuart Douglas */ public interface ExceptionHandler { /** * Handles an exception. If this method returns true then the request/response cycle is considered to be finished, * and no further action will take place, if this returns false then standard error page redirect will take place. * * The default implementation of this simply logs the exception and returns false, allowing error page and async context * error handling to proceed as normal. * * @param exchange The exchange * @param request The request * @param response The response * @param throwable The exception * @return true true if the error was handled by this method */ boolean handleThrowable(final HttpServerExchange exchange, ServletRequest request, ServletResponse response, Throwable throwable); } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/FilterInfo.java000066400000000000000000000104761420065311100302410ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import java.lang.reflect.Constructor; import java.util.Collections; import java.util.HashMap; import java.util.Map; import javax.servlet.Filter; import io.undertow.servlet.UndertowServletMessages; import io.undertow.servlet.util.ConstructorInstanceFactory; /** * @author Stuart Douglas */ public class FilterInfo implements Cloneable { private final Class filterClass; private final String name; private volatile InstanceFactory instanceFactory; private final Map initParams = new HashMap<>(); private volatile boolean asyncSupported; public FilterInfo(final String name, final Class filterClass) { if (name == null) { throw UndertowServletMessages.MESSAGES.paramCannotBeNull("name"); } if (filterClass == null) { throw UndertowServletMessages.MESSAGES.paramCannotBeNull("filterClass", "Filter", name); } if (!Filter.class.isAssignableFrom(filterClass)) { throw UndertowServletMessages.MESSAGES.filterMustImplementFilter(name, filterClass); } try { final Constructor ctor = (Constructor) filterClass.getDeclaredConstructor(); ctor.setAccessible(true); this.instanceFactory = new ConstructorInstanceFactory<>(ctor); this.name = name; this.filterClass = filterClass; } catch (NoSuchMethodException e) { throw UndertowServletMessages.MESSAGES.componentMustHaveDefaultConstructor("Filter", filterClass); } } public FilterInfo(final String name, final Class filterClass, final InstanceFactory instanceFactory) { if (name == null) { throw UndertowServletMessages.MESSAGES.paramCannotBeNull("name"); } if (filterClass == null) { throw UndertowServletMessages.MESSAGES.paramCannotBeNull("filterClass", "Filter", name); } if (!Filter.class.isAssignableFrom(filterClass)) { throw UndertowServletMessages.MESSAGES.filterMustImplementFilter(name, filterClass); } this.instanceFactory = instanceFactory; this.name = name; this.filterClass = filterClass; } public void validate() { //TODO } @Override public FilterInfo clone() { FilterInfo info = new FilterInfo(name, filterClass, instanceFactory) .setAsyncSupported(asyncSupported); info.initParams.putAll(initParams); return info; } public Class getFilterClass() { return filterClass; } public String getName() { return name; } public InstanceFactory getInstanceFactory() { return instanceFactory; } public void setInstanceFactory(InstanceFactory instanceFactory) { this.instanceFactory = instanceFactory; } public FilterInfo addInitParam(final String name, final String value) { initParams.put(name, value); return this; } public Map getInitParams() { return Collections.unmodifiableMap(initParams); } public boolean isAsyncSupported() { return asyncSupported; } public FilterInfo setAsyncSupported(final boolean asyncSupported) { this.asyncSupported = asyncSupported; return this; } @Override public String toString() { return "FilterInfo{" + "filterClass=" + filterClass + ", name='" + name + '\'' + '}'; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/FilterMappingInfo.java000066400000000000000000000032041420065311100315440ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import javax.servlet.DispatcherType; /** * @author Stuart Douglas */ public class FilterMappingInfo { private final String filterName; private final MappingType mappingType; private final String mapping; private final DispatcherType dispatcher; public FilterMappingInfo(final String filterName, final MappingType mappingType, final String mapping, final DispatcherType dispatcher) { this.filterName = filterName; this.mappingType = mappingType; this.mapping = mapping; this.dispatcher = dispatcher; } public MappingType getMappingType() { return mappingType; } public String getMapping() { return mapping; } public DispatcherType getDispatcher() { return dispatcher; } public String getFilterName() { return filterName; } public enum MappingType { URL, SERVLET; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/HttpMethodSecurityInfo.java000066400000000000000000000025721420065311100326220ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; /** * @author Stuart Douglas */ public class HttpMethodSecurityInfo extends SecurityInfo implements Cloneable { private volatile String method; public String getMethod() { return method; } public HttpMethodSecurityInfo setMethod(final String method) { this.method = method; return this; } @Override protected HttpMethodSecurityInfo createInstance() { return new HttpMethodSecurityInfo(); } @Override public HttpMethodSecurityInfo clone() { HttpMethodSecurityInfo info = super.clone(); info.method = method; return info; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/InstanceFactory.java000066400000000000000000000020651420065311100312670ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; /** * Factory that creates fully injected component instances. * * @author Stuart Douglas */ public interface InstanceFactory { /** * Factory that creates a fully injected instance. * * @return The fully injected instance */ InstanceHandle createInstance() throws InstantiationException; } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/InstanceHandle.java000066400000000000000000000022231420065311100310470ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; /** * A handle for a container managed instance. When the servlet container is * done with it it should call the {@link #release()} method * * @author Stuart Douglas */ public interface InstanceHandle { /** * @return The managed instance * */ T getInstance(); /** * releases the instance, uninjecting and calling an pre-destroy methods as appropriate */ void release(); } LegacyThreadSetupActionWrapper.java000066400000000000000000000033071420065311100341700ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import io.undertow.server.HttpServerExchange; /** * Class that allows legacy thread setup actions to still be used * * @author Stuart Douglas */ @SuppressWarnings("deprecation") class LegacyThreadSetupActionWrapper implements ThreadSetupHandler { private final ThreadSetupAction setupAction; LegacyThreadSetupActionWrapper(ThreadSetupAction setupAction) { this.setupAction = setupAction; } @Override public Action create(final Action action) { return new Action() { @Override public T call(HttpServerExchange exchange, C context) throws Exception { ThreadSetupAction.Handle handle = setupAction.setup(exchange); try { return action.call(exchange, context); } finally { if (handle != null) { handle.tearDown(); } } } }; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/LifecycleInterceptor.java000066400000000000000000000017471420065311100323170ustar00rootroot00000000000000package io.undertow.servlet.api; import javax.servlet.Filter; import javax.servlet.Servlet; import javax.servlet.ServletException; /** * Class that is run around invocations of servlet and filter lifecycle methods (init and destroy). * * Note that this only deals with lifecycle methods that are defined by the servlet spec. @POstConstruct, * PreDestroy and Inject methods are not handled. * * @author Stuart Douglas */ public interface LifecycleInterceptor { void init(ServletInfo servletInfo, Servlet servlet, LifecycleContext context) throws ServletException; void init(FilterInfo filterInfo, Filter filter, LifecycleContext context) throws ServletException; void destroy(ServletInfo servletInfo, Servlet servlet, LifecycleContext context) throws ServletException; void destroy(FilterInfo filterInfo, Filter filter, LifecycleContext context) throws ServletException; public interface LifecycleContext { void proceed() throws ServletException; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/ListenerInfo.java000066400000000000000000000062461420065311100306010ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import java.lang.reflect.Constructor; import java.util.EventListener; import io.undertow.servlet.UndertowServletMessages; import io.undertow.servlet.core.ApplicationListeners; import io.undertow.servlet.util.ConstructorInstanceFactory; /** * @author Stuart Douglas */ public class ListenerInfo { private final Class listenerClass; private volatile InstanceFactory instanceFactory; private final boolean programatic; public ListenerInfo(final Class listenerClass, final InstanceFactory instanceFactory) { this(listenerClass, instanceFactory, false); } public ListenerInfo(final Class listenerClass, final InstanceFactory instanceFactory, boolean programatic) { this.listenerClass = listenerClass; this.instanceFactory = instanceFactory; this.programatic = programatic; if(!ApplicationListeners.isListenerClass(listenerClass)) { throw UndertowServletMessages.MESSAGES.listenerMustImplementListenerClass(listenerClass); } } public ListenerInfo(final Class listenerClass) { this(listenerClass, false); } public ListenerInfo(final Class listenerClass, boolean programatic) { this.listenerClass = listenerClass; this.programatic = programatic; try { final Constructor ctor = (Constructor) listenerClass.getDeclaredConstructor(); ctor.setAccessible(true); this.instanceFactory = new ConstructorInstanceFactory<>(ctor); } catch (NoSuchMethodException e) { throw UndertowServletMessages.MESSAGES.componentMustHaveDefaultConstructor("Listener", listenerClass); } } public InstanceFactory getInstanceFactory() { return instanceFactory; } public void setInstanceFactory(InstanceFactory instanceFactory) { this.instanceFactory = instanceFactory; } public boolean isProgramatic() { return programatic; } public Class getListenerClass() { return listenerClass; } @Override public String toString() { return "ListenerInfo{" + "listenerClass=" + listenerClass + '}'; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/LoggingExceptionHandler.java000066400000000000000000000127361420065311100327440ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import io.undertow.UndertowLogger; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.ExceptionLog; import org.jboss.logging.BasicLogger; import org.jboss.logging.Logger; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; /** * An exception handler that * * * @author Stuart Douglas */ public class LoggingExceptionHandler implements ExceptionHandler { public static final LoggingExceptionHandler DEFAULT = new LoggingExceptionHandler(Collections., ExceptionDetails>emptyMap()); private final Map, ExceptionDetails> exceptionDetails; public LoggingExceptionHandler(Map, ExceptionDetails> exceptionDetails) { this.exceptionDetails = exceptionDetails; } @Override public boolean handleThrowable(HttpServerExchange exchange, ServletRequest request, ServletResponse response, Throwable t) { ExceptionDetails details = null; if (!exceptionDetails.isEmpty()) { Class c = t.getClass(); while (c != null && c != Object.class) { details = exceptionDetails.get(c); if (details != null) { break; } c = c.getSuperclass(); } } ExceptionLog log = t.getClass().getAnnotation(ExceptionLog.class); if (details != null) { Logger.Level level = details.level; Logger.Level stackTraceLevel = details.stackTraceLevel; String category = details.category; handleCustomLog(exchange, t, level, stackTraceLevel, category); } else if (log != null) { Logger.Level level = log.value(); Logger.Level stackTraceLevel = log.stackTraceLevel(); String category = log.category(); handleCustomLog(exchange, t, level, stackTraceLevel, category); } else if (t instanceof IOException) { //we log IOExceptions at a lower level //because they can be easily caused by malicious remote clients in at attempt to DOS the server by filling the logs UndertowLogger.REQUEST_IO_LOGGER.debugf(t, "Exception handling request to %s", exchange.getRequestURI()); } else { UndertowLogger.REQUEST_LOGGER.exceptionHandlingRequest(t, exchange.getRequestURI()); } return false; } private void handleCustomLog(HttpServerExchange exchange, Throwable t, Logger.Level level, Logger.Level stackTraceLevel, String category) { BasicLogger logger = UndertowLogger.REQUEST_LOGGER; if (!category.isEmpty()) { logger = Logger.getLogger(category); } boolean stackTrace = true; if (stackTraceLevel.ordinal() > level.ordinal()) { if (!logger.isEnabled(stackTraceLevel)) { stackTrace = false; } } if (stackTrace) { logger.logf(level, t, "Exception handling request to %s", exchange.getRequestURI()); } else { logger.logf(level, "Exception handling request to %s: %s", exchange.getRequestURI(), t.getMessage()); } } private static class ExceptionDetails { final Logger.Level level; final Logger.Level stackTraceLevel; final String category; private ExceptionDetails(Logger.Level level, Logger.Level stackTraceLevel, String category) { this.level = level; this.stackTraceLevel = stackTraceLevel; this.category = category; } } public static Builder builder() { return new Builder(); } public static final class Builder { private final Map, ExceptionDetails> exceptionDetails = new HashMap<>(); Builder() {} public Builder add(Class exception, String category, Logger.Level level) { exceptionDetails.put(exception, new ExceptionDetails(level, Logger.Level.FATAL, category)); return this; } public Builder add(Class exception, String category) { exceptionDetails.put(exception, new ExceptionDetails(Logger.Level.ERROR, Logger.Level.FATAL, category)); return this; } public Builder add(Class exception, String category, Logger.Level level, Logger.Level stackTraceLevel) { exceptionDetails.put(exception, new ExceptionDetails(level, stackTraceLevel, category)); return this; } public LoggingExceptionHandler build() { return new LoggingExceptionHandler(exceptionDetails); } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/LoginConfig.java000066400000000000000000000055741420065311100304010ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import java.util.LinkedList; import java.util.List; /** * @author Stuart Douglas */ public class LoginConfig implements Cloneable { private final LinkedList authMethods = new LinkedList<>(); private final String realmName; private final String loginPage; private final String errorPage; public LoginConfig(String realmName, String loginPage, String errorPage) { this.realmName = realmName; this.loginPage = loginPage; this.errorPage = errorPage; } public LoginConfig(final String realmName) { this(realmName, null, null); } public LoginConfig(String mechanismName, String realmName, String loginPage, String errorPage) { this.realmName = realmName; this.loginPage = loginPage; this.errorPage = errorPage; addFirstAuthMethod(mechanismName); } public LoginConfig(String mechanismName, final String realmName) { this(mechanismName, realmName, null, null); } public String getRealmName() { return realmName; } public String getLoginPage() { return loginPage; } public String getErrorPage() { return errorPage; } public LoginConfig addFirstAuthMethod(AuthMethodConfig authMethodConfig) { authMethods.addFirst(authMethodConfig); return this; } public LoginConfig addLastAuthMethod(AuthMethodConfig authMethodConfig) { authMethods.addLast(authMethodConfig); return this; } public LoginConfig addFirstAuthMethod(String authMethodConfig) { authMethods.addFirst(new AuthMethodConfig(authMethodConfig)); return this; } public LoginConfig addLastAuthMethod(String authMethodConfig) { authMethods.addLast(new AuthMethodConfig(authMethodConfig)); return this; } public List getAuthMethods() { return authMethods; } @Override public LoginConfig clone() { LoginConfig lc = new LoginConfig(realmName, loginPage, errorPage); for(AuthMethodConfig method : authMethods) { lc.authMethods.add(method.clone()); } return lc; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/MetricsCollector.java000066400000000000000000000017771420065311100314610ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import io.undertow.server.handlers.MetricsHandler; /** * An interface that can be used to collect Servlet metrics * * @author Tomaz Cerar (c) 2014 Red Hat Inc. */ public interface MetricsCollector { void registerMetric(String servletName, MetricsHandler handler); } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/MimeMapping.java000066400000000000000000000022141420065311100303720ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; /** * @author Stuart Douglas */ public class MimeMapping { private final String extension; private final String mimeType; public MimeMapping(final String extension, final String mimeType) { this.extension = extension; this.mimeType = mimeType; } public String getExtension() { return extension; } public String getMimeType() { return mimeType; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/SecurityActions.java000066400000000000000000000024661420065311100313300ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import java.security.AccessController; import java.security.PrivilegedAction; final class SecurityActions { private SecurityActions() { // forbidden inheritance } static String getSystemProperty(final String prop) { if (System.getSecurityManager() == null) { return System.getProperty(prop); } else { return (String) AccessController.doPrivileged(new PrivilegedAction() { public Object run() { return System.getProperty(prop); } }); } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/SecurityConstraint.java000066400000000000000000000042631420065311100320510ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; /** * @author Stuart Douglas */ public class SecurityConstraint extends SecurityInfo { private final Set webResourceCollections = new HashSet<>(); public Set getWebResourceCollections() { return Collections.unmodifiableSet(webResourceCollections); } public SecurityConstraint addWebResourceCollection(final WebResourceCollection webResourceCollection) { this.webResourceCollections.add(webResourceCollection); return this; } public SecurityConstraint addWebResourceCollections(final WebResourceCollection... webResourceCollection) { this.webResourceCollections.addAll(Arrays.asList(webResourceCollection)); return this; } public SecurityConstraint addWebResourceCollections(final List webResourceCollections) { this.webResourceCollections.addAll(webResourceCollections); return this; } @Override protected SecurityConstraint createInstance() { return new SecurityConstraint(); } @Override public SecurityConstraint clone() { SecurityConstraint info = super.clone(); for (WebResourceCollection wr : webResourceCollections) { info.addWebResourceCollection(wr.clone()); } return info; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/SecurityInfo.java000066400000000000000000000062271420065311100306220ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Set; /** * @author Stuart Douglas */ public class SecurityInfo implements Cloneable { /** * Equivalent to {@link javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic} but with an additional mode to require authentication but no role * check. */ public enum EmptyRoleSemantic { /** * Permit access to the resource without requiring authentication or role membership. */ PERMIT, /** * Deny access to the resource regardless of the authentication state. */ DENY, /** * Mandate authentication but authorize access as no roles to check against. */ AUTHENTICATE; } private volatile EmptyRoleSemantic emptyRoleSemantic = EmptyRoleSemantic.DENY; private final Set rolesAllowed = new HashSet<>(); private volatile TransportGuaranteeType transportGuaranteeType = TransportGuaranteeType.NONE; public EmptyRoleSemantic getEmptyRoleSemantic() { return emptyRoleSemantic; } public T setEmptyRoleSemantic(final EmptyRoleSemantic emptyRoleSemantic) { this.emptyRoleSemantic = emptyRoleSemantic; return (T)this; } public TransportGuaranteeType getTransportGuaranteeType() { return transportGuaranteeType; } public T setTransportGuaranteeType(final TransportGuaranteeType transportGuaranteeType) { this.transportGuaranteeType = transportGuaranteeType; return (T) this; } public T addRoleAllowed(final String role) { this.rolesAllowed.add(role); return (T) this; } public T addRolesAllowed(final String ... roles) { this.rolesAllowed.addAll(Arrays.asList(roles)); return (T) this; } public T addRolesAllowed(final Collection roles) { this.rolesAllowed.addAll(roles); return (T) this; } public Set getRolesAllowed() { return new HashSet<>(rolesAllowed); } @Override public T clone() { final SecurityInfo info = createInstance(); info.emptyRoleSemantic = emptyRoleSemantic; info.transportGuaranteeType = transportGuaranteeType; info.rolesAllowed.addAll(rolesAllowed); return (T) info; } protected T createInstance() { return (T) new SecurityInfo(); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/SecurityRoleRef.java000066400000000000000000000024531420065311100312620ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import io.undertow.servlet.UndertowServletMessages; /** * @author Stuart Douglas */ public class SecurityRoleRef { private final String role; private final String linkedRole; public SecurityRoleRef(final String role, final String linkedRole) { if(role == null) { throw UndertowServletMessages.MESSAGES.paramCannotBeNull("role"); } this.role = role; this.linkedRole = linkedRole; } public String getRole() { return role; } public String getLinkedRole() { return linkedRole; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/ServletContainer.java000066400000000000000000000026121420065311100314600ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import java.util.Collection; import io.undertow.servlet.core.ServletContainerImpl; /** * @author Stuart Douglas */ public interface ServletContainer { /** * * @return The names of the deployments in this container */ Collection listDeployments(); DeploymentManager addDeployment(DeploymentInfo deployment); DeploymentManager getDeployment(String deploymentName); void removeDeployment(DeploymentInfo deploymentInfo); DeploymentManager getDeploymentByPath(String uripath); class Factory { public static ServletContainer newInstance() { return new ServletContainerImpl(); } } } ServletContainerInitializerInfo.java000066400000000000000000000062241420065311100344240ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import io.undertow.servlet.UndertowServletMessages; import io.undertow.servlet.util.ConstructorInstanceFactory; import java.lang.reflect.Constructor; import java.util.Set; import javax.servlet.ServletContainerInitializer; /** * @author Stuart Douglas */ public class ServletContainerInitializerInfo { private final Class servletContainerInitializerClass; private final InstanceFactory instanceFactory; private final Set> handlesTypes; public ServletContainerInitializerInfo(final Class servletContainerInitializerClass, final InstanceFactory instanceFactory, final Set> handlesTypes) { this.servletContainerInitializerClass = servletContainerInitializerClass; this.instanceFactory = instanceFactory; this.handlesTypes = handlesTypes; } public ServletContainerInitializerInfo(final Class servletContainerInitializerClass, final Set> handlesTypes) { this.servletContainerInitializerClass = servletContainerInitializerClass; this.handlesTypes = handlesTypes; try { final Constructor ctor = (Constructor) servletContainerInitializerClass.getDeclaredConstructor(); ctor.setAccessible(true); this.instanceFactory = new ConstructorInstanceFactory<>(ctor); } catch (NoSuchMethodException e) { throw UndertowServletMessages.MESSAGES.componentMustHaveDefaultConstructor("ServletContainerInitializer", servletContainerInitializerClass); } } public Class getServletContainerInitializerClass() { return servletContainerInitializerClass; } /** * Returns the actual types present in the deployment that are handled by this ServletContainerInitializer. * * (i.e. not the types in the {@link javax.servlet.annotation.HandlesTypes} annotation, but rather actual types * the container has discovered that meet the criteria) * * @return The handled types */ public Set> getHandlesTypes() { return handlesTypes; } public InstanceFactory getInstanceFactory() { return instanceFactory; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/ServletDispatcher.java000066400000000000000000000037211420065311100316260ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.handlers.ServletChain; import io.undertow.servlet.handlers.ServletPathMatch; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public interface ServletDispatcher { /** * Dispatches a servlet request to the specified servlet path, changing the current path * @see io.undertow.servlet.handlers.ServletRequestContext */ void dispatchToPath(final HttpServerExchange exchange, final ServletPathMatch pathMatch, final DispatcherType dispatcherType) throws Exception; /** * Dispatches a servlet request to the specified servlet, without changing the current path */ void dispatchToServlet(final HttpServerExchange exchange, final ServletChain servletChain, final DispatcherType dispatcherType) throws Exception; /** * Dispatches a mock request to the servlet container. * * @param request The request * @param response The response */ void dispatchMockRequest(final HttpServletRequest request, final HttpServletResponse response) throws ServletException; } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/ServletInfo.java000066400000000000000000000224251420065311100304350ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Executor; import javax.servlet.MultipartConfigElement; import javax.servlet.Servlet; import io.undertow.server.HandlerWrapper; import io.undertow.servlet.UndertowServletMessages; import io.undertow.servlet.util.ConstructorInstanceFactory; /** * @author Stuart Douglas */ public class ServletInfo implements Cloneable { private final Class servletClass; private final String name; private final List mappings = new ArrayList<>(); private final Map initParams = new HashMap<>(); private final List securityRoleRefs = new ArrayList<>(); private final List handlerChainWrappers = new ArrayList<>(); private InstanceFactory instanceFactory; private String jspFile; private Integer loadOnStartup; private boolean enabled; private boolean asyncSupported; private String runAs; private MultipartConfigElement multipartConfig; private ServletSecurityInfo servletSecurityInfo; private Executor executor; /** * If this is true this servlet will not be considered when evaluating welcome file mappings, * and if the mapped path is a directory a welcome file match will be performed that may result in another servlet * being selected. * * Generally intended to be used by the default and JSP servlet. */ private boolean requireWelcomeFileMapping; public ServletInfo(final String name, final Class servletClass) { if (name == null) { throw UndertowServletMessages.MESSAGES.paramCannotBeNull("name"); } if (servletClass == null) { throw UndertowServletMessages.MESSAGES.paramCannotBeNull("servletClass", "Servlet", name); } if (!Servlet.class.isAssignableFrom(servletClass)) { throw UndertowServletMessages.MESSAGES.servletMustImplementServlet(name, servletClass); } try { final Constructor ctor = servletClass.getDeclaredConstructor(); ctor.setAccessible(true); this.instanceFactory = new ConstructorInstanceFactory(ctor); this.name = name; this.servletClass = servletClass; } catch (NoSuchMethodException e) { throw UndertowServletMessages.MESSAGES.componentMustHaveDefaultConstructor("Servlet", servletClass); } } public ServletInfo(final String name, final Class servletClass, final InstanceFactory instanceFactory) { if (name == null) { throw UndertowServletMessages.MESSAGES.paramCannotBeNull("name"); } if (servletClass == null) { throw UndertowServletMessages.MESSAGES.paramCannotBeNull("servletClass", "Servlet", name); } if (!Servlet.class.isAssignableFrom(servletClass)) { throw UndertowServletMessages.MESSAGES.servletMustImplementServlet(name, servletClass); } this.instanceFactory = instanceFactory; this.name = name; this.servletClass = servletClass; } public void validate() { //TODO } @Override public ServletInfo clone() { ServletInfo info = new ServletInfo(name, servletClass, instanceFactory) .setJspFile(jspFile) .setLoadOnStartup(loadOnStartup) .setEnabled(enabled) .setAsyncSupported(asyncSupported) .setRunAs(runAs) .setMultipartConfig(multipartConfig) .setExecutor(executor) .setRequireWelcomeFileMapping(requireWelcomeFileMapping); info.mappings.addAll(mappings); info.initParams.putAll(initParams); info.securityRoleRefs.addAll(securityRoleRefs); info.handlerChainWrappers.addAll(handlerChainWrappers); if (servletSecurityInfo != null) { info.servletSecurityInfo = servletSecurityInfo.clone(); } return info; } public Class getServletClass() { return servletClass; } public String getName() { return name; } public void setInstanceFactory(final InstanceFactory instanceFactory) { if (instanceFactory == null) { throw UndertowServletMessages.MESSAGES.paramCannotBeNull("instanceFactory"); } this.instanceFactory = instanceFactory; } public InstanceFactory getInstanceFactory() { return instanceFactory; } public List getMappings() { return Collections.unmodifiableList(mappings); } public ServletInfo addMapping(final String mapping) { if(!mapping.startsWith("/") && !mapping.startsWith("*") && !mapping.isEmpty()) { //if the user adds a mapping like 'index.html' we transparently translate it to '/index.html' mappings.add("/" + mapping); } else { mappings.add(mapping); } return this; } public ServletInfo addMappings(final Collection mappings) { for(String m : mappings) { addMapping(m); } return this; } public ServletInfo addMappings(final String... mappings) { for(String m : mappings) { addMapping(m); } return this; } public ServletInfo addInitParam(final String name, final String value) { initParams.put(name, value); return this; } public Map getInitParams() { return Collections.unmodifiableMap(initParams); } public String getJspFile() { return jspFile; } public ServletInfo setJspFile(final String jspFile) { this.jspFile = jspFile; return this; } public Integer getLoadOnStartup() { return loadOnStartup; } public ServletInfo setLoadOnStartup(final Integer loadOnStartup) { this.loadOnStartup = loadOnStartup; return this; } public boolean isAsyncSupported() { return asyncSupported; } public ServletInfo setAsyncSupported(final boolean asyncSupported) { this.asyncSupported = asyncSupported; return this; } public boolean isEnabled() { return enabled; } public ServletInfo setEnabled(final boolean enabled) { this.enabled = enabled; return this; } public String getRunAs() { return runAs; } public ServletInfo setRunAs(final String runAs) { this.runAs = runAs; return this; } public MultipartConfigElement getMultipartConfig() { return multipartConfig; } public ServletInfo setMultipartConfig(final MultipartConfigElement multipartConfig) { this.multipartConfig = multipartConfig; return this; } public void addSecurityRoleRef(final String role, final String linkedRole) { this.securityRoleRefs.add(new SecurityRoleRef(role, linkedRole)); } public List getSecurityRoleRefs() { return Collections.unmodifiableList(securityRoleRefs); } public ServletInfo addHandlerChainWrapper(final HandlerWrapper wrapper) { this.handlerChainWrappers.add(wrapper); return this; } public List getHandlerChainWrappers() { return Collections.unmodifiableList(handlerChainWrappers); } public ServletSecurityInfo getServletSecurityInfo() { return servletSecurityInfo; } public ServletInfo setServletSecurityInfo(final ServletSecurityInfo servletSecurityInfo) { this.servletSecurityInfo = servletSecurityInfo; return this; } public Executor getExecutor() { return executor; } public ServletInfo setExecutor(final Executor executor) { this.executor = executor; return this; } /** * * @return */ public boolean isRequireWelcomeFileMapping() { return requireWelcomeFileMapping; } public ServletInfo setRequireWelcomeFileMapping(boolean requireWelcomeFileMapping) { this.requireWelcomeFileMapping = requireWelcomeFileMapping; return this; } @Override public String toString() { return "ServletInfo{" + "mappings=" + mappings + ", servletClass=" + servletClass + ", name='" + name + '\'' + '}'; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/ServletSecurityInfo.java000066400000000000000000000032601420065311100321610ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import java.util.ArrayList; import java.util.List; /** * @author Stuart Douglas */ public class ServletSecurityInfo extends SecurityInfo implements Cloneable { private final List httpMethodSecurityInfo = new ArrayList<>(); @Override protected ServletSecurityInfo createInstance() { return new ServletSecurityInfo(); } public ServletSecurityInfo addHttpMethodSecurityInfo(final HttpMethodSecurityInfo info) { httpMethodSecurityInfo.add(info); return this; } public List getHttpMethodSecurityInfo() { return new ArrayList<>(httpMethodSecurityInfo); } @Override public ServletSecurityInfo clone() { ServletSecurityInfo info = super.clone(); for(HttpMethodSecurityInfo method : httpMethodSecurityInfo) { info.httpMethodSecurityInfo.add(method.clone()); } return info; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/ServletSessionConfig.java000066400000000000000000000054421420065311100323130ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import java.util.Set; import javax.servlet.SessionTrackingMode; /** * * Session config that gets * * @author Stuart Douglas */ public class ServletSessionConfig { public static final String DEFAULT_SESSION_ID = "JSESSIONID"; private Set sessionTrackingModes; private String name = DEFAULT_SESSION_ID; private String path; private String domain; private boolean secure; private boolean httpOnly; private int maxAge = -1; private String comment; public String getName() { return name; } public ServletSessionConfig setName(final String name) { this.name = name; return this; } public String getDomain() { return domain; } public ServletSessionConfig setDomain(final String domain) { this.domain = domain; return this; } public String getPath() { return path; } public ServletSessionConfig setPath(final String path) { this.path = path; return this; } public String getComment() { return comment; } public ServletSessionConfig setComment(final String comment) { this.comment = comment; return this; } public boolean isHttpOnly() { return httpOnly; } public ServletSessionConfig setHttpOnly(final boolean httpOnly) { this.httpOnly = httpOnly; return this; } public boolean isSecure() { return secure; } public ServletSessionConfig setSecure(final boolean secure) { this.secure = secure; return this; } public int getMaxAge() { return maxAge; } public ServletSessionConfig setMaxAge(final int maxAge) { this.maxAge = maxAge; return this; } public Set getSessionTrackingModes() { return sessionTrackingModes; } public ServletSessionConfig setSessionTrackingModes(final Set sessionTrackingModes) { this.sessionTrackingModes = sessionTrackingModes; return this; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/ServletStackTraces.java000066400000000000000000000020461420065311100317460ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; /** * @author Stuart Douglas */ public enum ServletStackTraces { NONE("none"), LOCAL_ONLY("local-only"), ALL("all"); private final String value; ServletStackTraces(String value) { this.value = value; } @Override public String toString() { return value; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/SessionConfigWrapper.java000066400000000000000000000021471420065311100323060ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import io.undertow.server.session.SessionConfig; /** * A class that allows the SessionConfig to be wrapped. * * This is generally used to append JVM route information to the session ID in clustered environments. * * @author Stuart Douglas */ public interface SessionConfigWrapper { SessionConfig wrap(final SessionConfig sessionConfig, final Deployment deployment); } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/SessionManagerFactory.java000066400000000000000000000017501420065311100324410ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import io.undertow.server.session.SessionManager; /** * Factory class used to create a session manager * * @author Stuart Douglas */ public interface SessionManagerFactory { SessionManager createSessionManager(final Deployment deployment); } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/SessionPersistenceManager.java000066400000000000000000000035701420065311100333200ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import java.util.Collections; import java.util.Date; import java.util.Map; /** * Interface that is used in development mode to support session persistence across redeploys. * * This is not intended for production use. Serialization is performed on a best effort basis and errors will be ignored. * * @author Stuart Douglas */ public interface SessionPersistenceManager { void persistSessions(final String deploymentName, Map sessionData); Map loadSessionAttributes(final String deploymentName, final ClassLoader classLoader); void clear(final String deploymentName); class PersistentSession { private final Date expiration; private final Map sessionData; public PersistentSession(Date expiration, Map sessionData) { this.expiration = expiration; this.sessionData = sessionData; } public Date getExpiration() { return expiration; } public Map getSessionData() { return Collections.unmodifiableMap(sessionData); } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/SingleConstraintMatch.java000066400000000000000000000035041420065311100324350ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import java.util.Set; /** * Representation of a single security constrain matched for a single request. * * When performing any authentication/authorization check every constraint MUST be satisfied for the request to be allowed to * proceed. * * @author Darran Lofthouse */ public class SingleConstraintMatch { private final SecurityInfo.EmptyRoleSemantic emptyRoleSemantic; private final Set requiredRoles; public SingleConstraintMatch(SecurityInfo.EmptyRoleSemantic emptyRoleSemantic, Set requiredRoles) { this.emptyRoleSemantic = emptyRoleSemantic; this.requiredRoles = requiredRoles; } public SecurityInfo.EmptyRoleSemantic getEmptyRoleSemantic() { return emptyRoleSemantic; } public Set getRequiredRoles() { return requiredRoles; } @Override public String toString() { return "SingleConstraintMatch{" + "emptyRoleSemantic=" + emptyRoleSemantic + ", requiredRoles=" + requiredRoles + '}'; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/ThreadSetupAction.java000066400000000000000000000024621420065311100315620ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import io.undertow.server.HttpServerExchange; /** * Interface that can be implemented by classes that need to setup * and thread local context before a request is processed. * * @author Stuart Douglas */ @Deprecated public interface ThreadSetupAction { /** * Setup any thread local context * * @param exchange The exchange, this may be null * @return A handle to tear down the request when the invocation is finished, or null */ Handle setup(final HttpServerExchange exchange); public interface Handle { void tearDown(); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/ThreadSetupHandler.java000066400000000000000000000020101420065311100317070ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import io.undertow.server.HttpServerExchange; /** * @author Stuart Douglas */ public interface ThreadSetupHandler { Action create(Action action); interface Action { T call(HttpServerExchange exchange, C context) throws Exception; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/TransportGuaranteeType.java000066400000000000000000000015541420065311100326670ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; /** * @author Stuart Douglas */ public enum TransportGuaranteeType { NONE, INTEGRAL, CONFIDENTIAL, REJECTED; } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/api/WebResourceCollection.java000066400000000000000000000055211420065311100324340ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.api; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Set; /** * @author Stuart Douglas */ public class WebResourceCollection implements Cloneable { private final Set httpMethods = new HashSet<>(); private final Set httpMethodOmissions = new HashSet<>(); private final Set urlPatterns = new HashSet<>(); public WebResourceCollection addHttpMethod(final String s) { httpMethods.add(s); return this; } public WebResourceCollection addHttpMethods(final String... s) { httpMethods.addAll(Arrays.asList(s)); return this; } public WebResourceCollection addHttpMethods(final Collection s) { httpMethods.addAll(s); return this; } public WebResourceCollection addUrlPattern(final String s) { urlPatterns.add(s); return this; } public WebResourceCollection addUrlPatterns(final String... s) { urlPatterns.addAll(Arrays.asList(s)); return this; } public WebResourceCollection addUrlPatterns(final Collection s) { urlPatterns.addAll(s); return this; } public WebResourceCollection addHttpMethodOmission(final String s) { httpMethodOmissions.add(s); return this; } public WebResourceCollection addHttpMethodOmissions(final String... s) { httpMethodOmissions.addAll(Arrays.asList(s)); return this; } public WebResourceCollection addHttpMethodOmissions(final Collection s) { httpMethodOmissions.addAll(s); return this; } public Set getHttpMethodOmissions() { return httpMethodOmissions; } public Set getUrlPatterns() { return urlPatterns; } public Set getHttpMethods() { return httpMethods; } @Override protected WebResourceCollection clone() { return new WebResourceCollection() .addHttpMethodOmissions(httpMethodOmissions) .addHttpMethods(httpMethods) .addUrlPatterns(urlPatterns); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/attribute/000077500000000000000000000000001420065311100265575ustar00rootroot00000000000000ServletContextAttribute.java000066400000000000000000000054461420065311100342310ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/attribute/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.attribute; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributeBuilder; import io.undertow.attribute.ReadOnlyAttributeException; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.handlers.ServletRequestContext; /** * An attribute in the servlet request * * @author Stuart Douglas */ public class ServletContextAttribute implements ExchangeAttribute { private final String attributeName; public ServletContextAttribute(final String attributeName) { this.attributeName = attributeName; } @Override public String readAttribute(final HttpServerExchange exchange) { ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (context != null) { Object result = context.getCurrentServletContext().getAttribute(attributeName); if (result != null) { return result.toString(); } } return null; } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (context != null) { context.getCurrentServletContext().setAttribute(attributeName, newValue); } } @Override public String toString() { return "%{sc," + attributeName + "}"; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Servlet context attribute"; } @Override public ExchangeAttribute build(final String token) { if (token.startsWith("%{sc,") && token.endsWith("}")) { final String attributeName = token.substring(5, token.length() - 1); return new ServletContextAttribute(attributeName); } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/attribute/ServletNameAttribute.java000066400000000000000000000044771420065311100335470ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.attribute; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributeBuilder; import io.undertow.attribute.ReadOnlyAttributeException; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.handlers.ServletRequestContext; /** * The current servlet name * * @author Stuart Douglas */ public class ServletNameAttribute implements ExchangeAttribute { public static final String SERVLET_NAME = "%{SERVLET_NAME}"; public static final ExchangeAttribute INSTANCE = new ServletNameAttribute(); public static final String NAME = "Servlet Name"; private ServletNameAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); return src.getCurrentServlet().getManagedServlet().getServletInfo().getName(); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException(NAME, newValue); } @Override public String toString() { return SERVLET_NAME; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return NAME; } @Override public ExchangeAttribute build(final String token) { return token.equals(SERVLET_NAME)? INSTANCE : null; } @Override public int priority() { return 0; } } } ServletRelativePathAttribute.java000066400000000000000000000060611420065311100351670ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/attribute/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.attribute; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributeBuilder; import io.undertow.attribute.ReadOnlyAttributeException; import io.undertow.attribute.RelativePathAttribute; import io.undertow.attribute.RequestURLAttribute; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.handlers.ServletRequestContext; import javax.servlet.RequestDispatcher; /** * The relative path * * @author Stuart Douglas */ public class ServletRelativePathAttribute implements ExchangeAttribute { public static final String RELATIVE_PATH_SHORT = "%R"; public static final String RELATIVE_PATH = "%{RELATIVE_PATH}"; public static final ExchangeAttribute INSTANCE = new ServletRelativePathAttribute(); private ServletRelativePathAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if(src == null) { return RequestURLAttribute.INSTANCE.readAttribute(exchange); } String path = (String) src.getServletRequest().getAttribute(RequestDispatcher.FORWARD_PATH_INFO); String sp = (String) src.getServletRequest().getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH); if(path == null && sp == null) { return RequestURLAttribute.INSTANCE.readAttribute(exchange); } if(sp == null) { return path; } else if(path == null) { return sp; } else { return sp + path; } } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { RelativePathAttribute.INSTANCE.writeAttribute(exchange, newValue); } @Override public String toString() { return RELATIVE_PATH; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Relative Path"; } @Override public ExchangeAttribute build(final String token) { return token.equals(RELATIVE_PATH) || token.equals(RELATIVE_PATH_SHORT) ? INSTANCE : null; } @Override public int priority() { return 0; } } } ServletRequestAttribute.java000066400000000000000000000065341420065311100342340ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/attribute/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.attribute; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributeBuilder; import io.undertow.attribute.ReadOnlyAttributeException; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.handlers.ServletRequestContext; import java.util.HashMap; import java.util.Map; /** * An attribute in the servlet request * * @author Stuart Douglas */ public class ServletRequestAttribute implements ExchangeAttribute { private final String attributeName; public ServletRequestAttribute(final String attributeName) { this.attributeName = attributeName; } @Override public String readAttribute(final HttpServerExchange exchange) { ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (context != null) { Object result = context.getServletRequest().getAttribute(attributeName); if (result != null) { return result.toString(); } } else { Map attrs = exchange.getAttachment(HttpServerExchange.REQUEST_ATTRIBUTES); if(attrs != null) { return attrs.get(attributeName); } } return null; } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (context != null) { context.getServletRequest().setAttribute(attributeName, newValue); } else { Map attrs = exchange.getAttachment(HttpServerExchange.REQUEST_ATTRIBUTES); if(attrs == null) { exchange.putAttachment(HttpServerExchange.REQUEST_ATTRIBUTES, attrs = new HashMap<>()); } attrs.put(attributeName, newValue); } } @Override public String toString() { return "%{r," + attributeName + "}"; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Servlet request attribute"; } @Override public ExchangeAttribute build(final String token) { if (token.startsWith("%{r,") && token.endsWith("}")) { final String attributeName = token.substring(4, token.length() - 1); return new ServletRequestAttribute(attributeName); } return null; } @Override public int priority() { return 0; } } } ServletRequestCharacterEncodingAttribute.java000066400000000000000000000050651420065311100375160ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/attribute/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.attribute; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributeBuilder; import io.undertow.attribute.ReadOnlyAttributeException; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.handlers.ServletRequestContext; import javax.servlet.ServletRequest; /** * The request session ID * * @author Stuart Douglas */ public class ServletRequestCharacterEncodingAttribute implements ExchangeAttribute { public static final String REQUEST_CHARACTER_ENCODING = "%{REQUEST_CHARACTER_ENCODING}"; public static final ServletRequestCharacterEncodingAttribute INSTANCE = new ServletRequestCharacterEncodingAttribute(); @Override public String readAttribute(final HttpServerExchange exchange) { ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (context != null) { ServletRequest req = context.getServletRequest(); return req.getCharacterEncoding(); } return null; } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Request Character Encoding", newValue); } @Override public String toString() { return REQUEST_CHARACTER_ENCODING; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Request Character Encoding"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(REQUEST_CHARACTER_ENCODING)) { return INSTANCE; } return null; } @Override public int priority() { return 0; } } } ServletRequestLineAttribute.java000066400000000000000000000064301420065311100350370ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/attribute/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.attribute; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributeBuilder; import io.undertow.attribute.ReadOnlyAttributeException; import io.undertow.attribute.RequestLineAttribute; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.handlers.ServletRequestContext; import javax.servlet.RequestDispatcher; /** * The request line * * @author Stuart Douglas */ public class ServletRequestLineAttribute implements ExchangeAttribute { public static final String REQUEST_LINE_SHORT = "%r"; public static final String REQUEST_LINE = "%{REQUEST_LINE}"; public static final ExchangeAttribute INSTANCE = new ServletRequestLineAttribute(); private ServletRequestLineAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (src == null) { return RequestLineAttribute.INSTANCE.readAttribute(exchange); } StringBuilder sb = new StringBuilder() .append(exchange.getRequestMethod().toString()) .append(' ') .append(ServletRequestURLAttribute.INSTANCE.readAttribute(exchange)); String query = (String) src.getServletRequest().getAttribute(RequestDispatcher.FORWARD_QUERY_STRING); if (query != null && !query.isEmpty()) { sb.append('?'); sb.append(query); } else if (!exchange.getQueryString().isEmpty()) { sb.append('?'); sb.append(exchange.getQueryString()); } sb.append(' ') .append(exchange.getProtocol().toString()).toString(); return sb.toString(); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Request line", newValue); } @Override public String toString() { return REQUEST_LINE; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Request line"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(REQUEST_LINE) || token.equals(REQUEST_LINE_SHORT)) { return ServletRequestLineAttribute.INSTANCE; } return null; } @Override public int priority() { return 1; } } } ServletRequestLocaleAttribute.java000066400000000000000000000047041420065311100353510ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/attribute/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.attribute; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributeBuilder; import io.undertow.attribute.ReadOnlyAttributeException; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.handlers.ServletRequestContext; import javax.servlet.ServletRequest; /** * The request session ID * * @author Stuart Douglas */ public class ServletRequestLocaleAttribute implements ExchangeAttribute { public static final String REQUEST_LOCALE = "%{REQUEST_LOCALE}"; public static final ServletRequestLocaleAttribute INSTANCE = new ServletRequestLocaleAttribute(); @Override public String readAttribute(final HttpServerExchange exchange) { ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (context != null) { ServletRequest req = context.getServletRequest(); return req.getLocale().toString(); } return null; } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Locale", newValue); } @Override public String toString() { return REQUEST_LOCALE; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Request Locale"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(REQUEST_LOCALE)) { return INSTANCE; } return null; } @Override public int priority() { return 0; } } } ServletRequestParameterAttribute.java000066400000000000000000000052051420065311100360670ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/attribute/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.attribute; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributeBuilder; import io.undertow.attribute.ReadOnlyAttributeException; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.handlers.ServletRequestContext; /** * An attribute in the servlet request * * @author Stuart Douglas */ public class ServletRequestParameterAttribute implements ExchangeAttribute { private final String attributeName; public ServletRequestParameterAttribute(final String attributeName) { this.attributeName = attributeName; } @Override public String readAttribute(final HttpServerExchange exchange) { ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (context != null) { Object result = context.getServletRequest().getParameter(attributeName); if (result != null) { return result.toString(); } } return null; } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException(); } @Override public String toString() { return "%{rp," + attributeName + "}"; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Servlet request parameter"; } @Override public ExchangeAttribute build(final String token) { if (token.startsWith("%{rp,") && token.endsWith("}")) { final String attributeName = token.substring(5, token.length() - 1); return new ServletRequestParameterAttribute(attributeName); } return null; } @Override public int priority() { return 0; } } } ServletRequestURLAttribute.java000066400000000000000000000057151420065311100346170ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/attribute/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.attribute; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributeBuilder; import io.undertow.attribute.ReadOnlyAttributeException; import io.undertow.attribute.RequestURLAttribute; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.handlers.ServletRequestContext; import javax.servlet.RequestDispatcher; /** * The request URL * * @author Stuart Douglas */ public class ServletRequestURLAttribute implements ExchangeAttribute { public static final String REQUEST_URL_SHORT = "%U"; public static final String REQUEST_URL = "%{REQUEST_URL}"; public static final ExchangeAttribute INSTANCE = new ServletRequestURLAttribute(); private ServletRequestURLAttribute() { } @Override public String readAttribute(final HttpServerExchange exchange) { ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (src == null) { return RequestURLAttribute.INSTANCE.readAttribute(exchange); } String uri = (String) src.getServletRequest().getAttribute(RequestDispatcher.FORWARD_REQUEST_URI); if (uri != null) { return uri; } uri = (String) src.getServletRequest().getAttribute(RequestDispatcher.ERROR_REQUEST_URI); if (uri != null) { return uri; } return RequestURLAttribute.INSTANCE.readAttribute(exchange); } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { RequestURLAttribute.INSTANCE.writeAttribute(exchange, newValue); } @Override public String toString() { return REQUEST_URL; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Request URL"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(REQUEST_URL) || token.equals(REQUEST_URL_SHORT)) { return ServletRequestURLAttribute.INSTANCE; } return null; } @Override public int priority() { return 1; } } } ServletRequestedSessionIdAttribute.java000066400000000000000000000052141420065311100363600ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/attribute/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.attribute; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributeBuilder; import io.undertow.attribute.ReadOnlyAttributeException; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.handlers.ServletRequestContext; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; /** * The request session ID * * @author Stuart Douglas */ public class ServletRequestedSessionIdAttribute implements ExchangeAttribute { public static final String REQUESTED_SESSION_ID = "%{REQUESTED_SESSION_ID}"; public static final ServletRequestedSessionIdAttribute INSTANCE = new ServletRequestedSessionIdAttribute(); @Override public String readAttribute(final HttpServerExchange exchange) { ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (context != null) { ServletRequest req = context.getServletRequest(); if (req instanceof HttpServletRequest) { return ((HttpServletRequest) req).getRequestedSessionId(); } } return null; } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Session ID", newValue); } @Override public String toString() { return REQUESTED_SESSION_ID; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Requested Session ID attribute"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(REQUESTED_SESSION_ID)) { return INSTANCE; } return null; } @Override public int priority() { return 0; } } } ServletRequestedSessionIdFromCookieAttribute.java000066400000000000000000000054271420065311100403440ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/attribute/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.attribute; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributeBuilder; import io.undertow.attribute.ReadOnlyAttributeException; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.handlers.ServletRequestContext; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; /** * The request session ID * * @author Stuart Douglas */ public class ServletRequestedSessionIdFromCookieAttribute implements ExchangeAttribute { public static final String REQUESTED_SESSION_ID_FROM_COOKIE = "%{REQUESTED_SESSION_ID_FROM_COOKIE}"; public static final ServletRequestedSessionIdFromCookieAttribute INSTANCE = new ServletRequestedSessionIdFromCookieAttribute(); @Override public String readAttribute(final HttpServerExchange exchange) { ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (context != null) { ServletRequest req = context.getServletRequest(); if (req instanceof HttpServletRequest) { return Boolean.toString(((HttpServletRequest) req).isRequestedSessionIdFromCookie()); } } return null; } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Requested session ID from cookie", newValue); } @Override public String toString() { return REQUESTED_SESSION_ID_FROM_COOKIE; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Requested Session ID from cookie attribute"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(REQUESTED_SESSION_ID_FROM_COOKIE)) { return INSTANCE; } return null; } @Override public int priority() { return 0; } } } ServletRequestedSessionIdValidAttribute.java000066400000000000000000000053531420065311100373440ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/attribute/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.attribute; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributeBuilder; import io.undertow.attribute.ReadOnlyAttributeException; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.handlers.ServletRequestContext; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; /** * The request session ID * * @author Stuart Douglas */ public class ServletRequestedSessionIdValidAttribute implements ExchangeAttribute { public static final String REQUESTED_SESSION_ID_VALID = "%{REQUESTED_SESSION_ID_VALID}"; public static final ServletRequestedSessionIdValidAttribute INSTANCE = new ServletRequestedSessionIdValidAttribute(); @Override public String readAttribute(final HttpServerExchange exchange) { ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (context != null) { ServletRequest req = context.getServletRequest(); if (req instanceof HttpServletRequest) { return Boolean.toString(((HttpServletRequest) req).isRequestedSessionIdValid()); } } return null; } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Requested session ID from cookie", newValue); } @Override public String toString() { return REQUESTED_SESSION_ID_VALID; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Requested Session ID from cookie attribute"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(REQUESTED_SESSION_ID_VALID)) { return INSTANCE; } return null; } @Override public int priority() { return 0; } } } ServletSessionAttribute.java000066400000000000000000000066551420065311100342330ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/attribute/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.attribute; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributeBuilder; import io.undertow.attribute.ReadOnlyAttributeException; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.handlers.ServletRequestContext; /** * An attribute in the servlet request * * @author Stuart Douglas */ public class ServletSessionAttribute implements ExchangeAttribute { private final String attributeName; public ServletSessionAttribute(final String attributeName) { this.attributeName = attributeName; } @Override public String readAttribute(final HttpServerExchange exchange) { ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (context != null) { ServletRequest req = context.getServletRequest(); if (req instanceof HttpServletRequest) { HttpSession session = ((HttpServletRequest) req).getSession(false); if (session != null) { Object result = session.getAttribute(attributeName); if (result != null) { return result.toString(); } } } } return null; } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (context != null) { ServletRequest req = context.getServletRequest(); if (req instanceof HttpServletRequest) { HttpSession session = ((HttpServletRequest) req).getSession(false); if (session != null) { session.setAttribute(attributeName, newValue); } } } } @Override public String toString() { return "%{s," + attributeName + "}"; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Servlet session attribute"; } @Override public ExchangeAttribute build(final String token) { if (token.startsWith("%{s,") && token.endsWith("}")) { final String attributeName = token.substring(4, token.length() - 1); return new ServletSessionAttribute(attributeName); } return null; } @Override public int priority() { return 0; } } } ServletSessionIdAttribute.java000066400000000000000000000054561420065311100345060ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/attribute/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.attribute; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributeBuilder; import io.undertow.attribute.ReadOnlyAttributeException; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.handlers.ServletRequestContext; /** * The request session ID * * @author Stuart Douglas */ public class ServletSessionIdAttribute implements ExchangeAttribute { public static final String SESSION_ID_SHORT = "%S"; public static final String SESSION_ID = "%{SESSION_ID}"; public static final ServletSessionIdAttribute INSTANCE = new ServletSessionIdAttribute(); @Override public String readAttribute(final HttpServerExchange exchange) { ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (context != null) { ServletRequest req = context.getServletRequest(); if (req instanceof HttpServletRequest) { HttpSession session = ((HttpServletRequest) req).getSession(false); if (session != null) { return session.getId(); } } } return null; } @Override public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { throw new ReadOnlyAttributeException("Session ID", newValue); } @Override public String toString() { return SESSION_ID; } public static final class Builder implements ExchangeAttributeBuilder { @Override public String name() { return "Session ID attribute"; } @Override public ExchangeAttribute build(final String token) { if (token.equals(SESSION_ID) || token.equals(SESSION_ID_SHORT)) { return INSTANCE; } return null; } @Override public int priority() { return 0; } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/compat/000077500000000000000000000000001420065311100260375ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/compat/rewrite/000077500000000000000000000000001420065311100275205ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/compat/rewrite/Resolver.java000066400000000000000000000022231420065311100321630ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.compat.rewrite; /** * Resolver abstract class. * * @author Remy Maucherat */ public abstract class Resolver { public abstract String resolve(String key); public String resolveEnv(String key) { return System.getProperty(key); } public abstract String resolveSsl(String key); public abstract String resolveHttp(String key); public abstract boolean resolveResource(int type, String name); }undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/compat/rewrite/RewriteCond.java000066400000000000000000000215331420065311100326140ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.compat.rewrite; import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author Remy Maucherat */ public class RewriteCond { public abstract static class Condition { public abstract boolean evaluate(String value, Resolver resolver); } public static class PatternCondition extends Condition { public Pattern pattern; public Matcher matcher = null; public boolean evaluate(String value, Resolver resolver) { Matcher m = pattern.matcher(value); if (m.matches()) { matcher = m; return true; } else { return false; } } } public static class LexicalCondition extends Condition { /** * -1: < * 0: = * 1: > */ public int type = 0; public String condition; public boolean evaluate(String value, Resolver resolver) { int result = value.compareTo(condition); switch (type) { case -1: return (result < 0); case 0: return (result == 0); case 1: return (result > 0); default: return false; } } } public static class ResourceCondition extends Condition { /** * 0: -d (is directory ?) * 1: -f (is regular file ?) * 2: -s (is regular file with size ?) */ public int type = 0; public boolean evaluate(String value, Resolver resolver) { switch (type) { case 0: return true; case 1: return true; case 2: return true; default: return false; } } } protected String testString = null; protected String condPattern = null; public String getCondPattern() { return condPattern; } public void setCondPattern(String condPattern) { this.condPattern = condPattern; } public String getTestString() { return testString; } public void setTestString(String testString) { this.testString = testString; } public void parse(Map maps) { test = new Substitution(); test.setSub(testString); test.parse(maps); if (condPattern.startsWith("!")) { positive = false; condPattern = condPattern.substring(1); } // The counted condition is never anywhere assigned, there is a question whether it shouldn't look more like evaluate method. // commenting it out as this code is taken from Tomcats, where it lives for quite long time without change. // if (condPattern.startsWith("<")) { // LexicalCondition condition = new LexicalCondition(); // condition.type = -1; // condition.condition = condPattern.substring(1); // } else if (condPattern.startsWith(">")) { // LexicalCondition condition = new LexicalCondition(); // condition.type = 1; // condition.condition = condPattern.substring(1); // } else if (condPattern.startsWith("=")) { // LexicalCondition condition = new LexicalCondition(); // condition.type = 0; // condition.condition = condPattern.substring(1); // } else if (condPattern.equals("-d")) { // ResourceCondition ncondition = new ResourceCondition(); // ncondition.type = 0; // } else if (condPattern.equals("-f")) { // ResourceCondition ncondition = new ResourceCondition(); // ncondition.type = 1; // } else if (condPattern.equals("-s")) { // ResourceCondition ncondition = new ResourceCondition(); // ncondition.type = 2; // } else { // PatternCondition condition = new PatternCondition(); // int flags = 0; // if (isNocase()) { // flags |= Pattern.CASE_INSENSITIVE; // } // condition.pattern = Pattern.compile(condPattern, flags); // } } public Matcher getMatcher() { Object condition = this.condition.get(); if (condition instanceof PatternCondition) { return ((PatternCondition) condition).matcher; } return null; } /** * String representation. */ public String toString() { // FIXME: Add flags if possible return "RewriteCond " + testString + " " + condPattern; } protected boolean positive = true; protected Substitution test = null; protected ThreadLocal condition = new ThreadLocal(); /** * This makes the test case-insensitive, i.e., there is no difference between * 'A-Z' and 'a-z' both in the expanded TestString and the CondPattern. This * flag is effective only for comparisons between TestString and CondPattern. * It has no effect on filesystem and subrequest checks. */ public boolean nocase = false; /** * Use this to combine rule conditions with a local OR instead of the implicit AND. */ public boolean ornext = false; /** * Evaluate the condition based on the context * * @param rule corresponding matched rule * @param cond last matched condition * @return */ public boolean evaluate(Matcher rule, Matcher cond, Resolver resolver) { String value = test.evaluate(rule, cond, resolver); if (nocase) { value = value.toLowerCase(Locale.ENGLISH); } Condition condition = this.condition.get(); if (condition == null) { if (condPattern.startsWith("<")) { LexicalCondition ncondition = new LexicalCondition(); ncondition.type = -1; ncondition.condition = condPattern.substring(1); condition = ncondition; } else if (condPattern.startsWith(">")) { LexicalCondition ncondition = new LexicalCondition(); ncondition.type = 1; ncondition.condition = condPattern.substring(1); condition = ncondition; } else if (condPattern.startsWith("=")) { LexicalCondition ncondition = new LexicalCondition(); ncondition.type = 0; ncondition.condition = condPattern.substring(1); condition = ncondition; } else if (condPattern.equals("-d")) { ResourceCondition ncondition = new ResourceCondition(); ncondition.type = 0; condition = ncondition; } else if (condPattern.equals("-f")) { ResourceCondition ncondition = new ResourceCondition(); ncondition.type = 1; condition = ncondition; } else if (condPattern.equals("-s")) { ResourceCondition ncondition = new ResourceCondition(); ncondition.type = 2; condition = ncondition; } else { PatternCondition ncondition = new PatternCondition(); int flags = 0; if (isNocase()) { flags |= Pattern.CASE_INSENSITIVE; } ncondition.pattern = Pattern.compile(condPattern, flags); condition = ncondition; } this.condition.set(condition); } if (positive) { return condition.evaluate(value, resolver); } else { return !condition.evaluate(value, resolver); } } public boolean isNocase() { return nocase; } public void setNocase(boolean nocase) { this.nocase = nocase; } public boolean isOrnext() { return ornext; } public void setOrnext(boolean ornext) { this.ornext = ornext; } public boolean isPositive() { return positive; } public void setPositive(boolean positive) { this.positive = positive; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/compat/rewrite/RewriteConfig.java000066400000000000000000000036471420065311100331440ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.compat.rewrite; import java.util.Map; /** * The configuration for a {@link io.undertow.servlet.compat.rewrite.RewriteHandler}. This should be produced by * {@link RewriteConfigFactory} * * @author Stuart Douglas */ public class RewriteConfig { /** * The rewrite rules that the valve will use. */ private final RewriteRule[] rules; /** * Maps to be used by the rules. */ private final Map maps; public RewriteConfig(RewriteRule[] rules, Map maps) { this.rules = rules; this.maps = maps; } public RewriteRule[] getRules() { return rules; } public Map getMaps() { return maps; } public String toString() { StringBuffer buffer = new StringBuffer(); // FIXME: Output maps if possible for (int i = 0; i < rules.length; i++) { for (int j = 0; j < rules[i].getConditions().length; j++) { buffer.append(rules[i].getConditions()[j].toString()).append("\r\n"); } buffer.append(rules[i].toString()).append("\r\n").append("\r\n"); } return buffer.toString(); } } RewriteConfigFactory.java000066400000000000000000000325071420065311100344120ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/compat/rewrite/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.compat.rewrite; import io.undertow.servlet.UndertowServletLogger; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.StringTokenizer; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class RewriteConfigFactory { public static RewriteConfig build(InputStream inputStream) { BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); try { return parse(reader); } finally { try { reader.close(); } catch (IOException e) { } } } private static RewriteConfig parse(BufferedReader reader) { ArrayList rules = new ArrayList(); ArrayList conditions = new ArrayList(); Map maps = new HashMap<>(); while (true) { try { String line = reader.readLine(); if (line == null) { break; } Object result = parse(line); if (result instanceof RewriteRule) { RewriteRule rule = (RewriteRule) result; if (UndertowServletLogger.ROOT_LOGGER.isDebugEnabled()) { UndertowServletLogger.ROOT_LOGGER.debug("Add rule with pattern " + rule.getPatternString() + " and substitution " + rule.getSubstitutionString()); } for (int i = (conditions.size() - 1); i > 0; i--) { if (conditions.get(i - 1).isOrnext()) { conditions.get(i).setOrnext(true); } } for (int i = 0; i < conditions.size(); i++) { if (UndertowServletLogger.ROOT_LOGGER.isDebugEnabled()) { RewriteCond cond = conditions.get(i); UndertowServletLogger.ROOT_LOGGER.debug("Add condition " + cond.getCondPattern() + " test " + cond.getTestString() + " to rule with pattern " + rule.getPatternString() + " and substitution " + rule.getSubstitutionString() + (cond.isOrnext() ? " [OR]" : "") + (cond.isNocase() ? " [NC]" : "")); } rule.addCondition(conditions.get(i)); } conditions.clear(); rules.add(rule); } else if (result instanceof RewriteCond) { conditions.add((RewriteCond) result); } else if (result instanceof Object[]) { String mapName = (String) ((Object[]) result)[0]; RewriteMap map = (RewriteMap) ((Object[]) result)[1]; maps.put(mapName, map); //if (map instanceof Lifecycle) { // ((Lifecycle) map).start(); //} } } catch (IOException e) { UndertowServletLogger.ROOT_LOGGER.errorReadingRewriteConfiguration(e); } } RewriteRule[] rulesArray = rules.toArray(new RewriteRule[0]); // Finish parsing the rules for (int i = 0; i < rulesArray.length; i++) { rulesArray[i].parse(maps); } return new RewriteConfig(rulesArray, maps); } /** * This factory method will parse a line formed like: * * Example: * RewriteCond %{REMOTE_HOST} ^host1.* [OR] * * @param line * @return */ private static Object parse(String line) { StringTokenizer tokenizer = new StringTokenizer(line); if (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (token.equals("RewriteCond")) { // RewriteCond TestString CondPattern [Flags] RewriteCond condition = new RewriteCond(); if (tokenizer.countTokens() < 2) { throw UndertowServletLogger.ROOT_LOGGER.invalidRewriteConfiguration(line); } condition.setTestString(tokenizer.nextToken()); condition.setCondPattern(tokenizer.nextToken()); if (tokenizer.hasMoreTokens()) { String flags = tokenizer.nextToken(); if (flags.startsWith("[") && flags.endsWith("]")) { flags = flags.substring(1, flags.length() - 1); } StringTokenizer flagsTokenizer = new StringTokenizer(flags, ","); while (flagsTokenizer.hasMoreElements()) { parseCondFlag(line, condition, flagsTokenizer.nextToken()); } } return condition; } else if (token.equals("RewriteRule")) { // RewriteRule Pattern Substitution [Flags] RewriteRule rule = new RewriteRule(); if (tokenizer.countTokens() < 2) { throw UndertowServletLogger.ROOT_LOGGER.invalidRewriteConfiguration(line); } rule.setPatternString(tokenizer.nextToken()); rule.setSubstitutionString(tokenizer.nextToken()); if (tokenizer.hasMoreTokens()) { String flags = tokenizer.nextToken(); if (flags.startsWith("[") && flags.endsWith("]")) { flags = flags.substring(1, flags.length() - 1); } StringTokenizer flagsTokenizer = new StringTokenizer(flags, ","); while (flagsTokenizer.hasMoreElements()) { parseRuleFlag(line, rule, flagsTokenizer.nextToken()); } } return rule; } else if (token.equals("RewriteMap")) { // RewriteMap name rewriteMapClassName whateverOptionalParameterInWhateverFormat if (tokenizer.countTokens() < 2) { throw UndertowServletLogger.ROOT_LOGGER.invalidRewriteConfiguration(line); } String name = tokenizer.nextToken(); String rewriteMapClassName = tokenizer.nextToken(); RewriteMap map = null; try { map = (RewriteMap) (Class.forName(rewriteMapClassName).newInstance()); } catch (Exception e) { throw UndertowServletLogger.ROOT_LOGGER.invalidRewriteMap(rewriteMapClassName); } if (tokenizer.hasMoreTokens()) { map.setParameters(tokenizer.nextToken()); } Object[] result = new Object[2]; result[0] = name; result[1] = map; return result; } else if (token.startsWith("#")) { // it's a comment, ignore it } else { throw UndertowServletLogger.ROOT_LOGGER.invalidRewriteConfiguration(line); } } return null; } /** * Parser for RewriteCond flags. * * @param condition * @param flag */ protected static void parseCondFlag(String line, RewriteCond condition, String flag) { if (flag.equals("NC") || flag.equals("nocase")) { condition.setNocase(true); } else if (flag.equals("OR") || flag.equals("ornext")) { condition.setOrnext(true); } else { throw UndertowServletLogger.ROOT_LOGGER.invalidRewriteFlags(line, flag); } } /** * Parser for ReweriteRule flags. * * @param rule * @param flag */ protected static void parseRuleFlag(String line, RewriteRule rule, String flag) { if (flag.equals("chain") || flag.equals("C")) { rule.setChain(true); } else if (flag.startsWith("cookie=") || flag.startsWith("CO=")) { rule.setCookie(true); if (flag.startsWith("cookie")) { flag = flag.substring("cookie=".length()); } else if (flag.startsWith("CO=")) { flag = flag.substring("CO=".length()); } StringTokenizer tokenizer = new StringTokenizer(flag, ":"); if (tokenizer.countTokens() < 2) { throw UndertowServletLogger.ROOT_LOGGER.invalidRewriteFlags(line); } rule.setCookieName(tokenizer.nextToken()); rule.setCookieValue(tokenizer.nextToken()); if (tokenizer.hasMoreTokens()) { rule.setCookieDomain(tokenizer.nextToken()); } if (tokenizer.hasMoreTokens()) { try { rule.setCookieLifetime(Integer.parseInt(tokenizer.nextToken())); } catch (NumberFormatException e) { throw UndertowServletLogger.ROOT_LOGGER.invalidRewriteFlags(line); } } if (tokenizer.hasMoreTokens()) { rule.setCookiePath(tokenizer.nextToken()); } if (tokenizer.hasMoreTokens()) { rule.setCookieSecure(Boolean.parseBoolean(tokenizer.nextToken())); } if (tokenizer.hasMoreTokens()) { rule.setCookieHttpOnly(Boolean.parseBoolean(tokenizer.nextToken())); } } else if (flag.startsWith("env=") || flag.startsWith("E=")) { rule.setEnv(true); if (flag.startsWith("env=")) { flag = flag.substring("env=".length()); } else if (flag.startsWith("E=")) { flag = flag.substring("E=".length()); } int pos = flag.indexOf(':'); if (pos == -1 || (pos + 1) == flag.length()) { throw UndertowServletLogger.ROOT_LOGGER.invalidRewriteFlags(line); } rule.addEnvName(flag.substring(0, pos)); rule.addEnvValue(flag.substring(pos + 1)); } else if (flag.startsWith("forbidden") || flag.startsWith("F")) { rule.setForbidden(true); } else if (flag.startsWith("gone") || flag.startsWith("G")) { rule.setGone(true); } else if (flag.startsWith("host") || flag.startsWith("H")) { rule.setHost(true); } else if (flag.startsWith("last") || flag.startsWith("L")) { rule.setLast(true); } else if (flag.startsWith("next") || flag.startsWith("N")) { rule.setNext(true); } else if (flag.startsWith("nocase") || flag.startsWith("NC")) { rule.setNocase(true); } else if (flag.startsWith("noescape") || flag.startsWith("NE")) { rule.setNoescape(true); /* Proxy not supported, would require strong proxy capabilities } else if (flag.startsWith("proxy") || flag.startsWith("P")) { rule.setProxy(true);*/ } else if (flag.startsWith("qsappend") || flag.startsWith("QSA")) { rule.setQsappend(true); } else if (flag.startsWith("redirect") || flag.startsWith("R")) { if (flag.startsWith("redirect=")) { flag = flag.substring("redirect=".length()); rule.setRedirect(true); rule.setRedirectCode(Integer.parseInt(flag)); } else if (flag.startsWith("R=")) { flag = flag.substring("R=".length()); rule.setRedirect(true); rule.setRedirectCode(Integer.parseInt(flag)); } else { rule.setRedirect(true); rule.setRedirectCode(HttpServletResponse.SC_FOUND); } } else if (flag.startsWith("skip") || flag.startsWith("S")) { if (flag.startsWith("skip=")) { flag = flag.substring("skip=".length()); } else if (flag.startsWith("S=")) { flag = flag.substring("S=".length()); } rule.setSkip(Integer.parseInt(flag)); } else if (flag.startsWith("type") || flag.startsWith("T")) { if (flag.startsWith("type=")) { flag = flag.substring("type=".length()); } else if (flag.startsWith("T=")) { flag = flag.substring("T=".length()); } rule.setType(true); rule.setTypeValue(flag); } else { throw UndertowServletLogger.ROOT_LOGGER.invalidRewriteFlags(line, flag); } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/compat/rewrite/RewriteHandler.java000066400000000000000000000257351420065311100333160ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.compat.rewrite; import io.undertow.UndertowOptions; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.UndertowServletLogger; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.servlet.spec.HttpServletRequestImpl; import io.undertow.servlet.spec.HttpServletResponseImpl; import io.undertow.util.Headers; import io.undertow.util.QueryParameterUtils; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; /** * @author Remy Maucherat */ public class RewriteHandler implements HttpHandler { private final RewriteConfig config; private final HttpHandler next; /** * If rewriting occurs, the whole request will be processed again. */ protected ThreadLocal invoked = new ThreadLocal<>(); public RewriteHandler(RewriteConfig config, HttpHandler next) { this.config = config; this.next = next; } public void handleRequest(HttpServerExchange exchange) throws Exception { RewriteRule[] rules = config.getRules(); if (rules == null || rules.length == 0) { next.handleRequest(exchange); return; } if (Boolean.TRUE.equals(invoked.get())) { next.handleRequest(exchange); invoked.set(null); return; } ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); HttpServletRequestImpl request = src.getOriginalRequest(); HttpServletResponseImpl response = src.getOriginalResponse(); UndertowResolver resolver = new UndertowResolver(src, src.getOriginalRequest()); invoked.set(Boolean.TRUE); // As long as MB isn't a char sequence or affiliated, this has to be // converted to a string CharSequence url = exchange.getRelativePath(); CharSequence host = request.getServerName(); boolean rewritten = false; boolean done = false; for (int i = 0; i < rules.length; i++) { CharSequence test = (rules[i].isHost()) ? host : url; CharSequence newtest = rules[i].evaluate(test, resolver); if (newtest != null && !test.equals(newtest.toString())) { if (UndertowServletLogger.REQUEST_LOGGER.isDebugEnabled()) { UndertowServletLogger.REQUEST_LOGGER.debug("Rewrote " + test + " as " + newtest + " with rule pattern " + rules[i].getPatternString()); } if (rules[i].isHost()) { host = newtest; } else { url = newtest; } rewritten = true; } // Final reply // - forbidden if (rules[i].isForbidden() && newtest != null) { response.sendError(HttpServletResponse.SC_FORBIDDEN); done = true; break; } // - gone if (rules[i].isGone() && newtest != null) { response.sendError(HttpServletResponse.SC_GONE); done = true; break; } // - redirect (code) if (rules[i].isRedirect() && newtest != null) { // append the query string to the url if there is one and it hasn't been rewritten String queryString = request.getQueryString(); StringBuffer urlString = new StringBuffer(url); if (queryString != null && queryString.length() > 0) { int index = urlString.indexOf("?"); if (index != -1) { // if qsa is specified append the query if (rules[i].isQsappend()) { urlString.append('&'); urlString.append(queryString); } // if the ? is the last character delete it, its only purpose was to // prevent the rewrite module from appending the query string else if (index == urlString.length() - 1) { urlString.deleteCharAt(index); } } else { urlString.append('?'); urlString.append(queryString); } } // Insert the context if // 1. this valve is associated with a context // 2. the url starts with a leading slash // 3. the url isn't absolute if (urlString.charAt(0) == '/' && !hasScheme(urlString)) { urlString.insert(0, request.getContextPath()); } response.sendRedirect(urlString.toString()); response.setStatus(rules[i].getRedirectCode()); done = true; break; } // Reply modification // - cookie if (rules[i].isCookie() && newtest != null) { Cookie cookie = new Cookie(rules[i].getCookieName(), rules[i].getCookieResult()); cookie.setDomain(rules[i].getCookieDomain()); cookie.setMaxAge(rules[i].getCookieLifetime()); cookie.setPath(rules[i].getCookiePath()); cookie.setSecure(rules[i].isCookieSecure()); cookie.setHttpOnly(rules[i].isCookieHttpOnly()); response.addCookie(cookie); } // - env (note: this sets a request attribute) if (rules[i].isEnv() && newtest != null) { Map attrs = exchange.getAttachment(HttpServerExchange.REQUEST_ATTRIBUTES); if (attrs == null) { attrs = new HashMap<>(); exchange.putAttachment(HttpServerExchange.REQUEST_ATTRIBUTES, attrs); } for (int j = 0; j < rules[i].getEnvSize(); j++) { final String envName = rules[i].getEnvName(j); final String envResult = rules[i].getEnvResult(j); attrs.put(envName, envResult); request.setAttribute(envName, envResult); } } // - content type (note: this will not force the content type, use a filter // to do that) if (rules[i].isType() && newtest != null) { exchange.getRequestHeaders().put(Headers.CONTENT_TYPE, rules[i].getTypeValue()); } // - qsappend if (rules[i].isQsappend() && newtest != null) { String queryString = request.getQueryString(); String urlString = url.toString(); if (urlString.indexOf('?') != -1 && queryString != null) { url = urlString + "&" + queryString; } } // Control flow processing // - chain (skip remaining chained rules if this one does not match) if (rules[i].isChain() && newtest == null) { for (int j = i; j < rules.length; j++) { if (!rules[j].isChain()) { i = j; break; } } continue; } // - last (stop rewriting here) if (rules[i].isLast() && newtest != null) { break; } // - next (redo again) if (rules[i].isNext() && newtest != null) { i = 0; continue; } // - skip (n rules) if (newtest != null) { i += rules[i].getSkip(); } } if (rewritten) { if (!done) { // See if we need to replace the query string String urlString = url.toString(); String queryString = null; int queryIndex = urlString.indexOf('?'); if (queryIndex != -1) { queryString = urlString.substring(queryIndex + 1); urlString = urlString.substring(0, queryIndex); } // Set the new URL StringBuilder chunk = new StringBuilder(); chunk.append(request.getContextPath()); chunk.append(urlString); String requestPath = chunk.toString(); exchange.setRequestURI(requestPath); exchange.setRequestPath(requestPath); exchange.setRelativePath(urlString); // Set the new Query if there is one if (queryString != null) { exchange.setQueryString(queryString); exchange.getQueryParameters().clear(); exchange.getQueryParameters().putAll(QueryParameterUtils.parseQueryString(queryString, exchange.getConnection().getUndertowOptions().get(UndertowOptions.URL_CHARSET, StandardCharsets.UTF_8.name()))); } // Set the new host if it changed if (!host.equals(request.getServerName())) { exchange.getRequestHeaders().put(Headers.HOST, host + ":" + exchange.getHostPort()); } // Reinvoke the whole request recursively src.getDeployment().getHandler().handleRequest(exchange); } } else { next.handleRequest(exchange); } invoked.set(null); } /** * Determine if a URI string has a scheme component. */ protected static boolean hasScheme(StringBuffer uri) { int len = uri.length(); for (int i = 0; i < len; i++) { char c = uri.charAt(i); if (c == ':') { return i > 0; } else if (!isSchemeChar(c)) { return false; } } return false; } /** * Determine if the character is allowed in the scheme of a URI. * See RFC 2396, Section 3.1 */ private static boolean isSchemeChar(char c) { return Character.isLetterOrDigit(c) || c == '+' || c == '-' || c == '.'; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/compat/rewrite/RewriteMap.java000066400000000000000000000016031420065311100324420ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.compat.rewrite; /** * @author Remy Maucherat */ public interface RewriteMap { String setParameters(String params); String lookup(String key); } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/compat/rewrite/RewriteRule.java000066400000000000000000000437311420065311100326440ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.compat.rewrite; import java.util.ArrayList; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author Remy Maucherat */ public class RewriteRule { protected RewriteCond[] conditions = new RewriteCond[0]; protected ThreadLocal pattern = new ThreadLocal(); protected Substitution substitution = null; protected String patternString = null; protected String substitutionString = null; public void parse(Map maps) { // Parse the substitution if (!"-".equals(substitutionString)) { substitution = new Substitution(); substitution.setSub(substitutionString); substitution.parse(maps); } // Parse the pattern int flags = 0; if (isNocase()) { flags |= Pattern.CASE_INSENSITIVE; } Pattern.compile(patternString, flags); // Parse conditions for (int i = 0; i < conditions.length; i++) { conditions[i].parse(maps); } // Parse flag which have substitution values if (isEnv()) { for (int i = 0; i < envValue.size(); i++) { Substitution newEnvSubstitution = new Substitution(); newEnvSubstitution.setSub(envValue.get(i)); newEnvSubstitution.parse(maps); envSubstitution.add(newEnvSubstitution); envResult.add(new ThreadLocal()); } } if (isCookie()) { cookieSubstitution = new Substitution(); cookieSubstitution.setSub(cookieValue); cookieSubstitution.parse(maps); } } public void addCondition(RewriteCond condition) { RewriteCond[] conditions = new RewriteCond[this.conditions.length + 1]; for (int i = 0; i < this.conditions.length; i++) { conditions[i] = this.conditions[i]; } conditions[this.conditions.length] = condition; this.conditions = conditions; } /** * Evaluate the rule based on the context * * @return null if no rewrite took place */ public CharSequence evaluate(CharSequence url, Resolver resolver) { Pattern pattern = this.pattern.get(); if (pattern == null) { // Parse the pattern int flags = 0; if (isNocase()) { flags |= Pattern.CASE_INSENSITIVE; } pattern = Pattern.compile(patternString, flags); this.pattern.set(pattern); } Matcher matcher = pattern.matcher(url); if (!matcher.matches()) { // Evaluation done return null; } // Evaluate conditions boolean done = false; boolean rewrite = true; Matcher lastMatcher = null; int pos = 0; while (!done) { if (pos < conditions.length) { rewrite = conditions[pos].evaluate(matcher, lastMatcher, resolver); if (rewrite) { Matcher lastMatcher2 = conditions[pos].getMatcher(); if (lastMatcher2 != null) { lastMatcher = lastMatcher2; } while (pos < conditions.length && conditions[pos].isOrnext()) { pos++; } } else if (!conditions[pos].isOrnext()) { done = true; } pos++; } else { done = true; } } // Use the substitution to rewrite the url if (rewrite) { if (isEnv()) { for (int i = 0; i < envSubstitution.size(); i++) { envResult.get(i).set(envSubstitution.get(i).evaluate(matcher, lastMatcher, resolver)); } } if (isCookie()) { cookieResult.set(cookieSubstitution.evaluate(matcher, lastMatcher, resolver)); } if (substitution != null) { return substitution.evaluate(matcher, lastMatcher, resolver); } else { return url; } } else { return null; } } /** * String representation. */ public String toString() { // FIXME: Add flags if possible return "RewriteRule " + patternString + " " + substitutionString; } /** * This flag chains the current rule with the next rule (which itself * can be chained with the following rule, etc.). This has the following * effect: if a rule matches, then processing continues as usual, i.e., * the flag has no effect. If the rule does not match, then all following * chained rules are skipped. For instance, use it to remove the ``.www'' * part inside a per-directory rule set when you let an external redirect * happen (where the ``.www'' part should not to occur!). */ protected boolean chain = false; /** * This sets a cookie on the client's browser. The cookie's name is * specified by NAME and the value is VAL. The domain field is the domain * of the cookie, such as '.apache.org',the optional lifetime * is the lifetime of the cookie in minutes, and the optional path is the * path of the cookie */ protected boolean cookie = false; protected String cookieName = null; protected String cookieValue = null; protected String cookieDomain = null; protected int cookieLifetime = -1; protected String cookiePath = null; protected boolean cookieSecure = false; protected boolean cookieHttpOnly = false; protected Substitution cookieSubstitution = null; protected ThreadLocal cookieResult = new ThreadLocal(); /** * This forces a request attribute named VAR to be set to the value VAL, * where VAL can contain regexp back references $N and %N which will be * expanded. Multiple env flags are allowed. */ protected boolean env = false; protected ArrayList envName = new ArrayList(); protected ArrayList envValue = new ArrayList(); protected ArrayList envSubstitution = new ArrayList(); protected ArrayList> envResult = new ArrayList>(); /** * This forces the current URL to be forbidden, i.e., it immediately sends * back a HTTP response of 403 (FORBIDDEN). Use this flag in conjunction * with appropriate RewriteConds to conditionally block some URLs. */ protected boolean forbidden = false; /** * This forces the current URL to be gone, i.e., it immediately sends * back a HTTP response of 410 (GONE). Use this flag to mark pages which * no longer exist as gone. */ protected boolean gone = false; /** * Host. This means this rule and its associated conditions will apply to * host, allowing host rewriting (ex: redirecting internally *.foo.com to * bar.foo.com). */ protected boolean host = false; /** * Stop the rewriting process here and don't apply any more rewriting * rules. This corresponds to the Perl last command or the break command * from the C language. Use this flag to prevent the currently rewritten * URL from being rewritten further by following rules. For example, use * it to rewrite the root-path URL ('/') to a real one, e.g., '/e/www/'. */ protected boolean last = false; /** * Re-run the rewriting process (starting again with the first rewriting * rule). Here the URL to match is again not the original URL but the URL * from the last rewriting rule. This corresponds to the Perl next * command or the continue command from the C language. Use this flag to * restart the rewriting process, i.e., to immediately go to the top of * the loop. But be careful not to create an infinite loop! */ protected boolean next = false; /** * This makes the Pattern case-insensitive, i.e., there is no difference * between 'A-Z' and 'a-z' when Pattern is matched against the current * URL. */ protected boolean nocase = false; /** * This flag keeps mod_rewrite from applying the usual URI escaping rules * to the result of a rewrite. Ordinarily, special characters (such as * '%', '$', ';', and so on) will be escaped into their hexcode * equivalents ('%25', '%24', and '%3B', respectively); this flag * prevents this from being done. This allows percent symbols to appear * in the output, as in * RewriteRule /foo/(.*) /bar?arg=P1\%3d$1 [R,NE] * which would turn '/foo/zed' into a safe request for '/bar?arg=P1=zed'. */ protected boolean noescape = false; /** * This flag forces the rewriting engine to skip a rewriting rule if the * current request is an internal sub-request. For instance, sub-requests * occur internally in Apache when mod_include tries to find out * information about possible directory default files (index.xxx). On * sub-requests it is not always useful and even sometimes causes a * failure to if the complete set of rules are applied. Use this flag to * exclude some rules. Use the following rule for your decision: whenever * you prefix some URLs with CGI-scripts to force them to be processed by * the CGI-script, the chance is high that you will run into problems (or * even overhead) on sub-requests. In these cases, use this flag. */ protected boolean nosubreq = false; /** * This flag forces the substitution part to be internally forced as a proxy * request and immediately (i.e., rewriting rule processing stops here) put * through the proxy module. You have to make sure that the substitution string * is a valid URI (e.g., typically starting with http://hostname) which can be * handled by the Apache proxy module. If not you get an error from the proxy * module. Use this flag to achieve a more powerful implementation of the * ProxyPass directive, to map some remote stuff into the namespace of * the local server. * Note: No proxy */ /** * Note: No passthrough */ /** * This flag forces the rewriting engine to append a query string part in * the substitution string to the existing one instead of replacing it. * Use this when you want to add more data to the query string via * a rewrite rule. */ protected boolean qsappend = false; /** * Prefix Substitution with http://thishost[:thisport]/ (which makes the * new URL a URI) to force a external redirection. If no code is given * a HTTP response of 302 (MOVED TEMPORARILY) is used. If you want to * use other response codes in the range 300-400 just specify them as * a number or use one of the following symbolic names: temp (default), * permanent, seeother. Use it for rules which should canonicalize the * URL and give it back to the client, e.g., translate ``/~'' into ``/u/'' * or always append a slash to /u/user, etc. Note: When you use this flag, * make sure that the substitution field is a valid URL! If not, you are * redirecting to an invalid location! And remember that this flag itself * only prefixes the URL with http://thishost[:thisport]/, rewriting * continues. Usually you also want to stop and do the redirection * immediately. To stop the rewriting you also have to provide the * 'L' flag. */ protected boolean redirect = false; protected int redirectCode = 0; /** * This flag forces the rewriting engine to skip the next num rules in * sequence when the current rule matches. Use this to make pseudo * if-then-else constructs: The last rule of the then-clause becomes * skip=N where N is the number of rules in the else-clause. * (This is not the same as the 'chain|C' flag!) */ protected int skip = 0; /** * Force the MIME-type of the target file to be MIME-type. For instance, * this can be used to setup the content-type based on some conditions. * For example, the following snippet allows .php files to be displayed * by mod_php if they are called with the .phps extension: * RewriteRule ^(.+\.php)s$ $1 [T=application/x-httpd-php-source] */ protected boolean type = false; protected String typeValue = null; public boolean isChain() { return chain; } public void setChain(boolean chain) { this.chain = chain; } public RewriteCond[] getConditions() { return conditions; } public void setConditions(RewriteCond[] conditions) { this.conditions = conditions; } public boolean isCookie() { return cookie; } public void setCookie(boolean cookie) { this.cookie = cookie; } public String getCookieName() { return cookieName; } public void setCookieName(String cookieName) { this.cookieName = cookieName; } public String getCookieValue() { return cookieValue; } public void setCookieValue(String cookieValue) { this.cookieValue = cookieValue; } public String getCookieResult() { return cookieResult.get(); } public boolean isEnv() { return env; } public int getEnvSize() { return envName.size(); } public void setEnv(boolean env) { this.env = env; } public String getEnvName(int i) { return envName.get(i); } public void addEnvName(String envName) { this.envName.add(envName); } public String getEnvValue(int i) { return envValue.get(i); } public void addEnvValue(String envValue) { this.envValue.add(envValue); } public String getEnvResult(int i) { return envResult.get(i).get(); } public boolean isForbidden() { return forbidden; } public void setForbidden(boolean forbidden) { this.forbidden = forbidden; } public boolean isGone() { return gone; } public void setGone(boolean gone) { this.gone = gone; } public boolean isLast() { return last; } public void setLast(boolean last) { this.last = last; } public boolean isNext() { return next; } public void setNext(boolean next) { this.next = next; } public boolean isNocase() { return nocase; } public void setNocase(boolean nocase) { this.nocase = nocase; } public boolean isNoescape() { return noescape; } public void setNoescape(boolean noescape) { this.noescape = noescape; } public boolean isNosubreq() { return nosubreq; } public void setNosubreq(boolean nosubreq) { this.nosubreq = nosubreq; } public boolean isQsappend() { return qsappend; } public void setQsappend(boolean qsappend) { this.qsappend = qsappend; } public boolean isRedirect() { return redirect; } public void setRedirect(boolean redirect) { this.redirect = redirect; } public int getRedirectCode() { return redirectCode; } public void setRedirectCode(int redirectCode) { this.redirectCode = redirectCode; } public int getSkip() { return skip; } public void setSkip(int skip) { this.skip = skip; } public Substitution getSubstitution() { return substitution; } public void setSubstitution(Substitution substitution) { this.substitution = substitution; } public boolean isType() { return type; } public void setType(boolean type) { this.type = type; } public String getTypeValue() { return typeValue; } public void setTypeValue(String typeValue) { this.typeValue = typeValue; } public String getPatternString() { return patternString; } public void setPatternString(String patternString) { this.patternString = patternString; } public String getSubstitutionString() { return substitutionString; } public void setSubstitutionString(String substitutionString) { this.substitutionString = substitutionString; } public boolean isHost() { return host; } public void setHost(boolean host) { this.host = host; } public String getCookieDomain() { return cookieDomain; } public void setCookieDomain(String cookieDomain) { this.cookieDomain = cookieDomain; } public int getCookieLifetime() { return cookieLifetime; } public void setCookieLifetime(int cookieLifetime) { this.cookieLifetime = cookieLifetime; } public String getCookiePath() { return cookiePath; } public void setCookiePath(String cookiePath) { this.cookiePath = cookiePath; } public boolean isCookieSecure() { return cookieSecure; } public void setCookieSecure(boolean cookieSecure) { this.cookieSecure = cookieSecure; } public boolean isCookieHttpOnly() { return cookieHttpOnly; } public void setCookieHttpOnly(boolean cookieHttpOnly) { this.cookieHttpOnly = cookieHttpOnly; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/compat/rewrite/Substitution.java000066400000000000000000000231021420065311100330750ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.compat.rewrite; import java.util.ArrayList; import java.util.Map; import java.util.regex.Matcher; /** * @author Remy Maucherat */ public class Substitution { public abstract class SubstitutionElement { public abstract String evaluate(Matcher rule, Matcher cond, Resolver resolver); } public class StaticElement extends SubstitutionElement { public String value; public String evaluate (Matcher rule, Matcher cond, Resolver resolver) { return value; } } public class RewriteRuleBackReferenceElement extends SubstitutionElement { public int n; public String evaluate(Matcher rule, Matcher cond, Resolver resolver) { return rule.group(n); } } public class RewriteCondBackReferenceElement extends SubstitutionElement { public int n; public String evaluate(Matcher rule, Matcher cond, Resolver resolver) { return cond.group(n); } } public class ServerVariableElement extends SubstitutionElement { public String key; public String evaluate(Matcher rule, Matcher cond, Resolver resolver) { return resolver.resolve(key); } } public class ServerVariableEnvElement extends SubstitutionElement { public String key; public String evaluate(Matcher rule, Matcher cond, Resolver resolver) { return resolver.resolveEnv(key); } } public class ServerVariableSslElement extends SubstitutionElement { public String key; public String evaluate(Matcher rule, Matcher cond, Resolver resolver) { return resolver.resolveSsl(key); } } public class ServerVariableHttpElement extends SubstitutionElement { public String key; public String evaluate(Matcher rule, Matcher cond, Resolver resolver) { return resolver.resolveHttp(key); } } public class MapElement extends SubstitutionElement { public RewriteMap map = null; public String key; public String defaultValue = null; public String evaluate(Matcher rule, Matcher cond, Resolver resolver) { String result = map.lookup(key); if (result == null) { result = defaultValue; } return result; } } protected SubstitutionElement[] elements = null; protected String sub = null; public String getSub() { return sub; } public void setSub(String sub) { this.sub = sub; } public void parse(Map maps) { ArrayList elements = new ArrayList(); int pos = 0; int percentPos = 0; int dollarPos = 0; while (pos < sub.length()) { percentPos = sub.indexOf('%', pos); dollarPos = sub.indexOf('$', pos); if (percentPos == -1 && dollarPos == -1) { // Static text StaticElement newElement = new StaticElement(); newElement.value = sub.substring(pos, sub.length()); pos = sub.length(); elements.add(newElement); } else if (percentPos == -1 || ((dollarPos != -1) && (dollarPos < percentPos))) { // $: back reference to rule or map lookup if (dollarPos + 1 == sub.length()) { throw new IllegalArgumentException(sub); } if (pos < dollarPos) { // Static text StaticElement newElement = new StaticElement(); newElement.value = sub.substring(pos, dollarPos); pos = dollarPos; elements.add(newElement); } if (Character.isDigit(sub.charAt(dollarPos + 1))) { // $: back reference to rule RewriteRuleBackReferenceElement newElement = new RewriteRuleBackReferenceElement(); newElement.n = Character.digit(sub.charAt(dollarPos + 1), 10); pos = dollarPos + 2; elements.add(newElement); } else { // $: map lookup as ${mapname:key|default} MapElement newElement = new MapElement(); int open = sub.indexOf('{', dollarPos); int colon = sub.indexOf(':', dollarPos); int def = sub.indexOf('|', dollarPos); int close = sub.indexOf('}', dollarPos); if (!(-1 < open && open < colon && colon < close)) { throw new IllegalArgumentException(sub); } newElement.map = maps.get(sub.substring(open + 1, colon)); if (newElement.map == null) { throw new IllegalArgumentException(sub + ": No map: " + sub.substring(open + 1, colon)); } if (def > -1) { if (!(colon < def && def < close)) { throw new IllegalArgumentException(sub); } newElement.key = sub.substring(colon + 1, def); newElement.defaultValue = sub.substring(def + 1, close); } else { newElement.key = sub.substring(colon + 1, close); } pos = close + 1; elements.add(newElement); } } else { // %: back reference to condition or server variable if (percentPos + 1 == sub.length()) { throw new IllegalArgumentException(sub); } if (pos < percentPos) { // Static text StaticElement newElement = new StaticElement(); newElement.value = sub.substring(pos, percentPos); pos = percentPos; elements.add(newElement); } if (Character.isDigit(sub.charAt(percentPos + 1))) { // %: back reference to condition RewriteCondBackReferenceElement newElement = new RewriteCondBackReferenceElement(); newElement.n = Character.digit(sub.charAt(percentPos + 1), 10); pos = percentPos + 2; elements.add(newElement); } else { // %: server variable as %{variable} SubstitutionElement newElement = null; int open = sub.indexOf('{', percentPos); int colon = sub.indexOf(':', percentPos); int close = sub.indexOf('}', percentPos); if (!(-1 < open && open < close)) { throw new IllegalArgumentException(sub); } if (colon > -1) { if (!(open < colon && colon < close)) { throw new IllegalArgumentException(sub); } String type = sub.substring(open + 1, colon); if (type.equals("ENV")) { newElement = new ServerVariableEnvElement(); ((ServerVariableEnvElement) newElement).key = sub.substring(colon + 1, close); } else if (type.equals("SSL")) { newElement = new ServerVariableSslElement(); ((ServerVariableSslElement) newElement).key = sub.substring(colon + 1, close); } else if (type.equals("HTTP")) { newElement = new ServerVariableHttpElement(); ((ServerVariableHttpElement) newElement).key = sub.substring(colon + 1, close); } else { throw new IllegalArgumentException(sub + ": Bad type: " + type); } } else { newElement = new ServerVariableElement(); ((ServerVariableElement) newElement).key = sub.substring(open + 1, close); } pos = close + 1; elements.add(newElement); } } } this.elements = elements.toArray(new SubstitutionElement[0]); } /** * Evaluate the substitution based on the context * * @param rule corresponding matched rule * @param cond last matched condition * @return */ public String evaluate(Matcher rule, Matcher cond, Resolver resolver) { StringBuffer buf = new StringBuffer(); for (int i = 0; i < elements.length; i++) { buf.append(elements[i].evaluate(rule, cond, resolver)); } return buf.toString(); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/compat/rewrite/UndertowResolver.java000066400000000000000000000156171420065311100337260ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.compat.rewrite; import io.undertow.server.handlers.resource.Resource; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.util.DateUtils; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Calendar; /** * @author Remy Maucherat */ public class UndertowResolver extends Resolver { private final ServletRequestContext servletRequestContext; private final HttpServletRequest request; public UndertowResolver(ServletRequestContext servletRequestContext, HttpServletRequest request) { this.servletRequestContext = servletRequestContext; this.request = request; } /** * The following are not implemented: * - SERVER_ADMIN * - API_VERSION * - IS_SUBREQ */ public String resolve(String key) { if (key.equals("HTTP_USER_AGENT")) { return request.getHeader("user-agent"); } else if (key.equals("HTTP_REFERER")) { return request.getHeader("referer"); } else if (key.equals("HTTP_COOKIE")) { return request.getHeader("cookie"); } else if (key.equals("HTTP_FORWARDED")) { return request.getHeader("forwarded"); } else if (key.equals("HTTP_HOST")) { String host = request.getHeader("host"); int index = (host != null) ? host.indexOf(':') : -1; if (index != -1) host = host.substring(0, index); return host; } else if (key.equals("HTTP_PROXY_CONNECTION")) { return request.getHeader("proxy-connection"); } else if (key.equals("HTTP_ACCEPT")) { return request.getHeader("accept"); } else if (key.equals("REMOTE_ADDR")) { return request.getRemoteAddr(); } else if (key.equals("REMOTE_HOST")) { return request.getRemoteHost(); } else if (key.equals("REMOTE_PORT")) { return String.valueOf(request.getRemotePort()); } else if (key.equals("REMOTE_USER")) { return request.getRemoteUser(); } else if (key.equals("REMOTE_IDENT")) { return request.getRemoteUser(); } else if (key.equals("REQUEST_METHOD")) { return request.getMethod(); } else if (key.equals("SCRIPT_FILENAME")) { return request.getRealPath(request.getServletPath()); } else if (key.equals("REQUEST_PATH")) { return servletRequestContext.getExchange().getRelativePath(); } else if (key.equals("CONTEXT_PATH")) { return request.getContextPath(); } else if (key.equals("SERVLET_PATH")) { return emptyStringIfNull(request.getServletPath()); } else if (key.equals("PATH_INFO")) { return emptyStringIfNull(request.getPathInfo()); } else if (key.equals("QUERY_STRING")) { return emptyStringIfNull(request.getQueryString()); } else if (key.equals("AUTH_TYPE")) { return request.getAuthType(); } else if (key.equals("DOCUMENT_ROOT")) { return request.getRealPath("/"); } else if (key.equals("SERVER_NAME")) { return request.getLocalName(); } else if (key.equals("SERVER_ADDR")) { return request.getLocalAddr(); } else if (key.equals("SERVER_PORT")) { return String.valueOf(request.getLocalPort()); } else if (key.equals("SERVER_PROTOCOL")) { return request.getProtocol(); } else if (key.equals("SERVER_SOFTWARE")) { return "tomcat"; } else if (key.equals("THE_REQUEST")) { return request.getMethod() + " " + request.getRequestURI() + " " + request.getProtocol(); } else if (key.equals("REQUEST_URI")) { return request.getRequestURI(); } else if (key.equals("REQUEST_FILENAME")) { return request.getPathTranslated(); } else if (key.equals("HTTPS")) { return request.isSecure() ? "on" : "off"; } else if (key.equals("TIME_YEAR")) { return String.valueOf(Calendar.getInstance().get(Calendar.YEAR)); } else if (key.equals("TIME_MON")) { return String.valueOf(Calendar.getInstance().get(Calendar.MONTH)); } else if (key.equals("TIME_DAY")) { return String.valueOf(Calendar.getInstance().get(Calendar.DAY_OF_MONTH)); } else if (key.equals("TIME_HOUR")) { return String.valueOf(Calendar.getInstance().get(Calendar.HOUR_OF_DAY)); } else if (key.equals("TIME_MIN")) { return String.valueOf(Calendar.getInstance().get(Calendar.MINUTE)); } else if (key.equals("TIME_SEC")) { return String.valueOf(Calendar.getInstance().get(Calendar.SECOND)); } else if (key.equals("TIME_WDAY")) { return String.valueOf(Calendar.getInstance().get(Calendar.DAY_OF_WEEK)); } else if (key.equals("TIME")) { return DateUtils.getCurrentDateTime(servletRequestContext.getExchange()); } return null; } public String resolveEnv(String key) { Object result = request.getAttribute(key); return (result != null) ? result.toString() : System.getProperty(key); } public String resolveSsl(String key) { // FIXME: Implement SSL environment variables return null; } public String resolveHttp(String key) { return request.getHeader(key); } public boolean resolveResource(int type, String name) { Resource resource; try { resource = servletRequestContext.getDeployment().getDeploymentInfo().getResourceManager().getResource(name); } catch (IOException e) { throw new RuntimeException(e); } switch (type) { case 0: return (resource == null); case 1: return (resource != null); case 2: return (resource != null && resource.getContentLength() > 0); default: return false; } } private static String emptyStringIfNull(String value) { if (value == null) { return ""; } else { return value; } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/000077500000000000000000000000001420065311100255045ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/ApplicationListeners.java000066400000000000000000000476501420065311100325170ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import io.undertow.servlet.UndertowServletLogger; import javax.servlet.ServletContext; import javax.servlet.ServletContextAttributeEvent; import javax.servlet.ServletContextAttributeListener; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletRequestAttributeEvent; import javax.servlet.ServletRequestAttributeListener; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionIdListener; import javax.servlet.http.HttpSessionListener; import java.util.ArrayList; import java.util.List; import static io.undertow.servlet.core.ApplicationListeners.ListenerState.DECLARED_LISTENER; import static io.undertow.servlet.core.ApplicationListeners.ListenerState.PROGRAMATIC_LISTENER; /** * Class that is responsible for invoking application listeners. *

    * This class does not perform any context setup, the context must be setup * before invoking this class. *

    * Note that arrays are used instead of lists for performance reasons. * * @author Stuart Douglas */ public class ApplicationListeners implements Lifecycle { private static final ManagedListener[] EMPTY = {}; private static final Class[] LISTENER_CLASSES = {ServletContextListener.class, ServletContextAttributeListener.class, ServletRequestListener.class, ServletRequestAttributeListener.class, javax.servlet.http.HttpSessionListener.class, javax.servlet.http.HttpSessionAttributeListener.class, HttpSessionIdListener.class}; private static final ThreadLocal IN_PROGRAMATIC_SC_LISTENER_INVOCATION = new ThreadLocal() { @Override protected ListenerState initialValue() { return ListenerState.NO_LISTENER; } }; private ServletContext servletContext; private final List allListeners = new ArrayList<>(); private ManagedListener[] servletContextListeners; private ManagedListener[] servletContextAttributeListeners; private ManagedListener[] servletRequestListeners; private ManagedListener[] servletRequestAttributeListeners; private ManagedListener[] httpSessionListeners; private ManagedListener[] httpSessionAttributeListeners; private ManagedListener[] httpSessionIdListeners; private volatile boolean started = false; public ApplicationListeners(final List allListeners, final ServletContext servletContext) { this.servletContext = servletContext; servletContextListeners = EMPTY; servletContextAttributeListeners = EMPTY; servletRequestListeners = EMPTY; servletRequestAttributeListeners = EMPTY; httpSessionListeners = EMPTY; httpSessionAttributeListeners = EMPTY; httpSessionIdListeners = EMPTY; for (final ManagedListener listener : allListeners) { addListener(listener); } } public void addListener(final ManagedListener listener) { if (ServletContextListener.class.isAssignableFrom(listener.getListenerInfo().getListenerClass())) { ManagedListener[] old = servletContextListeners; servletContextListeners = new ManagedListener[old.length + 1]; System.arraycopy(old, 0, servletContextListeners, 0, old.length); servletContextListeners[old.length] = listener; } if (ServletContextAttributeListener.class.isAssignableFrom(listener.getListenerInfo().getListenerClass())) { ManagedListener[] old = servletContextAttributeListeners; servletContextAttributeListeners = new ManagedListener[old.length + 1]; System.arraycopy(old, 0, servletContextAttributeListeners, 0, old.length); servletContextAttributeListeners[old.length] = listener; } if (ServletRequestListener.class.isAssignableFrom(listener.getListenerInfo().getListenerClass())) { ManagedListener[] old = servletRequestListeners; servletRequestListeners = new ManagedListener[old.length + 1]; System.arraycopy(old, 0, servletRequestListeners, 0, old.length); servletRequestListeners[old.length] = listener; } if (ServletRequestAttributeListener.class.isAssignableFrom(listener.getListenerInfo().getListenerClass())) { ManagedListener[] old = servletRequestAttributeListeners; servletRequestAttributeListeners = new ManagedListener[old.length + 1]; System.arraycopy(old, 0, servletRequestAttributeListeners, 0, old.length); servletRequestAttributeListeners[old.length] = listener; } if (HttpSessionListener.class.isAssignableFrom(listener.getListenerInfo().getListenerClass())) { ManagedListener[] old = httpSessionListeners; httpSessionListeners = new ManagedListener[old.length + 1]; System.arraycopy(old, 0, httpSessionListeners, 0, old.length); httpSessionListeners[old.length] = listener; } if (HttpSessionAttributeListener.class.isAssignableFrom(listener.getListenerInfo().getListenerClass())) { ManagedListener[] old = httpSessionAttributeListeners; httpSessionAttributeListeners = new ManagedListener[old.length + 1]; System.arraycopy(old, 0, httpSessionAttributeListeners, 0, old.length); httpSessionAttributeListeners[old.length] = listener; } if (HttpSessionIdListener.class.isAssignableFrom(listener.getListenerInfo().getListenerClass())) { ManagedListener[] old = httpSessionIdListeners; httpSessionIdListeners = new ManagedListener[old.length + 1]; System.arraycopy(old, 0, httpSessionIdListeners, 0, old.length); httpSessionIdListeners[old.length] = listener; } this.allListeners.add(listener); if(started) { try { listener.start(); } catch (ServletException e) { throw new RuntimeException(e); } } } public void start() throws ServletException { started = true; for (ManagedListener listener : allListeners) { listener.start(); } } public void stop() { if (started) { started = false; for (final ManagedListener listener : allListeners) { listener.stop(); } } } @Override public boolean isStarted() { return started; } public void contextInitialized() { if(!started) { return; } //new listeners can be added here, so we don't use an iterator final ServletContextEvent event = new ServletContextEvent(servletContext); for (int i = 0; i < servletContextListeners.length; ++i) { ManagedListener listener = servletContextListeners[i]; IN_PROGRAMATIC_SC_LISTENER_INVOCATION.set(listener.isProgramatic() ? PROGRAMATIC_LISTENER : DECLARED_LISTENER); try { this.get(listener).contextInitialized(event); } finally { IN_PROGRAMATIC_SC_LISTENER_INVOCATION.remove(); } } } public void contextDestroyed() { if(!started) { return; } final ServletContextEvent event = new ServletContextEvent(servletContext); for (int i = servletContextListeners.length - 1; i >= 0; --i) { ManagedListener listener = servletContextListeners[i]; try { this.get(listener).contextDestroyed(event); } catch (Throwable t) { UndertowServletLogger.REQUEST_LOGGER.errorInvokingListener("contextDestroyed", listener.getListenerInfo().getListenerClass(), t); } } } public void servletContextAttributeAdded(final String name, final Object value) { if(!started) { return; } final ServletContextAttributeEvent sre = new ServletContextAttributeEvent(servletContext, name, value); for (int i = 0; i < servletContextAttributeListeners.length; ++i) { ManagedListener listener = servletContextAttributeListeners[i]; try { this. get(listener).attributeAdded(sre); } catch (Exception e) { UndertowServletLogger.REQUEST_LOGGER.errorInvokingListener("attributeAdded", listener.getListenerInfo().getListenerClass(), e); } } } public void servletContextAttributeRemoved(final String name, final Object value) { if(!started) { return; } final ServletContextAttributeEvent sre = new ServletContextAttributeEvent(servletContext, name, value); for (int i = 0; i < servletContextAttributeListeners.length; ++i) { ManagedListener listener = servletContextAttributeListeners[i]; try { this. get(listener).attributeRemoved(sre); } catch (Exception e) { UndertowServletLogger.REQUEST_LOGGER.errorInvokingListener("attributeRemoved", listener.getListenerInfo().getListenerClass(), e); } } } public void servletContextAttributeReplaced(final String name, final Object value) { if(!started) { return; } final ServletContextAttributeEvent sre = new ServletContextAttributeEvent(servletContext, name, value); for (int i = 0; i < servletContextAttributeListeners.length; ++i) { ManagedListener listener = servletContextAttributeListeners[i]; try { this. get(listener).attributeReplaced(sre); } catch (Exception e) { UndertowServletLogger.REQUEST_LOGGER.errorInvokingListener("attributeReplaced", listener.getListenerInfo().getListenerClass(), e); } } } public void requestInitialized(final ServletRequest request) { if(!started) { return; } if(servletRequestListeners.length > 0) { int i = 0; final ServletRequestEvent sre = new ServletRequestEvent(servletContext, request); try { for (; i < servletRequestListeners.length; ++i) { this.get(servletRequestListeners[i]).requestInitialized(sre); } } catch (RuntimeException e) { UndertowServletLogger.REQUEST_LOGGER.errorInvokingListener("requestInitialized", servletRequestListeners[i].getListenerInfo().getListenerClass(), e); for (; i >= 0; i--) { ManagedListener listener = servletRequestListeners[i]; try { this. get(listener).requestDestroyed(sre); } catch (Throwable t) { UndertowServletLogger.REQUEST_LOGGER.errorInvokingListener("requestDestroyed", listener.getListenerInfo().getListenerClass(), e); } } throw e; } } } public void requestDestroyed(final ServletRequest request) { if(!started) { return; } if(servletRequestListeners.length > 0) { final ServletRequestEvent sre = new ServletRequestEvent(servletContext, request); for (int i = servletRequestListeners.length - 1; i >= 0; --i) { ManagedListener listener = servletRequestListeners[i]; try { this.get(listener).requestDestroyed(sre); } catch (Exception e) { UndertowServletLogger.REQUEST_LOGGER.errorInvokingListener("requestDestroyed", listener.getListenerInfo().getListenerClass(), e); } } } } public void servletRequestAttributeAdded(final HttpServletRequest request, final String name, final Object value) { if(!started) { return; } final ServletRequestAttributeEvent sre = new ServletRequestAttributeEvent(servletContext, request, name, value); for (int i = 0; i < servletRequestAttributeListeners.length; ++i) { ManagedListener listener = servletRequestAttributeListeners[i]; try { this. get(listener).attributeAdded(sre); } catch (Exception e) { UndertowServletLogger.REQUEST_LOGGER.errorInvokingListener("attributeAdded", listener.getListenerInfo().getListenerClass(), e); } } } public void servletRequestAttributeRemoved(final HttpServletRequest request, final String name, final Object value) { if(!started) { return; } final ServletRequestAttributeEvent sre = new ServletRequestAttributeEvent(servletContext, request, name, value); for (int i = 0; i < servletRequestAttributeListeners.length; ++i) { ManagedListener listener = servletRequestAttributeListeners[i]; try { this. get(listener).attributeRemoved(sre); } catch (Exception e) { UndertowServletLogger.REQUEST_LOGGER.errorInvokingListener("attributeRemoved", listener.getListenerInfo().getListenerClass(), e); } } } public void servletRequestAttributeReplaced(final HttpServletRequest request, final String name, final Object value) { if(!started) { return; } final ServletRequestAttributeEvent sre = new ServletRequestAttributeEvent(servletContext, request, name, value); for (int i = 0; i < servletRequestAttributeListeners.length; ++i) { ManagedListener listener = servletRequestAttributeListeners[i]; try { this. get(listener).attributeReplaced(sre); } catch (Exception e) { UndertowServletLogger.REQUEST_LOGGER.errorInvokingListener("attributeReplaced", listener.getListenerInfo().getListenerClass(), e); } } } public void sessionCreated(final HttpSession session) { if(!started) { return; } final HttpSessionEvent sre = new HttpSessionEvent(session); for (int i = 0; i < httpSessionListeners.length; ++i) { ManagedListener listener = httpSessionListeners[i]; try { this. get(listener).sessionCreated(sre); } catch (Exception e) { UndertowServletLogger.REQUEST_LOGGER.errorInvokingListener("sessionCreated", listener.getListenerInfo().getListenerClass(), e); } } } public void sessionDestroyed(final HttpSession session) { if(!started) { return; } final HttpSessionEvent sre = new HttpSessionEvent(session); for (int i = httpSessionListeners.length - 1; i >= 0; --i) { ManagedListener listener = httpSessionListeners[i]; try { this. get(listener).sessionDestroyed(sre); } catch (Exception e) { UndertowServletLogger.REQUEST_LOGGER.errorInvokingListener("sessionDestroyed", listener.getListenerInfo().getListenerClass(), e); } } } public void httpSessionAttributeAdded(final HttpSession session, final String name, final Object value) { if(!started) { return; } final HttpSessionBindingEvent sre = new HttpSessionBindingEvent(session, name, value); for (int i = 0; i < httpSessionAttributeListeners.length; ++i) { ManagedListener listener = httpSessionAttributeListeners[i]; try { this. get(listener).attributeAdded(sre); } catch (Exception e) { UndertowServletLogger.REQUEST_LOGGER.errorInvokingListener("attributeAdded", listener.getListenerInfo().getListenerClass(), e); } } } public void httpSessionAttributeRemoved(final HttpSession session, final String name, final Object value) { if(!started) { return; } final HttpSessionBindingEvent sre = new HttpSessionBindingEvent(session, name, value); for (int i = 0; i < httpSessionAttributeListeners.length; ++i) { ManagedListener listener = httpSessionAttributeListeners[i]; try { this. get(httpSessionAttributeListeners[i]).attributeRemoved(sre); } catch (Exception e) { UndertowServletLogger.REQUEST_LOGGER.errorInvokingListener("attributeRemoved", listener.getListenerInfo().getListenerClass(), e); } } } public void httpSessionAttributeReplaced(final HttpSession session, final String name, final Object value) { if(!started) { return; } final HttpSessionBindingEvent sre = new HttpSessionBindingEvent(session, name, value); for (int i = 0; i < httpSessionAttributeListeners.length; ++i) { ManagedListener listener = httpSessionAttributeListeners[i]; try { this. get(listener).attributeReplaced(sre); } catch (Exception e) { UndertowServletLogger.REQUEST_LOGGER.errorInvokingListener("attributeReplaced", listener.getListenerInfo().getListenerClass(), e); } } } public void httpSessionIdChanged(final HttpSession session, final String oldSessionId) { if(!started) { return; } final HttpSessionEvent sre = new HttpSessionEvent(session); for (int i = 0; i < httpSessionIdListeners.length; ++i) { ManagedListener listener = httpSessionIdListeners[i]; try { this. get(listener).sessionIdChanged(sre, oldSessionId); } catch (Exception e) { UndertowServletLogger.REQUEST_LOGGER.errorInvokingListener("sessionIdChanged", listener.getListenerInfo().getListenerClass(), e); } } } private T get(final ManagedListener listener) { return (T) listener.instance(); } /** * returns true if this is in in a */ public static ListenerState listenerState() { return IN_PROGRAMATIC_SC_LISTENER_INVOCATION.get(); } /** * @param clazz The potential listener class * @return true if the provided class is a valid listener class */ public static boolean isListenerClass(final Class clazz) { for (Class c : LISTENER_CLASSES) { if (c.isAssignableFrom(clazz)) { return true; } } return false; } public enum ListenerState { NO_LISTENER, DECLARED_LISTENER, PROGRAMATIC_LISTENER, } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/BlockingWriterSenderImpl.java000066400000000000000000000214031420065311100332570ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import java.io.EOFException; import java.io.IOException; import java.io.PrintWriter; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import javax.servlet.DispatcherType; import io.undertow.UndertowMessages; import io.undertow.io.IoCallback; import io.undertow.io.Sender; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.handlers.ServletRequestContext; import org.xnio.IoUtils; /** * A sender that uses a print writer. * * In general this should never be used. It exists for the edge case where a filter has called * getWriter() and then the default servlet is being used to serve a text file. * * @author Stuart Douglas */ public class BlockingWriterSenderImpl implements Sender { /** * TODO: we should be used pooled buffers */ public static final int BUFFER_SIZE = 128; private final CharsetDecoder charsetDecoder; private final HttpServerExchange exchange; private final PrintWriter writer; private FileChannel pendingFile; private volatile Thread inCall; private volatile Thread sendThread; private String next; private IoCallback queuedCallback; public BlockingWriterSenderImpl(final HttpServerExchange exchange, final PrintWriter writer, final String charset) { this.exchange = exchange; this.writer = writer; this.charsetDecoder = Charset.forName(charset).newDecoder(); } @Override public void send(final ByteBuffer buffer, final IoCallback callback) { sendThread = Thread.currentThread(); if (inCall == Thread.currentThread()) { queue(new ByteBuffer[]{buffer}, callback); return; } if (writeBuffer(buffer, callback)) { invokeOnComplete(callback); } } @Override public void send(final ByteBuffer[] buffer, final IoCallback callback) { sendThread = Thread.currentThread(); if (inCall == Thread.currentThread()) { queue(buffer, callback); return; } for (ByteBuffer b : buffer) { if (!writeBuffer(b, callback)) { return; } } invokeOnComplete(callback); } @Override public void send(final String data, final IoCallback callback) { sendThread = Thread.currentThread(); if (inCall == Thread.currentThread()) { queue(data, callback); return; } writer.write(data); invokeOnComplete(callback); } @Override public void send(final ByteBuffer buffer) { send(buffer, IoCallback.END_EXCHANGE); } @Override public void send(final ByteBuffer[] buffer) { send(buffer, IoCallback.END_EXCHANGE); } @Override public void send(final String data, final Charset charset, final IoCallback callback) { sendThread = Thread.currentThread(); if (inCall == Thread.currentThread()) { queue(new ByteBuffer[]{ByteBuffer.wrap(data.getBytes(charset))}, callback); return; } writer.write(data); invokeOnComplete(callback); } @Override public void send(final String data) { send(data, IoCallback.END_EXCHANGE); } @Override public void send(final String data, final Charset charset) { send(data, charset, IoCallback.END_EXCHANGE); } @Override public void transferFrom(FileChannel source, IoCallback callback) { sendThread = Thread.currentThread(); if (inCall == Thread.currentThread()) { queue(source, callback); return; } performTransfer(source, callback); } private void performTransfer(FileChannel source, IoCallback callback) { ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE); try { long pos = source.position(); long size = source.size(); while (size - pos > 0) { int ret = source.read(buffer); if (ret <= 0) { break; } pos += ret; buffer.flip(); if (!writeBuffer(buffer, callback)) { return; } buffer.clear(); } if (pos != size) { throw new EOFException("Unexpected EOF reading file"); } } catch (IOException e) { callback.onException(exchange, this, e); } invokeOnComplete(callback); } @Override public void close(final IoCallback callback) { writer.close(); if (writer.checkError()) { callback.onException(exchange, this, new IOException()); } else { invokeOnComplete(callback); } } @Override public void close() { if(exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getDispatcherType() != DispatcherType.INCLUDE) { IoUtils.safeClose(writer); } writer.checkError(); } private boolean writeBuffer(final ByteBuffer buffer, final IoCallback callback) { StringBuilder builder = new StringBuilder(); try { builder.append(charsetDecoder.decode(buffer)); } catch (CharacterCodingException e) { callback.onException(exchange, this, e); return false; } String data = builder.toString(); writer.write(data); return true; } private void invokeOnComplete(final IoCallback callback) { sendThread = null; inCall = Thread.currentThread(); try { callback.onComplete(exchange, this); } finally { inCall = null; } if (Thread.currentThread() != sendThread) { return; } while (next != null) { String next = this.next; IoCallback queuedCallback = this.queuedCallback; this.next = null; this.queuedCallback = null; writer.write(next); if (writer.checkError()) { queuedCallback.onException(exchange, this, new IOException()); } else { sendThread = null; inCall = Thread.currentThread(); try { queuedCallback.onComplete(exchange, this); } finally { inCall = null; } if (Thread.currentThread() != sendThread) { return; } } } } private void queue(final ByteBuffer[] byteBuffers, final IoCallback ioCallback) { //if data is sent from withing the callback we queue it, to prevent the stack growing indefinitely if (next != null || pendingFile != null) { throw UndertowMessages.MESSAGES.dataAlreadyQueued(); } StringBuilder builder = new StringBuilder(); for (ByteBuffer buffer : byteBuffers) { try { builder.append(charsetDecoder.decode(buffer)); } catch (CharacterCodingException e) { ioCallback.onException(exchange, this, e); return; } } this.next = builder.toString(); queuedCallback = ioCallback; } private void queue(final String data, final IoCallback callback) { //if data is sent from withing the callback we queue it, to prevent the stack growing indefinitely if (next != null || pendingFile != null) { throw UndertowMessages.MESSAGES.dataAlreadyQueued(); } next = data; queuedCallback = callback; } private void queue(final FileChannel data, final IoCallback callback) { //if data is sent from withing the callback we queue it, to prevent the stack growing indefinitely if (next != null || pendingFile != null) { throw UndertowMessages.MESSAGES.dataAlreadyQueued(); } pendingFile = data; queuedCallback = callback; } } ContextClassLoaderSetupAction.java000066400000000000000000000032661420065311100342170ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.api.ThreadSetupHandler; /** * @author Stuart Douglas */ public class ContextClassLoaderSetupAction implements ThreadSetupHandler { private final ClassLoader classLoader; public ContextClassLoaderSetupAction(final ClassLoader classLoader) { this.classLoader = classLoader; } @Override public Action create(final Action action) { return new Action() { @Override public T call(HttpServerExchange exchange, C context) throws Exception { final ClassLoader old = SecurityActions.getContextClassLoader(); SecurityActions.setContextClassLoader(classLoader); try { return action.call(exchange, context); } finally { SecurityActions.setContextClassLoader(old); } } }; } } DefaultAuthorizationManager.java000066400000000000000000000106371420065311100337370ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import io.undertow.security.idm.Account; import io.undertow.servlet.api.AuthorizationManager; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.api.SecurityInfo; import io.undertow.servlet.api.SecurityRoleRef; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.TransportGuaranteeType; import io.undertow.servlet.api.SingleConstraintMatch; import javax.servlet.http.HttpServletRequest; import java.util.List; import java.util.Map; import java.util.Set; /** * Default authorization manager that simply implements the rules as specified by the servlet spec * * @author Stuart Douglas */ public class DefaultAuthorizationManager implements AuthorizationManager { public static final DefaultAuthorizationManager INSTANCE = new DefaultAuthorizationManager(); private DefaultAuthorizationManager() { } @Override public boolean isUserInRole(String role, Account account, ServletInfo servletInfo, HttpServletRequest request, Deployment deployment) { final Map> principalVersusRolesMap = deployment.getDeploymentInfo().getPrincipalVersusRolesMap(); final Set roles = principalVersusRolesMap.get(account.getPrincipal().getName()); //TODO: a more efficient imple for (SecurityRoleRef ref : servletInfo.getSecurityRoleRefs()) { if (ref.getRole().equals(role)) { if (roles != null && roles.contains(ref.getLinkedRole())) { return true; } return account.getRoles().contains(ref.getLinkedRole()); } } if (roles != null && roles.contains(role)) { return true; } return account.getRoles().contains(role); } @Override public boolean canAccessResource(List constraints, Account account, ServletInfo servletInfo, HttpServletRequest request, Deployment deployment) { if (constraints == null || constraints.isEmpty()) { return true; } for (final SingleConstraintMatch constraint : constraints) { boolean found = false; Set roleSet = constraint.getRequiredRoles(); if (roleSet.isEmpty() && constraint.getEmptyRoleSemantic() != SecurityInfo.EmptyRoleSemantic.DENY) { /* * The EmptyRoleSemantic was either PERMIT or AUTHENTICATE, either way a roles check is not needed. */ found = true; } else if (account != null) { if(roleSet.contains("**") && !deployment.getDeploymentInfo().getSecurityRoles().contains("**")) { found = true; } else { final Set roles = deployment.getDeploymentInfo().getPrincipalVersusRolesMap().get(account.getPrincipal().getName()); for (String role : roleSet) { if (roles != null) { if (roles.contains(role)) { found = true; break; } } if (account.getRoles().contains(role)) { found = true; break; } } } } if (!found) { return false; } } return true; } @Override public TransportGuaranteeType transportGuarantee(TransportGuaranteeType currentConnectionGuarantee, TransportGuaranteeType configuredRequiredGuarentee, HttpServletRequest request) { return configuredRequiredGuarentee; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/DeploymentImpl.java000066400000000000000000000224031420065311100313120ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.server.HttpHandler; import io.undertow.server.session.SessionManager; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletDispatcher; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.ThreadSetupHandler; import io.undertow.servlet.handlers.ServletInitialHandler; import io.undertow.servlet.handlers.ServletPathMatches; import io.undertow.servlet.spec.ServletContextImpl; /** * Class that represents the mutable state associated with a servlet deployment that is built up * during the bootstrap process. *

    * Classes calling deployment methods during bootstrap must be aware of ordering concerns. * * @author Stuart Douglas */ public class DeploymentImpl implements Deployment { private final DeploymentManager deploymentManager; private final DeploymentInfo deploymentInfo; private final ServletContainer servletContainer; private final List lifecycleObjects = new ArrayList<>(); private final ServletPathMatches servletPaths; private final ManagedServlets servlets; private final ManagedFilters filters; private volatile ApplicationListeners applicationListeners; private volatile ServletContextImpl servletContext; private volatile ServletInitialHandler servletHandler; private volatile HttpHandler initialHandler; private volatile ErrorPages errorPages; private volatile Map mimeExtensionMappings; private volatile SessionManager sessionManager; @Deprecated private volatile Charset defaultCharset = StandardCharsets.ISO_8859_1; private volatile Charset defaultRequestCharset = StandardCharsets.ISO_8859_1; private volatile Charset defaultResponseCharset = StandardCharsets.ISO_8859_1; private volatile List authenticationMechanisms; private volatile List threadSetupActions; /** * user for {@link #tryAddServletMappings(ServletInfo, String...)} * * https://issues.jboss.org/browse/UNDERTOW-1418 */ private Set existingUrlPatterns; public DeploymentImpl(DeploymentManager deploymentManager, final DeploymentInfo deploymentInfo, ServletContainer servletContainer) { this.deploymentManager = deploymentManager; this.deploymentInfo = deploymentInfo; this.servletContainer = servletContainer; servletPaths = new ServletPathMatches(this); servlets = new ManagedServlets(this, servletPaths); filters = new ManagedFilters(this, servletPaths); } @Override public ServletContainer getServletContainer() { return servletContainer; } public ManagedServlets getServlets() { return servlets; } public ManagedFilters getFilters() { return filters; } void setApplicationListeners(final ApplicationListeners applicationListeners) { this.applicationListeners = applicationListeners; } void setServletContext(final ServletContextImpl servletContext) { this.servletContext = servletContext; } @Override public DeploymentInfo getDeploymentInfo() { return deploymentInfo; } @Override public ApplicationListeners getApplicationListeners() { return applicationListeners; } @Override public ServletContextImpl getServletContext() { return servletContext; } @Override public HttpHandler getHandler() { return initialHandler; } public void setInitialHandler(final HttpHandler initialHandler) { this.initialHandler = initialHandler; } void setServletHandler(final ServletInitialHandler servletHandler) { this.servletHandler = servletHandler; } void addLifecycleObjects(final Collection objects) { lifecycleObjects.addAll(objects); } void addLifecycleObjects(final Lifecycle... objects) { lifecycleObjects.addAll(Arrays.asList(objects)); } void setSessionManager(final SessionManager sessionManager) { this.sessionManager = sessionManager; } public List getLifecycleObjects() { return Collections.unmodifiableList(lifecycleObjects); } @Override public ServletPathMatches getServletPaths() { return servletPaths; } void setThreadSetupActions(List threadSetupActions) { this.threadSetupActions = threadSetupActions; } public ThreadSetupHandler.Action createThreadSetupAction(ThreadSetupHandler.Action target) { ThreadSetupHandler.Action ret = target; for(ThreadSetupHandler wrapper : threadSetupActions) { ret = wrapper.create(ret); } return ret; } public ErrorPages getErrorPages() { return errorPages; } public void setErrorPages(final ErrorPages errorPages) { this.errorPages = errorPages; } @Override public Map getMimeExtensionMappings() { return mimeExtensionMappings; } public void setMimeExtensionMappings(final Map mimeExtensionMappings) { this.mimeExtensionMappings = Collections.unmodifiableMap(new HashMap<>(mimeExtensionMappings)); } @Override public ServletDispatcher getServletDispatcher() { return servletHandler; } @Override public SessionManager getSessionManager() { return sessionManager; } @Override public Executor getExecutor() { return deploymentInfo.getExecutor(); } @Override public Executor getAsyncExecutor() { return deploymentInfo.getAsyncExecutor(); } @Deprecated public Charset getDefaultCharset() { return defaultCharset; } @Override public Charset getDefaultRequestCharset() { return defaultRequestCharset; } @Override public Charset getDefaultResponseCharset() { return defaultResponseCharset; } public void setAuthenticationMechanisms(List authenticationMechanisms) { this.authenticationMechanisms = authenticationMechanisms; } @Override public List getAuthenticationMechanisms() { return authenticationMechanisms; } @Override public DeploymentManager.State getDeploymentState() { return deploymentManager.getState(); } @Override public Set tryAddServletMappings(ServletInfo servletInfo, String... urlPatterns) { final Set ret = new HashSet<>(); if(existingUrlPatterns == null) { existingUrlPatterns = new HashSet<>(); for (ServletInfo s : deploymentInfo.getServlets().values()) { if (!s.getName().equals(servletInfo.getName())) { existingUrlPatterns.addAll(s.getMappings()); } } } for (String pattern : urlPatterns) { if (existingUrlPatterns.contains(pattern)) { ret.add(pattern); } } //only update if no changes have been made if (ret.isEmpty()) { for (String pattern : urlPatterns) { existingUrlPatterns.add(pattern); if (!servletInfo.getMappings().contains(pattern)) { servletInfo.addMapping(pattern); } } } getServletPaths().invalidate(); return ret; } @Deprecated public void setDefaultCharset(Charset defaultCharset) { this.defaultCharset = defaultCharset; } public void setDefaultRequestCharset(Charset defaultRequestCharset) { this.defaultRequestCharset = defaultRequestCharset; } public void setDefaultResponseCharset(Charset defaultResponseCharset) { this.defaultResponseCharset = defaultResponseCharset; } void destroy(){ getApplicationListeners().contextDestroyed(); getApplicationListeners().stop(); if (servletContext!=null){ servletContext.destroy(); } servletContext = null; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/DeploymentManagerImpl.java000066400000000000000000001054001420065311100326040ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import io.undertow.Handlers; import io.undertow.predicate.Predicates; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.AuthenticationMechanismFactory; import io.undertow.security.api.NotificationReceiver; import io.undertow.security.api.SecurityContextFactory; import io.undertow.security.handlers.AuthenticationMechanismsHandler; import io.undertow.security.handlers.NotificationReceiverHandler; import io.undertow.security.handlers.SecurityInitialHandler; import io.undertow.security.idm.IdentityManager; import io.undertow.security.impl.BasicAuthenticationMechanism; import io.undertow.security.impl.CachedAuthenticatedSessionMechanism; import io.undertow.security.impl.ClientCertAuthenticationMechanism; import io.undertow.security.impl.DigestAuthenticationMechanism; import io.undertow.security.impl.ExternalAuthenticationMechanism; import io.undertow.security.impl.GenericHeaderAuthenticationMechanism; import io.undertow.security.impl.SecurityContextFactoryImpl; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.HttpContinueReadHandler; import io.undertow.server.handlers.PredicateHandler; import io.undertow.server.handlers.form.FormEncodedDataDefinition; import io.undertow.server.handlers.form.FormParserFactory; import io.undertow.server.session.SessionListener; import io.undertow.server.session.SessionManager; import io.undertow.servlet.ServletExtension; import io.undertow.servlet.UndertowServletLogger; import io.undertow.servlet.UndertowServletMessages; import io.undertow.servlet.api.AuthMethodConfig; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ErrorPage; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.HttpMethodSecurityInfo; import io.undertow.servlet.api.InstanceHandle; import io.undertow.servlet.api.ListenerInfo; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.MetricsCollector; import io.undertow.servlet.api.MimeMapping; import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.SecurityInfo.EmptyRoleSemantic; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletContainerInitializerInfo; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.ServletSecurityInfo; import io.undertow.servlet.api.ServletSessionConfig; import io.undertow.servlet.api.ServletStackTraces; import io.undertow.servlet.api.SessionPersistenceManager; import io.undertow.servlet.api.ThreadSetupHandler; import io.undertow.servlet.api.WebResourceCollection; import io.undertow.servlet.handlers.CrawlerSessionManagerHandler; import io.undertow.servlet.handlers.RedirectDirHandler; import io.undertow.servlet.handlers.SendErrorPageHandler; import io.undertow.servlet.handlers.ServletDispatchingHandler; import io.undertow.servlet.handlers.ServletHandler; import io.undertow.servlet.handlers.ServletInitialHandler; import io.undertow.servlet.handlers.SessionRestoringHandler; import io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler; import io.undertow.servlet.handlers.security.SSLInformationAssociationHandler; import io.undertow.servlet.handlers.security.SecurityPathMatches; import io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler; import io.undertow.servlet.handlers.security.ServletAuthenticationConstraintHandler; import io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler; import io.undertow.servlet.handlers.security.ServletFormAuthenticationMechanism; import io.undertow.servlet.handlers.security.ServletSecurityConstraintHandler; import io.undertow.servlet.predicate.DispatcherTypePredicate; import io.undertow.servlet.spec.ServletContextImpl; import io.undertow.servlet.spec.SessionCookieConfigImpl; import io.undertow.util.MimeMappings; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.ServletException; import java.io.File; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.ServiceLoader; import java.util.Set; import java.util.TreeMap; import static javax.servlet.http.HttpServletRequest.BASIC_AUTH; import static javax.servlet.http.HttpServletRequest.CLIENT_CERT_AUTH; import static javax.servlet.http.HttpServletRequest.DIGEST_AUTH; import static javax.servlet.http.HttpServletRequest.FORM_AUTH; /** * The deployment manager. This manager is responsible for controlling the lifecycle of a servlet deployment. * * @author Stuart Douglas */ public class DeploymentManagerImpl implements DeploymentManager { /** * The original deployment information, this is */ private final DeploymentInfo originalDeployment; private final ServletContainer servletContainer; /** * Current deployment, this may be modified by SCI's */ private volatile DeploymentImpl deployment; private volatile State state = State.UNDEPLOYED; public DeploymentManagerImpl(final DeploymentInfo deployment, final ServletContainer servletContainer) { this.originalDeployment = deployment; this.servletContainer = servletContainer; } @Override public void deploy() { final DeploymentInfo deploymentInfo = originalDeployment.clone(); if (deploymentInfo.getServletStackTraces() == ServletStackTraces.ALL) { UndertowServletLogger.REQUEST_LOGGER.servletStackTracesAll(deploymentInfo.getDeploymentName()); } deploymentInfo.validate(); final DeploymentImpl deployment = new DeploymentImpl(this, deploymentInfo, servletContainer); this.deployment = deployment; final ServletContextImpl servletContext = new ServletContextImpl(servletContainer, deployment); deployment.setServletContext(servletContext); handleExtensions(deploymentInfo, servletContext); final List setup = new ArrayList<>(); setup.add(ServletRequestContextThreadSetupAction.INSTANCE); setup.add(new ContextClassLoaderSetupAction(deploymentInfo.getClassLoader())); setup.addAll(deploymentInfo.getThreadSetupActions()); deployment.setThreadSetupActions(setup); deployment.getServletPaths().setWelcomePages(deploymentInfo.getWelcomePages()); if (deploymentInfo.getDefaultEncoding() != null) { deployment.setDefaultCharset(Charset.forName(deploymentInfo.getDefaultEncoding())); } if(deploymentInfo.getDefaultRequestEncoding() != null) { deployment.setDefaultRequestCharset(Charset.forName(deploymentInfo.getDefaultRequestEncoding())); } else if (deploymentInfo.getDefaultEncoding() != null) { deployment.setDefaultRequestCharset(Charset.forName(deploymentInfo.getDefaultEncoding())); } if(deploymentInfo.getDefaultResponseEncoding() != null) { deployment.setDefaultResponseCharset(Charset.forName(deploymentInfo.getDefaultResponseEncoding())); } else if (deploymentInfo.getDefaultEncoding() != null) { deployment.setDefaultResponseCharset(Charset.forName(deploymentInfo.getDefaultEncoding())); } handleDeploymentSessionConfig(deploymentInfo, servletContext); deployment.setSessionManager(deploymentInfo.getSessionManagerFactory().createSessionManager(deployment)); deployment.getSessionManager().setDefaultSessionTimeout(deploymentInfo.getDefaultSessionTimeout()); try { deployment.createThreadSetupAction(new ThreadSetupHandler.Action() { @Override public Void call(HttpServerExchange exchange, Object ignore) throws Exception { final ApplicationListeners listeners = createListeners(); deployment.setApplicationListeners(listeners); //now create the servlets and filters that we know about. We can still get more later createServletsAndFilters(deployment, deploymentInfo); //first initialize the temp dir initializeTempDir(servletContext, deploymentInfo); //then run the SCI's for (final ServletContainerInitializerInfo sci : deploymentInfo.getServletContainerInitializers()) { final InstanceHandle instance = sci.getInstanceFactory().createInstance(); try { instance.getInstance().onStartup(sci.getHandlesTypes(), servletContext); } finally { instance.release(); } } listeners.start(); deployment.getSessionManager().registerSessionListener(new SessionListenerBridge(deployment, listeners, servletContext)); for(SessionListener listener : deploymentInfo.getSessionListeners()) { deployment.getSessionManager().registerSessionListener(listener); } initializeErrorPages(deployment, deploymentInfo); initializeMimeMappings(deployment, deploymentInfo); listeners.contextInitialized(); //run HttpHandler wrappedHandlers = ServletDispatchingHandler.INSTANCE; wrappedHandlers = wrapHandlers(wrappedHandlers, deploymentInfo.getInnerHandlerChainWrappers()); wrappedHandlers = new RedirectDirHandler(wrappedHandlers, deployment.getServletPaths()); if(!deploymentInfo.isSecurityDisabled()) { HttpHandler securityHandler = setupSecurityHandlers(wrappedHandlers); wrappedHandlers = new PredicateHandler(DispatcherTypePredicate.REQUEST, securityHandler, wrappedHandlers); } HttpHandler outerHandlers = wrapHandlers(wrappedHandlers, deploymentInfo.getOuterHandlerChainWrappers()); outerHandlers = new SendErrorPageHandler(outerHandlers); wrappedHandlers = new PredicateHandler(DispatcherTypePredicate.REQUEST, outerHandlers, wrappedHandlers); wrappedHandlers = handleDevelopmentModePersistentSessions(wrappedHandlers, deploymentInfo, deployment.getSessionManager(), servletContext); MetricsCollector metrics = deploymentInfo.getMetricsCollector(); if(metrics != null) { wrappedHandlers = new MetricsChainHandler(wrappedHandlers, metrics, deployment); } if( deploymentInfo.getCrawlerSessionManagerConfig() != null ) { wrappedHandlers = new CrawlerSessionManagerHandler(deploymentInfo.getCrawlerSessionManagerConfig(), wrappedHandlers); } final ServletInitialHandler servletInitialHandler = SecurityActions.createServletInitialHandler(deployment.getServletPaths(), wrappedHandlers, deployment, servletContext); HttpHandler initialHandler = wrapHandlers(servletInitialHandler, deployment.getDeploymentInfo().getInitialHandlerChainWrappers()); initialHandler = new HttpContinueReadHandler(initialHandler); if(deploymentInfo.getUrlEncoding() != null) { initialHandler = Handlers.urlDecodingHandler(deploymentInfo.getUrlEncoding(), initialHandler); } deployment.setInitialHandler(initialHandler); deployment.setServletHandler(servletInitialHandler); deployment.getServletPaths().invalidate(); //make sure we have a fresh set of servlet paths servletContext.initDone(); return null; } }).call(null, null); } catch (Exception e) { throw new RuntimeException(e); } //any problems with the paths won't get detected until the data is initialize //so we force initialization here deployment.getServletPaths().initData(); for(ServletContextListener listener : deploymentInfo.getDeploymentCompleteListeners()) { listener.contextInitialized(new ServletContextEvent(servletContext)); } state = State.DEPLOYED; } private void createServletsAndFilters(final DeploymentImpl deployment, final DeploymentInfo deploymentInfo) { for (Map.Entry servlet : deploymentInfo.getServlets().entrySet()) { deployment.getServlets().addServlet(servlet.getValue()); } for (Map.Entry filter : deploymentInfo.getFilters().entrySet()) { deployment.getFilters().addFilter(filter.getValue()); } } private void handleExtensions(final DeploymentInfo deploymentInfo, final ServletContextImpl servletContext) { Set loadedExtensions = new HashSet<>(); for (ServletExtension extension : ServiceLoader.load(ServletExtension.class, deploymentInfo.getClassLoader())) { loadedExtensions.add(extension.getClass().getName()); extension.handleDeployment(deploymentInfo, servletContext); } if (ServletExtension.class.getClassLoader() != null && !ServletExtension.class.getClassLoader().equals(deploymentInfo.getClassLoader())) { for (ServletExtension extension : ServiceLoader.load(ServletExtension.class)) { // Note: If the CLs are different, but can the see the same extensions and extension might get loaded // and thus instantiated twice, but the handleDeployment() is executed only once. if (!loadedExtensions.contains(extension.getClass().getName())) { extension.handleDeployment(deploymentInfo, servletContext); } } } for (ServletExtension extension : ServletExtensionHolder.getServletExtensions()) { if (!loadedExtensions.contains(extension.getClass().getName())) { extension.handleDeployment(deploymentInfo, servletContext); } } for(ServletExtension extension : deploymentInfo.getServletExtensions()) { extension.handleDeployment(deploymentInfo, servletContext); } } /** * sets up the outer security handlers. *

    * the handler that actually performs the access check happens later in the chain, it is not setup here * * @param initialHandler The handler to wrap with security handlers */ private HttpHandler setupSecurityHandlers(HttpHandler initialHandler) { final DeploymentInfo deploymentInfo = deployment.getDeploymentInfo(); final LoginConfig loginConfig = deploymentInfo.getLoginConfig(); HttpHandler current = initialHandler; current = new SSLInformationAssociationHandler(current); final SecurityPathMatches securityPathMatches = buildSecurityConstraints(); securityPathMatches.logWarningsAboutUncoveredMethods(); current = new ServletAuthenticationCallHandler(current); for(HandlerWrapper wrapper : deploymentInfo.getSecurityWrappers()) { current = wrapper.wrap(current); } if(deploymentInfo.isDisableCachingForSecuredPages()) { current = Handlers.predicate(Predicates.authRequired(), Handlers.disableCache(current), current); } if (!securityPathMatches.isEmpty()) { current = new ServletAuthenticationConstraintHandler(current); } current = new ServletConfidentialityConstraintHandler(deploymentInfo.getConfidentialPortManager(), current); if (!securityPathMatches.isEmpty()) { current = new ServletSecurityConstraintHandler(securityPathMatches, current); } HandlerWrapper initialSecurityWrapper = deploymentInfo.getInitialSecurityWrapper(); String mechName = null; if (initialSecurityWrapper == null) { final Map factoryMap = new HashMap<>(deploymentInfo.getAuthenticationMechanisms()); final IdentityManager identityManager = deploymentInfo.getIdentityManager(); if(!factoryMap.containsKey(BASIC_AUTH)) { factoryMap.put(BASIC_AUTH, BasicAuthenticationMechanism.FACTORY); } if(!factoryMap.containsKey(FORM_AUTH)) { factoryMap.put(FORM_AUTH, ServletFormAuthenticationMechanism.FACTORY); } if(!factoryMap.containsKey(DIGEST_AUTH)) { factoryMap.put(DIGEST_AUTH, DigestAuthenticationMechanism.FACTORY); } if(!factoryMap.containsKey(CLIENT_CERT_AUTH)) { factoryMap.put(CLIENT_CERT_AUTH, ClientCertAuthenticationMechanism.FACTORY); } if(!factoryMap.containsKey(ExternalAuthenticationMechanism.NAME)) { factoryMap.put(ExternalAuthenticationMechanism.NAME, ExternalAuthenticationMechanism.FACTORY); } if(!factoryMap.containsKey(GenericHeaderAuthenticationMechanism.NAME)) { factoryMap.put(GenericHeaderAuthenticationMechanism.NAME, GenericHeaderAuthenticationMechanism.FACTORY); } List authenticationMechanisms = new LinkedList<>(); if(deploymentInfo.isUseCachedAuthenticationMechanism()) { authenticationMechanisms.add(new CachedAuthenticatedSessionMechanism(identityManager)); } if (loginConfig != null || deploymentInfo.getJaspiAuthenticationMechanism() != null) { //we don't allow multipart requests, and use the default encoding when it's set FormEncodedDataDefinition formEncodedDataDefinition = new FormEncodedDataDefinition(); String reqEncoding = deploymentInfo.getDefaultRequestEncoding(); if(reqEncoding == null) { reqEncoding = deploymentInfo.getDefaultEncoding(); } if (reqEncoding != null) { formEncodedDataDefinition.setDefaultEncoding(reqEncoding); } FormParserFactory parser = FormParserFactory.builder(false) .addParser(formEncodedDataDefinition) .build(); List authMethods = Collections.emptyList(); if(loginConfig != null) { authMethods = loginConfig.getAuthMethods(); } for(AuthMethodConfig method : authMethods) { AuthenticationMechanismFactory factory = factoryMap.get(method.getName()); if(factory == null) { throw UndertowServletMessages.MESSAGES.unknownAuthenticationMechanism(method.getName()); } if(mechName == null) { mechName = method.getName(); } final Map properties = new HashMap<>(); properties.put(AuthenticationMechanismFactory.CONTEXT_PATH, deploymentInfo.getContextPath()); properties.put(AuthenticationMechanismFactory.REALM, loginConfig.getRealmName()); properties.put(AuthenticationMechanismFactory.ERROR_PAGE, loginConfig.getErrorPage()); properties.put(AuthenticationMechanismFactory.LOGIN_PAGE, loginConfig.getLoginPage()); properties.putAll(method.getProperties()); String name = method.getName().toUpperCase(Locale.US); // The mechanism name is passed in from the HttpServletRequest interface as the name reported needs to be // comparable using '==' name = name.equals(FORM_AUTH) ? FORM_AUTH : name; name = name.equals(BASIC_AUTH) ? BASIC_AUTH : name; name = name.equals(DIGEST_AUTH) ? DIGEST_AUTH : name; name = name.equals(CLIENT_CERT_AUTH) ? CLIENT_CERT_AUTH : name; authenticationMechanisms.add(factory.create(name, identityManager, parser, properties)); } } deployment.setAuthenticationMechanisms(authenticationMechanisms); //if the JASPI auth mechanism is set then it takes over if(deploymentInfo.getJaspiAuthenticationMechanism() == null) { current = new AuthenticationMechanismsHandler(current, authenticationMechanisms); } else { current = new AuthenticationMechanismsHandler(current, Collections.singletonList(deploymentInfo.getJaspiAuthenticationMechanism())); } current = new CachedAuthenticatedSessionHandler(current, this.deployment.getServletContext()); } List notificationReceivers = deploymentInfo.getNotificationReceivers(); if (!notificationReceivers.isEmpty()) { current = new NotificationReceiverHandler(current, notificationReceivers); } if (initialSecurityWrapper == null) { // TODO - A switch to constraint driven could be configurable, however before we can support that with servlets we would // need additional tracking within sessions if a servlet has specifically requested that authentication occurs. SecurityContextFactory contextFactory = deploymentInfo.getSecurityContextFactory(); if (contextFactory == null) { contextFactory = SecurityContextFactoryImpl.INSTANCE; } current = new SecurityInitialHandler(deploymentInfo.getAuthenticationMode(), deploymentInfo.getIdentityManager(), mechName, contextFactory, current); } else { current = initialSecurityWrapper.wrap(current); } return current; } private SecurityPathMatches buildSecurityConstraints() { SecurityPathMatches.Builder builder = SecurityPathMatches.builder(deployment.getDeploymentInfo()); final Set urlPatterns = new HashSet<>(); for (SecurityConstraint constraint : deployment.getDeploymentInfo().getSecurityConstraints()) { builder.addSecurityConstraint(constraint); for (WebResourceCollection webResources : constraint.getWebResourceCollections()) { urlPatterns.addAll(webResources.getUrlPatterns()); } } for (final ServletInfo servlet : deployment.getDeploymentInfo().getServlets().values()) { final ServletSecurityInfo securityInfo = servlet.getServletSecurityInfo(); if (securityInfo != null) { final Set mappings = new HashSet<>(servlet.getMappings()); mappings.removeAll(urlPatterns); if (!mappings.isEmpty()) { final Set methods = new HashSet<>(); for (HttpMethodSecurityInfo method : securityInfo.getHttpMethodSecurityInfo()) { methods.add(method.getMethod()); if (method.getRolesAllowed().isEmpty() && method.getEmptyRoleSemantic() == EmptyRoleSemantic.PERMIT) { //this is an implict allow continue; } SecurityConstraint newConstraint = new SecurityConstraint() .addRolesAllowed(method.getRolesAllowed()) .setTransportGuaranteeType(method.getTransportGuaranteeType()) .addWebResourceCollection(new WebResourceCollection().addUrlPatterns(mappings) .addHttpMethod(method.getMethod())); builder.addSecurityConstraint(newConstraint); } //now add the constraint, unless it has all default values and method constrains where specified if (!securityInfo.getRolesAllowed().isEmpty() || securityInfo.getEmptyRoleSemantic() != EmptyRoleSemantic.PERMIT || methods.isEmpty()) { SecurityConstraint newConstraint = new SecurityConstraint() .setEmptyRoleSemantic(securityInfo.getEmptyRoleSemantic()) .addRolesAllowed(securityInfo.getRolesAllowed()) .setTransportGuaranteeType(securityInfo.getTransportGuaranteeType()) .addWebResourceCollection(new WebResourceCollection().addUrlPatterns(mappings) .addHttpMethodOmissions(methods)); builder.addSecurityConstraint(newConstraint); } } } } return builder.build(); } private void initializeTempDir(final ServletContextImpl servletContext, final DeploymentInfo deploymentInfo) { if (deploymentInfo.getTempDir() != null) { servletContext.setAttribute(ServletContext.TEMPDIR, deploymentInfo.getTempDir()); } else { servletContext.setAttribute(ServletContext.TEMPDIR, new File(SecurityActions.getSystemProperty("java.io.tmpdir"))); } } private void initializeMimeMappings(final DeploymentImpl deployment, final DeploymentInfo deploymentInfo) { final Map mappings = new HashMap<>(MimeMappings.DEFAULT_MIME_MAPPINGS); for (MimeMapping mapping : deploymentInfo.getMimeMappings()) { mappings.put(mapping.getExtension().toLowerCase(Locale.ENGLISH), mapping.getMimeType()); } deployment.setMimeExtensionMappings(mappings); } private void initializeErrorPages(final DeploymentImpl deployment, final DeploymentInfo deploymentInfo) { final Map codes = new HashMap<>(); final Map, String> exceptions = new HashMap<>(); String defaultErrorPage = null; for (final ErrorPage page : deploymentInfo.getErrorPages()) { if (page.getExceptionType() != null) { exceptions.put(page.getExceptionType(), page.getLocation()); } else if (page.getErrorCode() != null) { codes.put(page.getErrorCode(), page.getLocation()); } else { if (defaultErrorPage != null) { throw UndertowServletMessages.MESSAGES.moreThanOneDefaultErrorPage(defaultErrorPage, page.getLocation()); } else { defaultErrorPage = page.getLocation(); } } } deployment.setErrorPages(new ErrorPages(codes, exceptions, defaultErrorPage)); } private ApplicationListeners createListeners() { final List managedListeners = new ArrayList<>(); for (final ListenerInfo listener : deployment.getDeploymentInfo().getListeners()) { managedListeners.add(new ManagedListener(listener, listener.isProgramatic())); } return new ApplicationListeners(managedListeners, deployment.getServletContext()); } private static HttpHandler wrapHandlers(final HttpHandler wrapee, final List wrappers) { HttpHandler current = wrapee; for (HandlerWrapper wrapper : wrappers) { current = wrapper.wrap(current); } return current; } @Override public HttpHandler start() throws ServletException { try { return deployment.createThreadSetupAction(new ThreadSetupHandler.Action() { @Override public HttpHandler call(HttpServerExchange exchange, Object ignore) throws ServletException { deployment.getSessionManager().start(); //we need to copy before iterating //because listeners can add other listeners ArrayList lifecycles = new ArrayList<>(deployment.getLifecycleObjects()); for (Lifecycle object : lifecycles) { object.start(); } HttpHandler root = deployment.getHandler(); final TreeMap> loadOnStartup = new TreeMap<>(); for (Map.Entry entry : deployment.getServlets().getServletHandlers().entrySet()) { ManagedServlet servlet = entry.getValue().getManagedServlet(); Integer loadOnStartupNumber = servlet.getServletInfo().getLoadOnStartup(); if (loadOnStartupNumber != null) { if (loadOnStartupNumber < 0) { continue; } List list = loadOnStartup.get(loadOnStartupNumber); if (list == null) { loadOnStartup.put(loadOnStartupNumber, list = new ArrayList<>()); } list.add(servlet); } } for (Map.Entry> load : loadOnStartup.entrySet()) { for (ManagedServlet servlet : load.getValue()) { servlet.createServlet(); } } if (deployment.getDeploymentInfo().isEagerFilterInit()) { for (ManagedFilter filter : deployment.getFilters().getFilters().values()) { filter.createFilter(); } } state = State.STARTED; return root; } }).call(null, null); } catch (ServletException|RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } } @Override public void stop() throws ServletException { try { deployment.createThreadSetupAction(new ThreadSetupHandler.Action() { @Override public Void call(HttpServerExchange exchange, Object ignore) throws ServletException { for (Lifecycle object : deployment.getLifecycleObjects()) { try { object.stop(); } catch (Throwable t) { UndertowServletLogger.ROOT_LOGGER.failedToDestroy(object, t); } } deployment.getSessionManager().stop(); state = State.DEPLOYED; return null; } }).call(null, null); } catch (ServletException|RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } } private HttpHandler handleDevelopmentModePersistentSessions(HttpHandler next, final DeploymentInfo deploymentInfo, final SessionManager sessionManager, final ServletContextImpl servletContext) { final SessionPersistenceManager sessionPersistenceManager = deploymentInfo.getSessionPersistenceManager(); if (sessionPersistenceManager != null) { SessionRestoringHandler handler = new SessionRestoringHandler(deployment.getDeploymentInfo().getDeploymentName(), sessionManager, servletContext, next, sessionPersistenceManager); deployment.addLifecycleObjects(handler); return handler; } return next; } public void handleDeploymentSessionConfig(DeploymentInfo deploymentInfo, ServletContextImpl servletContext) { SessionCookieConfigImpl sessionCookieConfig = servletContext.getSessionCookieConfig(); ServletSessionConfig sc = deploymentInfo.getServletSessionConfig(); if (sc != null) { sessionCookieConfig.setName(sc.getName()); sessionCookieConfig.setComment(sc.getComment()); sessionCookieConfig.setDomain(sc.getDomain()); sessionCookieConfig.setHttpOnly(sc.isHttpOnly()); sessionCookieConfig.setMaxAge(sc.getMaxAge()); if(sc.getPath() != null) { sessionCookieConfig.setPath(sc.getPath()); } else { sessionCookieConfig.setPath(deploymentInfo.getContextPath()); } sessionCookieConfig.setSecure(sc.isSecure()); if (sc.getSessionTrackingModes() != null) { servletContext.setDefaultSessionTrackingModes(new HashSet<>(sc.getSessionTrackingModes())); } } } @Override public void undeploy() { try { deployment.createThreadSetupAction(new ThreadSetupHandler.Action() { @Override public Void call(HttpServerExchange exchange, Object ignore) throws ServletException { for(ServletContextListener listener : deployment.getDeploymentInfo().getDeploymentCompleteListeners()) { try { listener.contextDestroyed(new ServletContextEvent(deployment.getServletContext())); } catch (Throwable t) { UndertowServletLogger.REQUEST_LOGGER.failedToDestroy(listener, t); } } deployment.destroy(); deployment = null; state = State.UNDEPLOYED; return null; } }).call(null, null); } catch (Exception e) { throw new RuntimeException(e); } } @Override public State getState() { return state; } @Override public Deployment getDeployment() { return deployment; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/ErrorPages.java000066400000000000000000000061441420065311100304250ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import io.undertow.util.StatusCodes; import java.util.Map; import javax.servlet.ServletException; /** * Class that maintains information about error page mappings. * * @author Stuart Douglas */ public class ErrorPages { private final Map errorCodeLocations; private final Map, String> exceptionMappings; private final String defaultErrorPage; public ErrorPages(final Map errorCodeLocations, final Map, String> exceptionMappings, final String defaultErrorPage) { this.errorCodeLocations = errorCodeLocations; this.exceptionMappings = exceptionMappings; this.defaultErrorPage = defaultErrorPage; } public String getErrorLocation(final int code) { String location = errorCodeLocations.get(code); if (location == null) { return defaultErrorPage; } return location; } public String getErrorLocation(final Throwable exception) { if (exception == null) { return null; } //todo: this is kinda slow, but there is probably not a great deal that can be done about it String location = null; for (Class c = exception.getClass(); c != null && location == null; c = c.getSuperclass()) { location = exceptionMappings.get(c); } if (location == null && exception instanceof ServletException) { Throwable rootCause = ((ServletException) exception).getRootCause(); //Iterate through any nested JasperException in case it is in JSP development mode while (rootCause != null && rootCause instanceof ServletException && location == null) { for (Class c = rootCause.getClass(); c != null && location == null; c = c.getSuperclass()) { location = exceptionMappings.get(c); } rootCause = ((ServletException) rootCause).getRootCause(); } if (rootCause != null && location == null) { for (Class c = rootCause.getClass(); c != null && location == null; c = c.getSuperclass()) { location = exceptionMappings.get(c); } } } if (location == null) { location = getErrorLocation(StatusCodes.INTERNAL_SERVER_ERROR); } return location; } } InMemorySessionManagerFactory.java000066400000000000000000000036771420065311100342330ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import io.undertow.server.session.InMemorySessionManager; import io.undertow.server.session.SessionManager; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.api.SessionManagerFactory; /** * Session manager factory that creates an in-memory session manager * @author Paul Ferraro */ public class InMemorySessionManagerFactory implements SessionManagerFactory { private final int maxSessions; private final boolean expireOldestUnusedSessionOnMax; public InMemorySessionManagerFactory() { this(-1, false); } public InMemorySessionManagerFactory(int maxSessions) { this(maxSessions, false); } public InMemorySessionManagerFactory(int maxSessions, boolean expireOldestUnusedSessionOnMax) { this.maxSessions = maxSessions; this.expireOldestUnusedSessionOnMax = expireOldestUnusedSessionOnMax; } @Override public SessionManager createSessionManager(Deployment deployment) { return new InMemorySessionManager(deployment.getDeploymentInfo().getSessionIdGenerator(), deployment.getDeploymentInfo().getDeploymentName(), maxSessions, expireOldestUnusedSessionOnMax, deployment.getDeploymentInfo().getMetricsCollector() != null); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/Lifecycle.java000066400000000000000000000017701420065311100302530ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import javax.servlet.ServletException; /** * * An object that can be started or stopped. * * @author Stuart Douglas */ public interface Lifecycle { void start() throws ServletException; void stop() throws ServletException; boolean isStarted(); } LifecyleInterceptorInvocation.java000066400000000000000000000101411420065311100342720ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.LifecycleInterceptor; import io.undertow.servlet.api.ServletInfo; import javax.servlet.Filter; import javax.servlet.FilterConfig; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import java.util.List; /** * Utility class for invoking servlet and filter lifecycle methods. */ class LifecyleInterceptorInvocation implements LifecycleInterceptor.LifecycleContext { private final List list; private final ServletInfo servletInfo; private final FilterInfo filterInfo; private final Servlet servlet; private final Filter filter; private int i; private final ServletConfig servletConfig; private final FilterConfig filterConfig; LifecyleInterceptorInvocation(List list, ServletInfo servletInfo, Servlet servlet, ServletConfig servletConfig) { this.list = list; this.servletInfo = servletInfo; this.servlet = servlet; this.servletConfig = servletConfig; this.filter = null; this.filterConfig = null; this.filterInfo = null; i = list.size(); } LifecyleInterceptorInvocation(List list, ServletInfo servletInfo, Servlet servlet) { this.list = list; this.servlet = servlet; this.servletInfo = servletInfo; this.filterInfo = null; this.servletConfig = null; this.filter = null; this.filterConfig = null; i = list.size(); } LifecyleInterceptorInvocation(List list, FilterInfo filterInfo, Filter filter, FilterConfig filterConfig) { this.list = list; this.servlet = null; this.servletConfig = null; this.filter = filter; this.filterConfig = filterConfig; this.filterInfo = filterInfo; this.servletInfo = null; i = list.size(); } LifecyleInterceptorInvocation(List list, FilterInfo filterInfo, Filter filter) { this.list = list; this.servlet = null; this.servletConfig = null; this.filter = filter; this.filterConfig = null; this.filterInfo = filterInfo; this.servletInfo = null; i = list.size(); } @Override public void proceed() throws ServletException { if (--i >= 0) { final LifecycleInterceptor next = list.get(i); if(filter != null) { if(filterConfig == null) { next.destroy(filterInfo, filter, this); } else { next.init(filterInfo, filter, this); } } else { if(servletConfig == null) { next.destroy(servletInfo, servlet, this); } else { next.init(servletInfo, servlet, this); } } } else if (i == -1) { if(filter != null) { if(filterConfig == null) { filter.destroy(); } else { filter.init(filterConfig); } } else { if(servletConfig == null) { servlet.destroy(); } else { servlet.init(servletConfig); } } } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/ManagedFilter.java000066400000000000000000000102461420065311100310540ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import io.undertow.servlet.UndertowServletLogger; import io.undertow.servlet.UndertowServletMessages; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.InstanceHandle; import io.undertow.servlet.spec.FilterConfigImpl; import io.undertow.servlet.spec.ServletContextImpl; /** * @author Stuart Douglas */ public class ManagedFilter implements Lifecycle { private final FilterInfo filterInfo; private final ServletContextImpl servletContext; private volatile boolean started = false; private volatile Filter filter; private volatile InstanceHandle handle; public ManagedFilter(final FilterInfo filterInfo, final ServletContextImpl servletContext) { this.filterInfo = filterInfo; this.servletContext = servletContext; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if(servletContext.getDeployment().getDeploymentState() != DeploymentManager.State.STARTED) { throw UndertowServletMessages.MESSAGES.deploymentStopped(servletContext.getDeployment().getDeploymentInfo().getDeploymentName()); } if (!started) { start(); } getFilter().doFilter(request, response, chain); } private Filter getFilter() throws ServletException { if (filter == null) { createFilter(); } return filter; } public void createFilter() throws ServletException { synchronized (this) { if (filter == null) { try { handle = filterInfo.getInstanceFactory().createInstance(); } catch (Exception e) { throw UndertowServletMessages.MESSAGES.couldNotInstantiateComponent(filterInfo.getName(), e); } Filter filter = handle.getInstance(); new LifecyleInterceptorInvocation(servletContext.getDeployment().getDeploymentInfo().getLifecycleInterceptors(), filterInfo, filter, new FilterConfigImpl(filterInfo, servletContext)).proceed(); this.filter = filter; } } } public synchronized void start() throws ServletException { if (!started) { started = true; } } public synchronized void stop() { started = false; if (handle != null) { try { new LifecyleInterceptorInvocation(servletContext.getDeployment().getDeploymentInfo().getLifecycleInterceptors(), filterInfo, filter).proceed(); } catch (Exception e) { UndertowServletLogger.ROOT_LOGGER.failedToDestroy(filterInfo, e); } handle.release(); } filter = null; handle = null; } @Override public boolean isStarted() { return started; } public FilterInfo getFilterInfo() { return filterInfo; } @Override public String toString() { return "ManagedFilter{" + "filterInfo=" + filterInfo + '}'; } public void forceInit() throws ServletException { if (filter == null) { createFilter(); } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/ManagedFilters.java000066400000000000000000000040771420065311100312440ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import java.util.HashMap; import java.util.Map; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.handlers.ServletPathMatches; import io.undertow.util.CopyOnWriteMap; /** * Runtime representation of filters. Basically a container for {@link io.undertow.servlet.core.ManagedFilter} instances * * @author Stuart Douglas */ public class ManagedFilters { private final Map managedFilterMap = new CopyOnWriteMap<>(); private final DeploymentImpl deployment; private final ServletPathMatches servletPathMatches; public ManagedFilters(final DeploymentImpl deployment, final ServletPathMatches servletPathMatches) { this.deployment = deployment; this.servletPathMatches = servletPathMatches; } public ManagedFilter addFilter(final FilterInfo filterInfo) { ManagedFilter managedFilter = new ManagedFilter(filterInfo, deployment.getServletContext()); managedFilterMap.put(filterInfo.getName(),managedFilter); deployment.addLifecycleObjects(managedFilter); servletPathMatches.invalidate(); return managedFilter; } public ManagedFilter getManagedFilter(final String name) { return managedFilterMap.get(name); } public Map getFilters() { return new HashMap<>(managedFilterMap); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/ManagedListener.java000066400000000000000000000050001420065311100314040ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import java.util.EventListener; import javax.servlet.ServletException; import io.undertow.servlet.UndertowServletMessages; import io.undertow.servlet.api.InstanceHandle; import io.undertow.servlet.api.ListenerInfo; /** * @author Stuart Douglas */ public class ManagedListener implements Lifecycle { private final ListenerInfo listenerInfo; private final boolean programatic; private volatile boolean started = false; private volatile InstanceHandle handle; public ManagedListener(final ListenerInfo listenerInfo, final boolean programatic) { this.listenerInfo = listenerInfo; this.programatic = programatic; } public synchronized void start() throws ServletException { if (!started) { try { handle = listenerInfo.getInstanceFactory().createInstance(); } catch (Exception e) { throw UndertowServletMessages.MESSAGES.couldNotInstantiateComponent(listenerInfo.getListenerClass().getName(), e); } started = true; } } public synchronized void stop() { started = false; if (handle != null) { handle.release(); } } public ListenerInfo getListenerInfo() { return listenerInfo; } @Override public boolean isStarted() { return started; } public EventListener instance() { if (!started) { throw UndertowServletMessages.MESSAGES.listenerIsNotStarted(); } return handle.getInstance(); } public boolean isProgramatic() { return programatic; } @Override public String toString() { return "ManagedListener{" + "listenerInfo=" + listenerInfo + '}'; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/ManagedServlet.java000066400000000000000000000362111420065311100312530ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Date; import java.util.List; import java.util.concurrent.atomic.AtomicLongFieldUpdater; import javax.servlet.MultipartConfigElement; import javax.servlet.Servlet; import javax.servlet.ServletException; import javax.servlet.SingleThreadModel; import javax.servlet.UnavailableException; import io.undertow.server.handlers.form.FormEncodedDataDefinition; import io.undertow.server.handlers.form.FormParserFactory; import io.undertow.server.handlers.form.MultiPartParserDefinition; import io.undertow.server.handlers.resource.ResourceChangeListener; import io.undertow.server.handlers.resource.ResourceManager; import io.undertow.servlet.UndertowServletLogger; import io.undertow.servlet.UndertowServletMessages; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.InstanceFactory; import io.undertow.servlet.api.InstanceHandle; import io.undertow.servlet.api.LifecycleInterceptor; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.spec.ServletConfigImpl; import io.undertow.servlet.spec.ServletContextImpl; /** * Manager for a servlets lifecycle. * * @author Stuart Douglas */ public class ManagedServlet implements Lifecycle { private final ServletInfo servletInfo; private final ServletContextImpl servletContext; private volatile boolean started = false; private final InstanceStrategy instanceStrategy; private volatile boolean permanentlyUnavailable = false; private long maxMultipartRequestSize; private FormParserFactory formParserFactory; private MultipartConfigElement multipartConfig; private static final AtomicLongFieldUpdater unavailableUntilUpdater = AtomicLongFieldUpdater.newUpdater(ManagedServlet.class, "unavailableUntil"); @SuppressWarnings("unused") private volatile long unavailableUntil = 0; public ManagedServlet(final ServletInfo servletInfo, final ServletContextImpl servletContext) { this.servletInfo = servletInfo; this.servletContext = servletContext; if (SingleThreadModel.class.isAssignableFrom(servletInfo.getServletClass())) { instanceStrategy = new SingleThreadModelPoolStrategy(servletInfo.getInstanceFactory(), servletInfo, servletContext); } else { instanceStrategy = new DefaultInstanceStrategy(servletInfo.getInstanceFactory(), servletInfo, servletContext); } setupMultipart(servletContext); } public void setupMultipart(ServletContextImpl servletContext) { FormEncodedDataDefinition formDataParser = new FormEncodedDataDefinition() .setDefaultEncoding(servletContext.getDeployment().getDefaultRequestCharset().name()); MultipartConfigElement multipartConfig = servletInfo.getMultipartConfig(); if(multipartConfig == null) { multipartConfig = servletContext.getDeployment().getDeploymentInfo().getDefaultMultipartConfig(); } this.multipartConfig = multipartConfig; if (multipartConfig != null) { //todo: fileSizeThreshold MultipartConfigElement config = multipartConfig; if (config.getMaxRequestSize() != -1) { maxMultipartRequestSize = config.getMaxRequestSize(); } else { maxMultipartRequestSize = -1; } final Path tempDir; if(config.getLocation() == null || config.getLocation().isEmpty()) { tempDir = servletContext.getDeployment().getDeploymentInfo().getTempPath(); } else { String location = config.getLocation(); Path locFile = Paths.get(location); if(locFile.isAbsolute()) { tempDir = locFile; } else { final DeploymentInfo deploymentInfo = servletContext.getDeployment().getDeploymentInfo(); tempDir = deploymentInfo.requireTempPath().resolve(location); } } MultiPartParserDefinition multiPartParserDefinition = new MultiPartParserDefinition(tempDir); if(config.getMaxFileSize() > 0) { multiPartParserDefinition.setMaxIndividualFileSize(config.getMaxFileSize()); } if (config.getFileSizeThreshold() > 0) { multiPartParserDefinition.setFileSizeThreshold(config.getFileSizeThreshold()); } multiPartParserDefinition.setDefaultEncoding(servletContext.getDeployment().getDefaultRequestCharset().name()); formParserFactory = FormParserFactory.builder(false) .addParser(formDataParser) .addParser(multiPartParserDefinition) .build(); } else { //no multipart config we don't allow multipart requests formParserFactory = FormParserFactory.builder(false).addParser(formDataParser).build(); maxMultipartRequestSize = -1; } } public synchronized void start() throws ServletException { } public void createServlet() throws ServletException { if (permanentlyUnavailable) { return; } try { if (!started && servletInfo.getLoadOnStartup() != null && servletInfo.getLoadOnStartup() >= 0) { instanceStrategy.start(); started = true; } } catch (UnavailableException e) { if (e.isPermanent()) { permanentlyUnavailable = true; stop(); } } } public synchronized void stop() { if (started) { instanceStrategy.stop(); } started = false; } @Override public boolean isStarted() { return started; } public boolean isPermanentlyUnavailable() { return permanentlyUnavailable; } public boolean isTemporarilyUnavailable() { long until = unavailableUntil; if (until != 0) { if (System.currentTimeMillis() < until) { return true; } else { unavailableUntilUpdater.compareAndSet(this, until, 0); } } return false; } public void setPermanentlyUnavailable(final boolean permanentlyUnavailable) { this.permanentlyUnavailable = permanentlyUnavailable; } public InstanceHandle getServlet() throws ServletException { if(servletContext.getDeployment().getDeploymentState() != DeploymentManager.State.STARTED) { throw UndertowServletMessages.MESSAGES.deploymentStopped(servletContext.getDeployment().getDeploymentInfo().getDeploymentName()); } if (!started) { synchronized (this) { if (!started) { instanceStrategy.start(); started = true; } } } return instanceStrategy.getServlet(); } public void forceInit() throws ServletException { if (!started) { if(servletContext.getDeployment().getDeploymentState() != DeploymentManager.State.STARTED) { throw UndertowServletMessages.MESSAGES.deploymentStopped(servletContext.getDeployment().getDeploymentInfo().getDeploymentName()); } synchronized (this) { if (!started) { try { instanceStrategy.start(); } catch (UnavailableException e) { handleUnavailableException(e); } started = true; } } } } public void handleUnavailableException(UnavailableException e) { if (e.isPermanent()) { UndertowServletLogger.REQUEST_LOGGER.stoppingServletDueToPermanentUnavailability(getServletInfo().getName(), e); stop(); setPermanentlyUnavailable(true); } else { long until = System.currentTimeMillis() + e.getUnavailableSeconds() * 1000; unavailableUntilUpdater.set(this, until); UndertowServletLogger.REQUEST_LOGGER.stoppingServletUntilDueToTemporaryUnavailability(getServletInfo().getName(), new Date(until), e); } } public ServletInfo getServletInfo() { return servletInfo; } /** * This value determines max multipart message size * @return */ public long getMaxRequestSize() { return maxMultipartRequestSize; } public FormParserFactory getFormParserFactory() { return formParserFactory; } public MultipartConfigElement getMultipartConfig() { return multipartConfig; } @Override public String toString() { return "ManagedServlet{" + "servletInfo=" + servletInfo + '}'; } /** * interface used to abstract the difference between single thread model servlets and normal servlets */ interface InstanceStrategy { void start() throws ServletException; void stop(); InstanceHandle getServlet() throws ServletException; } /** * The default servlet pooling strategy that just uses a single instance for all requests */ private static class DefaultInstanceStrategy implements InstanceStrategy { private final InstanceFactory factory; private final ServletInfo servletInfo; private final ServletContextImpl servletContext; private volatile InstanceHandle handle; private volatile Servlet instance; private ResourceChangeListener changeListener; private final InstanceHandle instanceHandle = new InstanceHandle() { @Override public Servlet getInstance() { return instance; } @Override public void release() { } }; DefaultInstanceStrategy(final InstanceFactory factory, final ServletInfo servletInfo, final ServletContextImpl servletContext) { this.factory = factory; this.servletInfo = servletInfo; this.servletContext = servletContext; } public synchronized void start() throws ServletException { try { handle = factory.createInstance(); } catch (Exception e) { throw UndertowServletMessages.MESSAGES.couldNotInstantiateComponent(servletInfo.getName(), e); } instance = handle.getInstance(); new LifecyleInterceptorInvocation(servletContext.getDeployment().getDeploymentInfo().getLifecycleInterceptors(), servletInfo, instance, new ServletConfigImpl(servletInfo, servletContext)).proceed(); //if a servlet implements FileChangeCallback it will be notified of file change events final ResourceManager resourceManager = servletContext.getDeployment().getDeploymentInfo().getResourceManager(); if(instance instanceof ResourceChangeListener && resourceManager.isResourceChangeListenerSupported()) { resourceManager.registerResourceChangeListener(changeListener = (ResourceChangeListener) instance); } } public synchronized void stop() { if (handle != null) { final ResourceManager resourceManager = servletContext.getDeployment().getDeploymentInfo().getResourceManager(); if(changeListener != null) { resourceManager.removeResourceChangeListener(changeListener); } invokeDestroy(); handle.release(); } } private void invokeDestroy() { List interceptors = servletContext.getDeployment().getDeploymentInfo().getLifecycleInterceptors(); try { new LifecyleInterceptorInvocation(interceptors, servletInfo, instance).proceed(); } catch (Exception e) { UndertowServletLogger.ROOT_LOGGER.failedToDestroy(servletInfo, e); } } public InstanceHandle getServlet() { return instanceHandle; } } /** * pooling strategy for single thread model servlet */ private static class SingleThreadModelPoolStrategy implements InstanceStrategy { private final InstanceFactory factory; private final ServletInfo servletInfo; private final ServletContextImpl servletContext; private SingleThreadModelPoolStrategy(final InstanceFactory factory, final ServletInfo servletInfo, final ServletContextImpl servletContext) { this.factory = factory; this.servletInfo = servletInfo; this.servletContext = servletContext; } @Override public void start() throws ServletException { if(servletInfo.getLoadOnStartup() != null) { //see UNDERTOW-734, make sure init method is called for load on startup getServlet().release(); } } @Override public void stop() { } @Override public InstanceHandle getServlet() throws ServletException { final InstanceHandle instanceHandle; final Servlet instance; //TODO: pooling try { instanceHandle = factory.createInstance(); } catch (Exception e) { throw UndertowServletMessages.MESSAGES.couldNotInstantiateComponent(servletInfo.getName(), e); } instance = instanceHandle.getInstance(); new LifecyleInterceptorInvocation(servletContext.getDeployment().getDeploymentInfo().getLifecycleInterceptors(), servletInfo, instance, new ServletConfigImpl(servletInfo, servletContext)).proceed(); return new InstanceHandle() { @Override public Servlet getInstance() { return instance; } @Override public void release() { try { instance.destroy(); } catch (Throwable t) { UndertowServletLogger.REQUEST_LOGGER.failedToDestroy(instance, t); } instanceHandle.release(); } }; } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/ManagedServlets.java000066400000000000000000000046701420065311100314420ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import java.util.HashMap; import java.util.Map; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.handlers.ServletHandler; import io.undertow.servlet.handlers.ServletPathMatches; import io.undertow.util.CopyOnWriteMap; /** * Runtime representation of servlets. Basically a container for {@link ManagedServlet} instances * * @author Stuart Douglas */ public class ManagedServlets { private final Map managedServletMap = new CopyOnWriteMap<>(); private final DeploymentImpl deployment; private final ServletPathMatches servletPaths; public ManagedServlets(final DeploymentImpl deployment, final ServletPathMatches servletPaths) { this.deployment = deployment; this.servletPaths = servletPaths; } public ServletHandler addServlet(final ServletInfo servletInfo) { ManagedServlet managedServlet = new ManagedServlet(servletInfo, deployment.getServletContext()); ServletHandler servletHandler = new ServletHandler(managedServlet); managedServletMap.put(servletInfo.getName(), servletHandler); deployment.addLifecycleObjects(managedServlet); this.servletPaths.invalidate(); return servletHandler; } public ManagedServlet getManagedServlet(final String name) { ServletHandler servletHandler = managedServletMap.get(name); if(servletHandler == null) { return null; } return servletHandler.getManagedServlet(); } public ServletHandler getServletHandler(final String name) { return managedServletMap.get(name); } public Map getServletHandlers() { return new HashMap<>(managedServletMap); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/MetricsChainHandler.java000066400000000000000000000047471420065311100322320ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.MetricsHandler; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.api.MetricsCollector; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.handlers.ServletHandler; import io.undertow.servlet.handlers.ServletRequestContext; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * @author Tomaz Cerar (c) 2014 Red Hat Inc. */ class MetricsChainHandler implements HttpHandler { private final HttpHandler next; private final Map servletHandlers; MetricsChainHandler(HttpHandler next, MetricsCollector collector, Deployment deployment) { this.next = next; final Map servletHandlers = new HashMap<>(); for(Map.Entry entry : deployment.getServlets().getServletHandlers().entrySet()) { MetricsHandler handler = new MetricsHandler(next); servletHandlers.put(entry.getKey(), handler); collector.registerMetric(entry.getKey(), handler); } this.servletHandlers = Collections.unmodifiableMap(servletHandlers); } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); ServletInfo servletInfo = context.getCurrentServlet().getManagedServlet().getServletInfo(); MetricsHandler handler = servletHandlers.get(servletInfo.getName()); if(handler != null) { handler.handleRequest(exchange); } else { next.handleRequest(exchange); } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/SecurityActions.java000066400000000000000000000140431420065311100315010ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import java.security.AccessController; import java.security.PrivilegedAction; import javax.servlet.ServletContext; import io.undertow.server.HttpHandler; import io.undertow.server.session.Session; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.handlers.ServletInitialHandler; import io.undertow.servlet.handlers.ServletPathMatches; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.servlet.spec.HttpSessionImpl; import io.undertow.servlet.spec.ServletContextImpl; final class SecurityActions { private SecurityActions() { // forbidden inheritance } /** * Gets context classloader. * * @return the current context classloader */ static ClassLoader getContextClassLoader() { if (System.getSecurityManager() == null) { return Thread.currentThread().getContextClassLoader(); } else { return AccessController.doPrivileged(new PrivilegedAction() { public ClassLoader run() { return Thread.currentThread().getContextClassLoader(); } }); } } /** * Sets context classloader. * * @param classLoader * the classloader */ static void setContextClassLoader(final ClassLoader classLoader) { if (System.getSecurityManager() == null) { Thread.currentThread().setContextClassLoader(classLoader); } else { AccessController.doPrivileged(new PrivilegedAction() { public Object run() { Thread.currentThread().setContextClassLoader(classLoader); return null; } }); } } static String getSystemProperty(final String prop) { if (System.getSecurityManager() == null) { return System.getProperty(prop); } else { return (String) AccessController.doPrivileged(new PrivilegedAction() { public Object run() { return System.getProperty(prop); } }); } } static HttpSessionImpl forSession(final Session session, final ServletContext servletContext, final boolean newSession) { if (System.getSecurityManager() == null) { return HttpSessionImpl.forSession(session, servletContext, newSession); } else { return AccessController.doPrivileged(new PrivilegedAction() { @Override public HttpSessionImpl run() { return HttpSessionImpl.forSession(session, servletContext, newSession); } }); } } static ServletRequestContext currentServletRequestContext() { if (System.getSecurityManager() == null) { return ServletRequestContext.current(); } else { return AccessController.doPrivileged(new PrivilegedAction() { @Override public ServletRequestContext run() { return ServletRequestContext.current(); } }); } } static void setCurrentRequestContext(final ServletRequestContext servletRequestContext) { if (System.getSecurityManager() == null) { ServletRequestContext.setCurrentRequestContext(servletRequestContext); } else { AccessController.doPrivileged(new PrivilegedAction() { @Override public Object run() { ServletRequestContext.setCurrentRequestContext(servletRequestContext); return null; } }); } } static void clearCurrentServletAttachments() { if (System.getSecurityManager() == null) { ServletRequestContext.clearCurrentServletAttachments(); } else { AccessController.doPrivileged(new PrivilegedAction() { @Override public Object run() { ServletRequestContext.clearCurrentServletAttachments(); return null; } }); } } static ServletRequestContext requireCurrentServletRequestContext() { if (System.getSecurityManager() == null) { return ServletRequestContext.requireCurrent(); } else { return AccessController.doPrivileged(new PrivilegedAction() { @Override public ServletRequestContext run() { return ServletRequestContext.requireCurrent(); } }); } } static ServletInitialHandler createServletInitialHandler(final ServletPathMatches paths, final HttpHandler next, final Deployment deployment, final ServletContextImpl servletContext) { if (System.getSecurityManager() == null) { return new ServletInitialHandler(paths, next, deployment, servletContext); } else { return AccessController.doPrivileged(new PrivilegedAction() { @Override public ServletInitialHandler run() { return new ServletInitialHandler(paths, next, deployment, servletContext); } }); } } } ServletBlockingHttpExchange.java000066400000000000000000000074031420065311100336740ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import io.undertow.io.BlockingReceiverImpl; import io.undertow.io.BlockingSenderImpl; import io.undertow.io.Receiver; import io.undertow.io.Sender; import io.undertow.server.BlockingHttpExchange; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.servlet.spec.HttpServletRequestImpl; import io.undertow.servlet.spec.HttpServletResponseImpl; /** * @author Stuart Douglas */ public class ServletBlockingHttpExchange implements BlockingHttpExchange { private final HttpServerExchange exchange; public ServletBlockingHttpExchange(final HttpServerExchange exchange) { this.exchange = exchange; } @Override public InputStream getInputStream() { ServletRequest request = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getServletRequest(); try { return request.getInputStream(); } catch (IOException e) { throw new RuntimeException(e); } } @Override public OutputStream getOutputStream() { ServletResponse response = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getServletResponse(); try { return response.getOutputStream(); } catch (IOException e) { throw new RuntimeException(e); } } @Override public Sender getSender() { try { return new BlockingSenderImpl(exchange, getOutputStream()); } catch (IllegalStateException e) { ServletResponse response = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getServletResponse(); try { return new BlockingWriterSenderImpl(exchange, response.getWriter(), response.getCharacterEncoding()); } catch (IOException e1) { throw new RuntimeException(e1); } } } @Override public void close() throws IOException { ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (!exchange.isComplete()) { try { HttpServletRequestImpl request = servletRequestContext.getOriginalRequest(); request.closeAndDrainRequest(); } finally { HttpServletResponseImpl response = servletRequestContext.getOriginalResponse(); response.closeStreamAndWriter(); } } else { try { HttpServletRequestImpl request = servletRequestContext.getOriginalRequest(); request.freeResources(); } finally { HttpServletResponseImpl response = servletRequestContext.getOriginalResponse(); response.freeResources(); } } } @Override public Receiver getReceiver() { return new BlockingReceiverImpl(exchange, getInputStream()); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/ServletContainerImpl.java000066400000000000000000000065271420065311100324720ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import io.undertow.servlet.UndertowServletMessages; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; /** * The manager for all servlet deployments. * * @author Stuart Douglas */ public class ServletContainerImpl implements ServletContainer { private final Map deployments = Collections.synchronizedMap(new HashMap()); private final Map deploymentsByPath = Collections.synchronizedMap(new HashMap()); @Override public Collection listDeployments() { return new HashSet<>(deployments.keySet()); } @Override public DeploymentManager addDeployment(final DeploymentInfo deployment) { final DeploymentInfo dep = deployment.clone(); DeploymentManager deploymentManager = new DeploymentManagerImpl(dep, this); deployments.put(dep.getDeploymentName(), deploymentManager); deploymentsByPath.put(dep.getContextPath(), deploymentManager); return deploymentManager; } @Override public DeploymentManager getDeployment(final String deploymentName) { return deployments.get(deploymentName); } @Override public void removeDeployment(final DeploymentInfo deploymentInfo) { final DeploymentManager deploymentManager = deployments.get(deploymentInfo.getDeploymentName()); if (deploymentManager.getState() != DeploymentManager.State.UNDEPLOYED) { throw UndertowServletMessages.MESSAGES.canOnlyRemoveDeploymentsWhenUndeployed(deploymentManager.getState()); } deployments.remove(deploymentInfo.getDeploymentName()); deploymentsByPath.remove(deploymentInfo.getContextPath()); } @Override public DeploymentManager getDeploymentByPath(final String path) { DeploymentManager exact = deploymentsByPath.get(path.isEmpty() ? "/" : path); if (exact != null) { return exact; } int length = path.length(); int pos = length; while (pos > 1) { --pos; if (path.charAt(pos) == '/') { String part = path.substring(0, pos); DeploymentManager deployment = deploymentsByPath.get(part); if (deployment != null) { return deployment; } } } return deploymentsByPath.get("/"); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/ServletExtensionHolder.java000066400000000000000000000024421420065311100330300ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import io.undertow.servlet.ServletExtension; /** * Holder for global ServletExtension services. * This is particularly useful in an OSGi environment where classloader constraints * lead to the ServiceLoader not able to see ServletExtension implementations. */ public class ServletExtensionHolder { private static List extensions = new CopyOnWriteArrayList<>(); public static List getServletExtensions() { return extensions; } } ServletRequestContextThreadSetupAction.java000066400000000000000000000040751420065311100361470ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.api.ThreadSetupHandler; import io.undertow.servlet.handlers.ServletRequestContext; /** * @author Stuart Douglas */ class ServletRequestContextThreadSetupAction implements ThreadSetupHandler { static final ServletRequestContextThreadSetupAction INSTANCE = new ServletRequestContextThreadSetupAction(); private ServletRequestContextThreadSetupAction() { } @Override public Action create(final Action action) { return new Action() { @Override public T call(HttpServerExchange exchange, C context) throws Exception { if (exchange == null) { return action.call(null, context); } else { ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); final ServletRequestContext old = ServletRequestContext.current(); SecurityActions.setCurrentRequestContext(servletRequestContext); try { return action.call(exchange, context); } finally { ServletRequestContext.setCurrentRequestContext(old); } } } }; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/ServletUpgradeListener.java000066400000000000000000000112471420065311100330160ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; import javax.servlet.http.HttpUpgradeHandler; import org.xnio.ChannelListener; import org.xnio.StreamConnection; import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpUpgradeListener; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.api.InstanceHandle; import io.undertow.servlet.api.ThreadSetupHandler; import io.undertow.servlet.spec.WebConnectionImpl; /** * Lister that handles a servlet exchange upgrade event. * * @author Stuart Douglas */ public class ServletUpgradeListener implements HttpUpgradeListener { private final HttpServerExchange exchange; private final ThreadSetupHandler.Action initAction; private final ThreadSetupHandler.Action destroyAction; public ServletUpgradeListener(final InstanceHandle instance, Deployment deployment, HttpServerExchange exchange) { this.exchange = exchange; this.initAction = deployment.createThreadSetupAction(new ThreadSetupHandler.Action() { @Override public Void call(HttpServerExchange exchange, StreamConnection context) { DelayedExecutor executor = new DelayedExecutor(exchange.getIoThread()); try { //run the upgrade in the worker thread instance.getInstance().init(new WebConnectionImpl(context, ServletUpgradeListener.this.exchange.getConnection().getByteBufferPool(), executor)); } finally { executor.openGate(); } return null; } }); this.destroyAction = new ThreadSetupHandler.Action() { @Override public Void call(HttpServerExchange exchange, Object context) throws Exception { try { instance.getInstance().destroy(); } finally { instance.release(); } return null; } }; } @Override public void handleUpgrade(final StreamConnection channel, final HttpServerExchange exchange) { channel.getCloseSetter().set(new ChannelListener() { @Override public void handleEvent(StreamConnection channel) { try { destroyAction.call(null, null); } catch (Exception e) { throw new RuntimeException(e); } } }); this.exchange.getConnection().getWorker().execute(new Runnable() { @Override public void run() { try { initAction.call(exchange, channel); } catch (Exception e) { throw new RuntimeException(e); } } }); } /** * Executor that delays submitting tasks to the delegate until a condition is satisfied. */ private static final class DelayedExecutor implements Executor { private final Executor delegate; private volatile boolean queue = true; private final List tasks = new ArrayList<>(); private DelayedExecutor(Executor delegate) { this.delegate = delegate; } @Override public void execute(Runnable command) { if (!queue) { delegate.execute(command); } else { synchronized (this) { if (!queue) { delegate.execute(command); } else { tasks.add(command); } } } } synchronized void openGate() { queue = false; for (Runnable task : tasks) { delegate.execute(task); } } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/core/SessionListenerBridge.java000066400000000000000000000143531420065311100326230ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.core; import java.security.AccessController; import java.util.HashSet; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; import io.undertow.server.HttpServerExchange; import io.undertow.server.session.Session; import io.undertow.server.session.SessionListener; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.api.ThreadSetupHandler; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.servlet.spec.HttpSessionImpl; /** * Class that bridges between Undertow native session listeners and servlet ones. * * @author Stuart Douglas */ public class SessionListenerBridge implements SessionListener { public static final String IO_UNDERTOW = "io.undertow"; private final ApplicationListeners applicationListeners; private final ServletContext servletContext; private final ThreadSetupHandler.Action destroyedAction; public SessionListenerBridge(final Deployment deployment, final ApplicationListeners applicationListeners, final ServletContext servletContext) { this.applicationListeners = applicationListeners; this.servletContext = servletContext; this.destroyedAction = deployment.createThreadSetupAction(new ThreadSetupHandler.Action() { @Override public Void call(HttpServerExchange exchange, Session session) throws ServletException { doDestroy(session); return null; } }); } @Override public void sessionCreated(final Session session, final HttpServerExchange exchange) { final HttpSessionImpl httpSession = SecurityActions.forSession(session, servletContext, true); applicationListeners.sessionCreated(httpSession); } @Override public void sessionDestroyed(final Session session, final HttpServerExchange exchange, final SessionDestroyedReason reason) { if (reason == SessionDestroyedReason.TIMEOUT) { try { //we need to perform thread setup actions destroyedAction.call(exchange, session); } catch (Exception e) { throw new RuntimeException(e); } } else { doDestroy(session); } ServletRequestContext current = SecurityActions.currentServletRequestContext(); Session underlying = null; if (current != null && current.getSession() != null) { if (System.getSecurityManager() == null) { underlying = current.getSession().getSession(); } else { underlying = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(current.getSession())); } } if (current != null && underlying == session) { current.setSession(null); } } private void doDestroy(Session session) { final HttpSessionImpl httpSession = SecurityActions.forSession(session, servletContext, false); applicationListeners.sessionDestroyed(httpSession); //we make a defensive copy here, as there is no guarantee that the underlying session map //is a concurrent map, and as a result a concurrent modification exception may be thrown HashSet names = new HashSet<>(session.getAttributeNames()); for (String attribute : names) { session.removeAttribute(attribute); } } @Override public void attributeAdded(final Session session, final String name, final Object value) { if (name.startsWith(IO_UNDERTOW)) { return; } final HttpSessionImpl httpSession = SecurityActions.forSession(session, servletContext, false); applicationListeners.httpSessionAttributeAdded(httpSession, name, value); if (value instanceof HttpSessionBindingListener) { ((HttpSessionBindingListener) value).valueBound(new HttpSessionBindingEvent(httpSession, name, value)); } } @Override public void attributeUpdated(final Session session, final String name, final Object value, final Object old) { if (name.startsWith(IO_UNDERTOW)) { return; } final HttpSessionImpl httpSession = SecurityActions.forSession(session, servletContext, false); if (old != value) { if (old instanceof HttpSessionBindingListener) { ((HttpSessionBindingListener) old).valueUnbound(new HttpSessionBindingEvent(httpSession, name, old)); } applicationListeners.httpSessionAttributeReplaced(httpSession, name, old); } if (value instanceof HttpSessionBindingListener) { ((HttpSessionBindingListener) value).valueBound(new HttpSessionBindingEvent(httpSession, name, value)); } } @Override public void attributeRemoved(final Session session, final String name, final Object old) { if (name.startsWith(IO_UNDERTOW)) { return; } final HttpSessionImpl httpSession = SecurityActions.forSession(session, servletContext, false); if (old != null) { applicationListeners.httpSessionAttributeRemoved(httpSession, name, old); if (old instanceof HttpSessionBindingListener) { ((HttpSessionBindingListener) old).valueUnbound(new HttpSessionBindingEvent(httpSession, name, old)); } } } @Override public void sessionIdChanged(Session session, String oldSessionId) { } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/000077500000000000000000000000001420065311100263545ustar00rootroot00000000000000CrawlerSessionManagerHandler.java000066400000000000000000000164471420065311100347100ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers; import java.io.Serializable; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; import io.undertow.UndertowLogger; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.api.CrawlerSessionManagerConfig; import io.undertow.util.HeaderValues; import io.undertow.util.Headers; /** * Web crawlers can trigger the creation of many thousands of sessions as they * crawl a site which may result in significant memory consumption. This Valve * ensures that crawlers are associated with a single session - just like normal * users - regardless of whether or not they provide a session token with their * requests. * */ public class CrawlerSessionManagerHandler implements HttpHandler { private static final String SESSION_ATTRIBUTE_NAME = "listener_" + CrawlerSessionManagerHandler.class.getName(); private final Map clientIpSessionId = new ConcurrentHashMap<>(); private final Map sessionIdClientIp = new ConcurrentHashMap<>(); private final CrawlerSessionManagerConfig config; private final Pattern uaPattern; private final HttpHandler next; public CrawlerSessionManagerHandler(CrawlerSessionManagerConfig config, HttpHandler next) { this.config = config; this.next = next; this.uaPattern = Pattern.compile(config.getCrawlerUserAgents()); } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { boolean isBot = false; String sessionId = null; String clientIp = null; ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); // If the incoming request has a valid session ID, no action is required if ( src.getOriginalRequest().getSession(false) == null) { // Is this a crawler - check the UA headers HeaderValues userAgentHeaders = exchange.getRequestHeaders().get(Headers.USER_AGENT); if (userAgentHeaders != null) { Iterator uaHeaders = userAgentHeaders.iterator(); String uaHeader = null; if (uaHeaders.hasNext()) { uaHeader = uaHeaders.next(); } // If more than one UA header - assume not a bot if (uaHeader != null && !uaHeaders.hasNext()) { if (uaPattern.matcher(uaHeader).matches()) { isBot = true; if (UndertowLogger.REQUEST_LOGGER.isDebugEnabled()) { UndertowLogger.REQUEST_LOGGER.debug(exchange + ": Bot found. UserAgent=" + uaHeader); } } } // If this is a bot, is the session ID known? if (isBot) { clientIp = src.getServletRequest().getRemoteAddr(); sessionId = clientIpSessionId.get(clientIp); if (sessionId != null) { src.setOverridenSessionId(sessionId); if (UndertowLogger.REQUEST_LOGGER.isDebugEnabled()) { UndertowLogger.REQUEST_LOGGER.debug(exchange + ": SessionID=" + sessionId); } } } } } if (isBot) { final String finalSessionId = sessionId; final String finalClientId = clientIp; exchange.addExchangeCompleteListener(new ExchangeCompletionListener() { @Override public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { try { ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (finalSessionId == null) { // Has bot just created a session, if so make a note of it HttpSession s = src.getOriginalRequest().getSession(false); if (s != null) { clientIpSessionId.put(finalClientId, s.getId()); sessionIdClientIp.put(s.getId(), finalClientId); // #valueUnbound() will be called on session expiration s.setAttribute(SESSION_ATTRIBUTE_NAME, new CrawlerBindingListener(clientIpSessionId, sessionIdClientIp)); s.setMaxInactiveInterval(config.getSessionInactiveInterval()); if (UndertowLogger.REQUEST_LOGGER.isDebugEnabled()) { UndertowLogger.REQUEST_LOGGER.debug(exchange + ": New bot session. SessionID=" + s.getId()); } } } else { if (UndertowLogger.REQUEST_LOGGER.isDebugEnabled()) { UndertowLogger.REQUEST_LOGGER.debug(exchange + ": Bot session accessed. SessionID=" + finalSessionId); } } } finally { nextListener.proceed(); } } }); } next.handleRequest(exchange); } } class CrawlerBindingListener implements HttpSessionBindingListener, Serializable { private static final long serialVersionUID = -8841692120840734349L; private transient Map clientIpSessionId; private transient Map sessionIdClientIp; CrawlerBindingListener(Map clientIpSessionId, Map sessionIdClientIp) { this.clientIpSessionId = clientIpSessionId; this.sessionIdClientIp = sessionIdClientIp; } @Override public void valueBound(HttpSessionBindingEvent event) { // NOOP } @Override public void valueUnbound(HttpSessionBindingEvent event) { if (sessionIdClientIp != null) { String clientIp = sessionIdClientIp.remove(event.getSession().getId()); if (clientIp != null) { clientIpSessionId.remove(clientIp); } } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/DefaultServlet.java000066400000000000000000000452721420065311100321620ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers; import io.undertow.io.IoCallback; import io.undertow.io.Sender; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.resource.DefaultResourceSupplier; import io.undertow.server.handlers.resource.DirectoryUtils; import io.undertow.server.handlers.resource.PreCompressedResourceSupplier; import io.undertow.server.handlers.resource.RangeAwareResource; import io.undertow.server.handlers.resource.Resource; import io.undertow.server.handlers.resource.ResourceSupplier; import io.undertow.servlet.UndertowServletLogger; import io.undertow.servlet.api.DefaultServletConfig; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.spec.ServletContextImpl; import io.undertow.util.ByteRange; import io.undertow.util.CanonicalPathUtils; import io.undertow.util.DateUtils; import io.undertow.util.ETag; import io.undertow.util.ETagUtils; import io.undertow.util.Headers; import io.undertow.util.Methods; import io.undertow.util.StatusCodes; import javax.servlet.DispatcherType; import javax.servlet.RequestDispatcher; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Default servlet responsible for serving up resources. This is both a handler and a servlet. If no filters * match the current path then the resources will be served up asynchronously using the * {@link io.undertow.server.HttpHandler#handleRequest(io.undertow.server.HttpServerExchange)} method, * otherwise the request is handled as a normal servlet request. *

    * By default we only allow a restricted set of extensions. *

    * todo: this thing needs a lot more work. In particular: * - caching for blocking requests * - correct mime type * - range/last-modified and other headers to be handled properly * - head requests * - and probably heaps of other things * * @author Stuart Douglas */ public class DefaultServlet extends HttpServlet { public static final String DIRECTORY_LISTING = "directory-listing"; public static final String DEFAULT_ALLOWED = "default-allowed"; public static final String ALLOWED_EXTENSIONS = "allowed-extensions"; public static final String DISALLOWED_EXTENSIONS = "disallowed-extensions"; public static final String RESOLVE_AGAINST_CONTEXT_ROOT = "resolve-against-context-root"; public static final String ALLOW_POST = "allow-post"; private static final Set DEFAULT_ALLOWED_EXTENSIONS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("js", "css", "png", "jpg", "gif", "html", "htm", "txt", "pdf", "jpeg", "xml"))); private Deployment deployment; private ResourceSupplier resourceSupplier; private boolean directoryListingEnabled = false; private boolean defaultAllowed = true; private Set allowed = DEFAULT_ALLOWED_EXTENSIONS; private Set disallowed = Collections.emptySet(); private boolean resolveAgainstContextRoot; private boolean allowPost = false; @Override public void init(ServletConfig config) throws ServletException { super.init(config); ServletContextImpl sc = (ServletContextImpl) config.getServletContext(); this.deployment = sc.getDeployment(); DefaultServletConfig defaultServletConfig = deployment.getDeploymentInfo().getDefaultServletConfig(); if (defaultServletConfig != null) { defaultAllowed = defaultServletConfig.isDefaultAllowed(); allowed = new HashSet<>(); if (defaultServletConfig.getAllowed() != null) { allowed.addAll(defaultServletConfig.getAllowed()); } disallowed = new HashSet<>(); if (defaultServletConfig.getDisallowed() != null) { disallowed.addAll(defaultServletConfig.getDisallowed()); } } if (config.getInitParameter(DEFAULT_ALLOWED) != null) { defaultAllowed = Boolean.parseBoolean(config.getInitParameter(DEFAULT_ALLOWED)); } if (config.getInitParameter(ALLOWED_EXTENSIONS) != null) { String extensions = config.getInitParameter(ALLOWED_EXTENSIONS); allowed = new HashSet<>(Arrays.asList(extensions.split(","))); } if (config.getInitParameter(DISALLOWED_EXTENSIONS) != null) { String extensions = config.getInitParameter(DISALLOWED_EXTENSIONS); disallowed = new HashSet<>(Arrays.asList(extensions.split(","))); } if (config.getInitParameter(RESOLVE_AGAINST_CONTEXT_ROOT) != null) { resolveAgainstContextRoot = Boolean.parseBoolean(config.getInitParameter(RESOLVE_AGAINST_CONTEXT_ROOT)); } if (config.getInitParameter(ALLOW_POST) != null) { allowPost = Boolean.parseBoolean(config.getInitParameter(ALLOW_POST)); } if(deployment.getDeploymentInfo().getPreCompressedResources().isEmpty()) { this.resourceSupplier = new DefaultResourceSupplier(deployment.getDeploymentInfo().getResourceManager()); } else { PreCompressedResourceSupplier preCompressedResourceSupplier = new PreCompressedResourceSupplier(deployment.getDeploymentInfo().getResourceManager()); for(Map.Entry entry : deployment.getDeploymentInfo().getPreCompressedResources().entrySet()) { preCompressedResourceSupplier.addEncoding(entry.getKey(), entry.getValue()); } this.resourceSupplier = preCompressedResourceSupplier; } String listings = config.getInitParameter(DIRECTORY_LISTING); if (Boolean.valueOf(listings)) { this.directoryListingEnabled = true; } } @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { String path = getPath(req); if (!isAllowed(path, req.getDispatcherType())) { resp.sendError(StatusCodes.NOT_FOUND); return; } if(File.separatorChar != '/') { //if the separator char is not / we want to replace it with a / and canonicalise path = CanonicalPathUtils.canonicalize(path.replace(File.separatorChar, '/')); } HttpServerExchange exchange = SecurityActions.requireCurrentServletRequestContext().getOriginalRequest().getExchange(); final Resource resource; //we want to disallow windows characters in the path if(File.separatorChar == '/' || !path.contains(File.separator)) { resource = resourceSupplier.getResource(exchange, path); } else { resource = null; } if (resource == null) { if (req.getDispatcherType() == DispatcherType.INCLUDE) { //servlet 9.3 UndertowServletLogger.REQUEST_LOGGER.requestedResourceDoesNotExistForIncludeMethod(path); throw new FileNotFoundException(path); } else { resp.sendError(StatusCodes.NOT_FOUND); } return; } else if (resource.isDirectory()) { if ("css".equals(req.getQueryString())) { resp.setContentType("text/css"); resp.getWriter().write(DirectoryUtils.Blobs.FILE_CSS); return; } else if ("js".equals(req.getQueryString())) { resp.setContentType("application/javascript"); resp.getWriter().write(DirectoryUtils.Blobs.FILE_JS); return; } if (directoryListingEnabled) { resp.setContentType("text/html"); StringBuilder output = DirectoryUtils.renderDirectoryListing(req.getRequestURI(), resource); resp.getWriter().write(output.toString()); } else { resp.sendError(StatusCodes.FORBIDDEN); } } else { if(path.endsWith("/")) { //UNDERTOW-432 resp.sendError(StatusCodes.NOT_FOUND); return; } serveFileBlocking(req, resp, resource, exchange); } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if(allowPost) { doGet(req, resp); } else { /* * Where a servlet has received a POST request we still require the capability to include static content. */ switch (req.getDispatcherType()) { case INCLUDE: case FORWARD: case ERROR: doGet(req, resp); break; default: super.doPost(req, resp); } } } @Override protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { switch (req.getDispatcherType()) { case INCLUDE: case FORWARD: case ERROR: doGet(req, resp); break; default: super.doPut(req, resp); } } @Override protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { switch (req.getDispatcherType()) { case INCLUDE: case FORWARD: case ERROR: doGet(req, resp); break; default: super.doDelete(req, resp); } } @Override protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { switch (req.getDispatcherType()) { case INCLUDE: case FORWARD: case ERROR: doGet(req, resp); break; default: super.doOptions(req, resp); } } @Override protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { switch (req.getDispatcherType()) { case INCLUDE: case FORWARD: case ERROR: doGet(req, resp); break; default: super.doTrace(req, resp); } } private void serveFileBlocking(final HttpServletRequest req, final HttpServletResponse resp, final Resource resource, HttpServerExchange exchange) throws IOException { final ETag etag = resource.getETag(); final Date lastModified = resource.getLastModified(); if(req.getDispatcherType() != DispatcherType.INCLUDE) { if (!ETagUtils.handleIfMatch(req.getHeader(Headers.IF_MATCH_STRING), etag, false) || !DateUtils.handleIfUnmodifiedSince(req.getHeader(Headers.IF_UNMODIFIED_SINCE_STRING), lastModified)) { resp.setStatus(StatusCodes.PRECONDITION_FAILED); return; } if (!ETagUtils.handleIfNoneMatch(req.getHeader(Headers.IF_NONE_MATCH_STRING), etag, true) || !DateUtils.handleIfModifiedSince(req.getHeader(Headers.IF_MODIFIED_SINCE_STRING), lastModified)) { if(req.getMethod().equals(Methods.GET_STRING) || req.getMethod().equals(Methods.HEAD_STRING)) { resp.setStatus(StatusCodes.NOT_MODIFIED); } else { resp.setStatus(StatusCodes.PRECONDITION_FAILED); } return; } } //we are going to proceed. Set the appropriate headers if(resp.getContentType() == null) { if(!resource.isDirectory()) { final String contentType = deployment.getServletContext().getMimeType(resource.getName()); if (contentType != null) { resp.setContentType(contentType); } else { resp.setContentType("application/octet-stream"); } } } if (lastModified != null) { resp.setHeader(Headers.LAST_MODIFIED_STRING, resource.getLastModifiedString()); } if (etag != null) { resp.setHeader(Headers.ETAG_STRING, etag.toString()); } ByteRange.RangeResponseResult rangeResponse = null; long start = -1, end = -1; try { //only set the content length if we are using a stream //if we are using a writer who knows what the length will end up being //todo: if someone installs a filter this can cause problems //not sure how best to deal with this //we also can't deal with range requests if a writer is in use Long contentLength = resource.getContentLength(); if (contentLength != null) { resp.getOutputStream(); if(contentLength > Integer.MAX_VALUE) { resp.setContentLengthLong(contentLength); } else { resp.setContentLength(contentLength.intValue()); } if(resource instanceof RangeAwareResource && ((RangeAwareResource)resource).isRangeSupported() && resource.getContentLength() != null) { resp.setHeader(Headers.ACCEPT_RANGES_STRING, "bytes"); //TODO: figure out what to do with the content encoded resource manager final ByteRange range = ByteRange.parse(req.getHeader(Headers.RANGE_STRING)); if(range != null) { rangeResponse = range.getResponseResult(resource.getContentLength(), req.getHeader(Headers.IF_RANGE_STRING), resource.getLastModified(), resource.getETag() == null ? null : resource.getETag().getTag()); if(rangeResponse != null){ start = rangeResponse.getStart(); end = rangeResponse.getEnd(); resp.setStatus(rangeResponse.getStatusCode()); resp.setHeader(Headers.CONTENT_RANGE_STRING, rangeResponse.getContentRange()); long length = rangeResponse.getContentLength(); if(length > Integer.MAX_VALUE) { resp.setContentLengthLong(length); } else { resp.setContentLength((int) length); } if(rangeResponse.getStatusCode() == StatusCodes.REQUEST_RANGE_NOT_SATISFIABLE) { return; } } } } } } catch (IllegalStateException e) { } final boolean include = req.getDispatcherType() == DispatcherType.INCLUDE; if (!req.getMethod().equals(Methods.HEAD_STRING)) { if(rangeResponse == null) { resource.serve(exchange.getResponseSender(), exchange, completionCallback(include)); } else { ((RangeAwareResource)resource).serveRange(exchange.getResponseSender(), exchange, start, end, completionCallback(include)); } } } private IoCallback completionCallback(final boolean include) { return new IoCallback() { @Override public void onComplete(final HttpServerExchange exchange, final Sender sender) { if (!include) { sender.close(); } } @Override public void onException(final HttpServerExchange exchange, final Sender sender, final IOException exception) { //not much we can do here, the connection is broken sender.close(); } }; } private String getPath(final HttpServletRequest request) { String servletPath; String pathInfo; if (request.getDispatcherType() == DispatcherType.INCLUDE && request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null) { pathInfo = (String) request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO); servletPath = (String) request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH); } else { pathInfo = request.getPathInfo(); servletPath = request.getServletPath(); } String result = pathInfo; if (result == null) { result = CanonicalPathUtils.canonicalize(servletPath); } else if(resolveAgainstContextRoot) { result = servletPath + CanonicalPathUtils.canonicalize(pathInfo); } else { result = CanonicalPathUtils.canonicalize(result); } if ((result == null) || (result.isEmpty())) { result = "/"; } return result; } private boolean isAllowed(String path, DispatcherType dispatcherType) { if (!path.isEmpty()) { if(dispatcherType == DispatcherType.REQUEST) { //WFLY-3543 allow the dispatcher to access stuff in web-inf and meta inf if (Paths.isForbidden(path)) { return false; } } } if(defaultAllowed && disallowed.isEmpty()) { return true; } int pos = path.lastIndexOf('/'); final String lastSegment; if (pos == -1) { lastSegment = path; } else { lastSegment = path.substring(pos + 1); } if (lastSegment.isEmpty()) { return true; } int ext = lastSegment.lastIndexOf('.'); if (ext == -1) { //no extension return true; } final String extension = lastSegment.substring(ext + 1, lastSegment.length()); if (defaultAllowed) { return !disallowed.contains(extension); } else { return allowed.contains(extension); } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/FilterHandler.java000066400000000000000000000136171420065311100317520ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers; import java.io.IOException; import java.util.EnumMap; import java.util.List; import java.util.Map; import javax.servlet.DispatcherType; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletRequestWrapper; import javax.servlet.ServletResponse; import javax.servlet.ServletResponseWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.UndertowServletMessages; import io.undertow.servlet.core.ManagedFilter; /** * @author Stuart Douglas */ public class FilterHandler implements HttpHandler { private final Map> filters; private final Map asyncSupported; private final boolean allowNonStandardWrappers; private final HttpHandler next; public FilterHandler(final Map> filters, final boolean allowNonStandardWrappers, final HttpHandler next) { this.allowNonStandardWrappers = allowNonStandardWrappers; this.next = next; this.filters = new EnumMap<>(filters); Map asyncSupported = new EnumMap<>(DispatcherType.class); for(Map.Entry> entry : filters.entrySet()) { boolean supported = true; for(ManagedFilter i : entry.getValue()) { if(!i.getFilterInfo().isAsyncSupported()) { supported = false; break; } } asyncSupported.put(entry.getKey(), supported); } this.asyncSupported = asyncSupported; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); ServletRequest request = servletRequestContext.getServletRequest(); ServletResponse response = servletRequestContext.getServletResponse(); DispatcherType dispatcher = servletRequestContext.getDispatcherType(); Boolean supported = asyncSupported.get(dispatcher); if(supported != null && ! supported) { servletRequestContext.setAsyncSupported(false); } final List filters = this.filters.get(dispatcher); if(filters == null) { next.handleRequest(exchange); } else { final FilterChainImpl filterChain = new FilterChainImpl(exchange, filters, next, allowNonStandardWrappers); filterChain.doFilter(request, response); } } private static class FilterChainImpl implements FilterChain { int location = 0; final HttpServerExchange exchange; final List filters; final HttpHandler next; final boolean allowNonStandardWrappers; private FilterChainImpl(final HttpServerExchange exchange, final List filters, final HttpHandler next, final boolean allowNonStandardWrappers) { this.exchange = exchange; this.filters = filters; this.next = next; this.allowNonStandardWrappers = allowNonStandardWrappers; } @Override public void doFilter(final ServletRequest request, final ServletResponse response) throws IOException, ServletException { final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); final ServletRequest oldReq = servletRequestContext.getServletRequest(); final ServletResponse oldResp = servletRequestContext.getServletResponse(); try { if(!allowNonStandardWrappers) { if(oldReq != request) { if(!(request instanceof ServletRequestWrapper)) { throw UndertowServletMessages.MESSAGES.requestWasNotOriginalOrWrapper(request); } } if(oldResp != response) { if(!(response instanceof ServletResponseWrapper)) { throw UndertowServletMessages.MESSAGES.responseWasNotOriginalOrWrapper(response); } } } servletRequestContext.setServletRequest(request); servletRequestContext.setServletResponse(response); int index = location++; if (index >= filters.size()) { next.handleRequest(exchange); } else { filters.get(index).doFilter(request, response, this); } } catch (IOException e) { throw e; } catch (ServletException e) { throw e; } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } finally { location--; servletRequestContext.setServletRequest(oldReq); servletRequestContext.setServletResponse(oldResp); } } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/MarkSecureHandler.java000066400000000000000000000047131420065311100325630ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.builder.HandlerBuilder; import java.util.Collections; import java.util.Map; import java.util.Set; /** * Handler that marks a request as secure, regardless of the underlying protocol. * * @author Stuart Douglas */ public class MarkSecureHandler implements HttpHandler { public static final HandlerWrapper WRAPPER = new Wrapper(); private final HttpHandler next; public MarkSecureHandler(HttpHandler next) { this.next = next; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { exchange.putAttachment(HttpServerExchange.SECURE_REQUEST, Boolean.TRUE); next.handleRequest(exchange); } public static class Wrapper implements HandlerWrapper { @Override public HttpHandler wrap(HttpHandler handler) { return new MarkSecureHandler(handler); } } @Override public String toString() { return "mark-secure()"; } public static class Builder implements HandlerBuilder { @Override public String name() { return "mark-secure"; } @Override public Map> parameters() { return Collections.emptyMap(); } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return null; } @Override public HandlerWrapper build(Map config) { return WRAPPER; } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/Paths.java000066400000000000000000000026611420065311100303030ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers; import java.util.StringTokenizer; /** * @author Richard Opalka */ final class Paths { private static final String META_INF = "META-INF"; private static final String WEB_INF = "WEB-INF"; private Paths() { // forbidden instantiation } static boolean isForbidden(final String path) { final StringTokenizer st = new StringTokenizer(path, "/\\", false); String subPath; while (st.hasMoreTokens()) { subPath = st.nextToken(); if (META_INF.equalsIgnoreCase(subPath) || WEB_INF.equalsIgnoreCase(subPath)) { return true; } } return false; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/RedirectDirHandler.java000066400000000000000000000056441420065311100327260ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; import io.undertow.util.Methods; import io.undertow.util.RedirectBuilder; import io.undertow.util.StatusCodes; /** * Handler that redirects the directory requests without trailing slash to the one append trailing slash. * * @author Lin Gao */ public class RedirectDirHandler implements HttpHandler { private static final String HTTP2_UPGRADE_PREFIX = "h2"; private final HttpHandler next; private final ServletPathMatches paths; public RedirectDirHandler(HttpHandler next, ServletPathMatches paths) { this.next = next; this.paths = paths; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { final String path = exchange.getRelativePath(); final ServletPathMatch info = paths.getServletHandlerByPath(path); // https://issues.jboss.org/browse/WFLY-3439 // if the request is an upgrade request then we don't want to redirect // as there is a good chance the web socket client won't understand the redirect // we make an exception for HTTP2 upgrade requests, as this would have already be handled at // the connector level if it was going to be handled. String upgradeString = exchange.getRequestHeaders().getFirst(Headers.UPGRADE); boolean isUpgradeRequest = upgradeString != null && !upgradeString.startsWith(HTTP2_UPGRADE_PREFIX); if (info.getType() == ServletPathMatch.Type.REDIRECT && !isUpgradeRequest) { // UNDERTOW-89 // we redirect on GET requests to the root context to add an / to the end if (exchange.getRequestMethod().equals(Methods.GET) || exchange.getRequestMethod().equals(Methods.HEAD)) { exchange.setStatusCode(StatusCodes.FOUND); } else { exchange.setStatusCode(StatusCodes.TEMPORARY_REDIRECT); } exchange.getResponseHeaders().put(Headers.LOCATION, RedirectBuilder.redirect(exchange, exchange.getRelativePath() + "/", true)); return; } next.handleRequest(exchange); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/SecurityActions.java000066400000000000000000000040211420065311100323440ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers; import java.security.AccessController; import java.security.PrivilegedAction; import javax.servlet.ServletContext; import io.undertow.server.session.Session; import io.undertow.servlet.spec.HttpSessionImpl; class SecurityActions { static HttpSessionImpl forSession(final Session session, final ServletContext servletContext, final boolean newSession) { if (System.getSecurityManager() == null) { return HttpSessionImpl.forSession(session, servletContext, newSession); } else { return AccessController.doPrivileged(new PrivilegedAction() { @Override public HttpSessionImpl run() { return HttpSessionImpl.forSession(session, servletContext, newSession); } }); } } static ServletRequestContext requireCurrentServletRequestContext() { if (System.getSecurityManager() == null) { return ServletRequestContext.requireCurrent(); } else { return AccessController.doPrivileged(new PrivilegedAction() { @Override public ServletRequestContext run() { return ServletRequestContext.requireCurrent(); } }); } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/SendErrorPageHandler.java000066400000000000000000000035431420065311100332220ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import javax.servlet.http.HttpServletResponse; /** * A handler that sends the servlet's error page if the status code is greater than 399 * * @author Brad Wood */ public class SendErrorPageHandler implements HttpHandler { private final HttpHandler next; /** * Construct a new instance. * * @param next The next handler to call if there is no error response */ public SendErrorPageHandler(HttpHandler next) { this.next = next; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); // If the servlet is available and the status code has been set to an error, return the error page if( src != null && exchange.getStatusCode() > 399 && !exchange.isResponseStarted() ) { ((HttpServletResponse)src.getServletResponse()).sendError(exchange.getStatusCode()); } else { next.handleRequest(exchange); } } }undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/ServletChain.java000066400000000000000000000111521420065311100316060ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers; import java.util.List; import java.util.Map; import java.util.concurrent.Executor; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.core.ManagedFilter; import io.undertow.servlet.core.ManagedServlet; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import javax.servlet.http.MappingMatch; /** * @author Stuart Douglas */ public class ServletChain { private final HttpHandler handler; private final ManagedServlet managedServlet; private final String servletPath; private final Executor executor; private final boolean defaultServletMapping; private final MappingMatch mappingMatch; private final String pattern; private final Map> filters; public ServletChain(final HttpHandler handler, final ManagedServlet managedServlet, final String servletPath, boolean defaultServletMapping, MappingMatch mappingMatch, String pattern, Map> filters) { this(handler, managedServlet, servletPath, defaultServletMapping, mappingMatch, pattern, filters, true); } private ServletChain(final HttpHandler originalHandler, final ManagedServlet managedServlet, final String servletPath, boolean defaultServletMapping, MappingMatch mappingMatch, String pattern, Map> filters, boolean wrapHandler) { if (wrapHandler) { this.handler = new HttpHandler() { private volatile boolean initDone = false; @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if(!initDone) { synchronized (this) { if(!initDone) { ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); forceInit(src.getDispatcherType()); initDone = true; } } } originalHandler.handleRequest(exchange); } }; } else { this.handler = originalHandler; } this.managedServlet = managedServlet; this.servletPath = servletPath; this.defaultServletMapping = defaultServletMapping; this.mappingMatch = mappingMatch; this.pattern = pattern; this.executor = managedServlet.getServletInfo().getExecutor(); this.filters = filters; } public ServletChain(final ServletChain other, String pattern, MappingMatch mappingMatch) { this(other.getHandler(), other.getManagedServlet(), other.getServletPath(), other.isDefaultServletMapping(), mappingMatch, pattern, other.filters, false); } public HttpHandler getHandler() { return handler; } public ManagedServlet getManagedServlet() { return managedServlet; } /** * * @return The servlet path part */ public String getServletPath() { return servletPath; } public Executor getExecutor() { return executor; } public boolean isDefaultServletMapping() { return defaultServletMapping; } public MappingMatch getMappingMatch() { return mappingMatch; } public String getPattern() { return pattern; } //see UNDERTOW-1132 void forceInit(DispatcherType dispatcherType) throws ServletException { if(filters != null) { List list = filters.get(dispatcherType); if(list != null && !list.isEmpty()) { for(int i = 0; i < list.size(); ++i) { ManagedFilter filter = list.get(i); filter.forceInit(); } } } managedServlet.forceInit(); } } ServletDebugPageHandler.java000066400000000000000000000220571420065311100336340ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.spec.HttpServletRequestImpl; import javax.servlet.ServletOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.charset.StandardCharsets; /** * generates a servlet error page with a stack trace * * @author Stuart Douglas */ public class ServletDebugPageHandler { public static final String ERROR_CSS = ""; public static void handleRequest(HttpServerExchange exchange, final ServletRequestContext servletRequestContext, final Throwable exception) throws IOException { HttpServletRequestImpl req = servletRequestContext.getOriginalRequest(); StringBuilder sb = new StringBuilder(); //todo: make this good sb.append("ERROR"); sb.append(ERROR_CSS); sb.append("
    Error processing request
    "); writeLabel(sb, "Context Path", req.getContextPath()); writeLabel(sb, "Servlet Path", req.getServletPath()); writeLabel(sb, "Path Info", req.getPathInfo()); writeLabel(sb, "Query String", req.getQueryString()); writeLabel(sb, "Stack Trace", ""); sb.append("
    ");
            StringWriter stringWriter = new StringWriter();
            exception.printStackTrace(new PrintWriter(stringWriter));
            sb.append(escapeBodyText(stringWriter.toString()));
            sb.append("
    "); servletRequestContext.getOriginalResponse().setContentType("text/html"); servletRequestContext.getOriginalResponse().setCharacterEncoding("UTF-8"); try { ServletOutputStream out = servletRequestContext.getOriginalResponse().getOutputStream(); out.write(sb.toString().getBytes(StandardCharsets.UTF_8)); out.close(); } catch (IllegalStateException e) { PrintWriter writer = servletRequestContext.getOriginalResponse().getWriter(); writer.write(sb.toString()); writer.close(); } } private static void writeLabel(StringBuilder sb, String label, String value) { sb.append("
    "); sb.append(escapeBodyText(label)); sb.append(":
    "); sb.append(escapeBodyText(value)); sb.append("

    "); } public static String escapeBodyText(final String bodyText) { if(bodyText == null) { return "null"; } return bodyText.replace("&", "&").replace("<", "<").replace(">", ">"); } } ServletDispatchingHandler.java000066400000000000000000000025131420065311100342410ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; /** * Handler that dispatches to the resolved servlet. * * @author Stuart Douglas */ public class ServletDispatchingHandler implements HttpHandler { public static final ServletDispatchingHandler INSTANCE = new ServletDispatchingHandler(); @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { ServletChain info = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getCurrentServlet(); info.getHandler().handleRequest(exchange); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/ServletHandler.java000066400000000000000000000102031420065311100321350ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers; import java.io.IOException; import javax.servlet.Servlet; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.UnavailableException; import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpHandler; import io.undertow.servlet.UndertowServletLogger; import io.undertow.servlet.api.InstanceHandle; import io.undertow.servlet.core.ManagedServlet; import io.undertow.util.StatusCodes; /** * The handler that is responsible for invoking the servlet *

    * TODO: do we want to move lifecycle considerations out of this handler? * * @author Stuart Douglas */ public class ServletHandler implements HttpHandler { private final ManagedServlet managedServlet; public ServletHandler(final ManagedServlet managedServlet) { this.managedServlet = managedServlet; } @Override public void handleRequest(final HttpServerExchange exchange) throws IOException, ServletException { if (managedServlet.isPermanentlyUnavailable()) { UndertowServletLogger.REQUEST_LOGGER.debugf("Returning 404 for servlet %s due to permanent unavailability", managedServlet.getServletInfo().getName()); exchange.setStatusCode(StatusCodes.NOT_FOUND); return; } if (managedServlet.isTemporarilyUnavailable()) { UndertowServletLogger.REQUEST_LOGGER.debugf("Returning 503 for servlet %s due to temporary unavailability", managedServlet.getServletInfo().getName()); exchange.setStatusCode(StatusCodes.SERVICE_UNAVAILABLE); return; } final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if(!managedServlet.getServletInfo().isAsyncSupported()) { servletRequestContext.setAsyncSupported(false); } ServletRequest request = servletRequestContext.getServletRequest(); ServletResponse response = servletRequestContext.getServletResponse(); InstanceHandle servlet = null; try { servlet = managedServlet.getServlet(); servlet.getInstance().service(request, response); //according to the spec we have to call AsyncContext.complete() at this point //straight after the service method //not super sure about this, surely it would make more sense to do this when the request has returned to the container, however the spec is quite clear wording wise //todo: should we actually enable this? Apparently other containers do not do it //if(!request.isAsyncStarted()) { // AsyncContextImpl existingAsyncContext = servletRequestContext.getOriginalRequest().getAsyncContextInternal(); // if (existingAsyncContext != null) { // existingAsyncContext.complete(); // } //} } catch (UnavailableException e) { managedServlet.handleUnavailableException(e); if (e.isPermanent()) { exchange.setStatusCode(StatusCodes.NOT_FOUND); } else { exchange.setStatusCode(StatusCodes.SERVICE_UNAVAILABLE); } } finally { if(servlet != null) { servlet.release(); } } } public ManagedServlet getManagedServlet() { return managedServlet; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/ServletInitialHandler.java000066400000000000000000000523731420065311100334650ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.server.DefaultByteBufferPool; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpUpgradeListener; import io.undertow.server.SSLSessionInfo; import io.undertow.server.ServerConnection; import io.undertow.server.XnioBufferPoolAdaptor; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.api.ExceptionHandler; import io.undertow.servlet.api.LoggingExceptionHandler; import io.undertow.servlet.api.ServletDispatcher; import io.undertow.servlet.api.ThreadSetupHandler; import io.undertow.servlet.core.ApplicationListeners; import io.undertow.servlet.core.ServletBlockingHttpExchange; import io.undertow.servlet.spec.AsyncContextImpl; import io.undertow.servlet.spec.HttpServletRequestImpl; import io.undertow.servlet.spec.HttpServletResponseImpl; import io.undertow.servlet.spec.RequestDispatcherImpl; import io.undertow.servlet.spec.ServletContextImpl; import io.undertow.util.HeaderValues; import io.undertow.util.HttpString; import io.undertow.util.Protocols; import io.undertow.util.StatusCodes; import org.xnio.ChannelListener; import org.xnio.Option; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import org.xnio.Pool; import org.xnio.StreamConnection; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.ConnectedChannel; import org.xnio.conduits.ConduitStreamSinkChannel; import org.xnio.conduits.ConduitStreamSourceChannel; import org.xnio.conduits.StreamSinkConduit; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.security.AccessController; import java.security.PrivilegedExceptionAction; import java.util.Map; import java.util.concurrent.Executor; /** * This must be the initial handler in the blocking servlet chain. This sets up the request and response objects, * and attaches them the to exchange. * * @author Stuart Douglas */ public class ServletInitialHandler implements HttpHandler, ServletDispatcher { private static final RuntimePermission PERMISSION = new RuntimePermission("io.undertow.servlet.CREATE_INITIAL_HANDLER"); private final HttpHandler next; //private final HttpHandler asyncPath; private final ThreadSetupHandler.Action firstRequestHandler; private final ServletContextImpl servletContext; private final ApplicationListeners listeners; private final ServletPathMatches paths; private final ExceptionHandler exceptionHandler; private final HttpHandler dispatchHandler = new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (System.getSecurityManager() == null) { dispatchRequest(exchange, servletRequestContext, servletRequestContext.getOriginalServletPathMatch().getServletChain(), DispatcherType.REQUEST); } else { //sometimes thread pools inherit some random AccessController.doPrivileged(new PrivilegedExceptionAction() { @Override public Object run() throws Exception { dispatchRequest(exchange, servletRequestContext, servletRequestContext.getOriginalServletPathMatch().getServletChain(), DispatcherType.REQUEST); return null; } }); } } }; public ServletInitialHandler(final ServletPathMatches paths, final HttpHandler next, final Deployment deployment, final ServletContextImpl servletContext) { this.next = next; this.servletContext = servletContext; this.paths = paths; this.listeners = servletContext.getDeployment().getApplicationListeners(); SecurityManager sm = System.getSecurityManager(); if(sm != null) { //handle request can use doPrivilidged //we need to make sure this is not abused sm.checkPermission(PERMISSION); } ExceptionHandler handler = servletContext.getDeployment().getDeploymentInfo().getExceptionHandler(); if(handler != null) { this.exceptionHandler = handler; } else { this.exceptionHandler = LoggingExceptionHandler.DEFAULT; } this.firstRequestHandler = deployment.createThreadSetupAction(new ThreadSetupHandler.Action() { @Override public Object call(HttpServerExchange exchange, ServletRequestContext context) throws Exception { handleFirstRequest(exchange, context); return null; } }); } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final String path = exchange.getRelativePath(); if (Paths.isForbidden(path)) { exchange.setStatusCode(StatusCodes.NOT_FOUND); return; } final ServletPathMatch info = paths.getServletHandlerByPath(path); if (info.getType() == ServletPathMatch.Type.REWRITE) { // this can only happen if the path ends with a / // otherwise there would be a redirect instead exchange.setRelativePath(info.getRewriteLocation()); exchange.setRequestPath(exchange.getResolvedPath() + info.getRewriteLocation()); } final HttpServletResponseImpl response = new HttpServletResponseImpl(exchange, servletContext); final HttpServletRequestImpl request = new HttpServletRequestImpl(exchange, servletContext); final ServletRequestContext servletRequestContext = new ServletRequestContext(servletContext.getDeployment(), request, response, info); //set the max request size if applicable if (info.getServletChain().getManagedServlet().getMaxRequestSize() > 0 && isMultiPartExchange(exchange)) { exchange.setMaxEntitySize(info.getServletChain().getManagedServlet().getMaxRequestSize()); } exchange.putAttachment(ServletRequestContext.ATTACHMENT_KEY, servletRequestContext); exchange.startBlocking(new ServletBlockingHttpExchange(exchange)); servletRequestContext.setServletPathMatch(info); Executor executor = info.getServletChain().getExecutor(); if (executor == null) { executor = servletContext.getDeployment().getExecutor(); } if (exchange.isInIoThread() || executor != null) { //either the exchange has not been dispatched yet, or we need to use a special executor exchange.dispatch(executor, dispatchHandler); } else { dispatchRequest(exchange, servletRequestContext, info.getServletChain(), DispatcherType.REQUEST); } } public void dispatchToPath(final HttpServerExchange exchange, final ServletPathMatch pathInfo, final DispatcherType dispatcherType) throws Exception { final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); servletRequestContext.setServletPathMatch(pathInfo); dispatchRequest(exchange, servletRequestContext, pathInfo.getServletChain(), dispatcherType); } @Override public void dispatchToServlet(final HttpServerExchange exchange, final ServletChain servletchain, final DispatcherType dispatcherType) throws Exception { final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); DispatcherType oldDispatch = servletRequestContext.getDispatcherType(); ServletChain oldChain = servletRequestContext.getCurrentServlet(); try { dispatchRequest(exchange, servletRequestContext, servletchain, dispatcherType); } finally { servletRequestContext.setDispatcherType(oldDispatch); servletRequestContext.setCurrentServlet(oldChain); } } @Override public void dispatchMockRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException { final DefaultByteBufferPool bufferPool = new DefaultByteBufferPool(false, 1024, 0, 0); MockServerConnection connection = new MockServerConnection(bufferPool); HttpServerExchange exchange = new HttpServerExchange(connection); exchange.setRequestScheme(request.getScheme()); exchange.setRequestMethod(new HttpString(request.getMethod())); exchange.setProtocol(Protocols.HTTP_1_0); exchange.setResolvedPath(request.getContextPath()); String relative; if (request.getPathInfo() == null) { relative = request.getServletPath(); } else { relative = request.getServletPath() + request.getPathInfo(); } exchange.setRelativePath(relative); final ServletPathMatch info = paths.getServletHandlerByPath(request.getServletPath()); final HttpServletResponseImpl oResponse = new HttpServletResponseImpl(exchange, servletContext); final HttpServletRequestImpl oRequest = new HttpServletRequestImpl(exchange, servletContext); final ServletRequestContext servletRequestContext = new ServletRequestContext(servletContext.getDeployment(), oRequest, oResponse, info); servletRequestContext.setServletRequest(request); servletRequestContext.setServletResponse(response); //set the max request size if applicable if (info.getServletChain().getManagedServlet().getMaxRequestSize() > 0 && isMultiPartExchange(exchange)) { exchange.setMaxEntitySize(info.getServletChain().getManagedServlet().getMaxRequestSize()); } exchange.putAttachment(ServletRequestContext.ATTACHMENT_KEY, servletRequestContext); exchange.startBlocking(new ServletBlockingHttpExchange(exchange)); servletRequestContext.setServletPathMatch(info); try { dispatchRequest(exchange, servletRequestContext, info.getServletChain(), DispatcherType.REQUEST); } catch (Exception e) { if (e instanceof RuntimeException) { throw (RuntimeException) e; } throw new ServletException(e); } } private boolean isMultiPartExchange(final HttpServerExchange exhange) { //NOTE: should this include Range response? final HeaderValues contentTypeHeaders = exhange.getRequestHeaders().get("Content-Type"); if(contentTypeHeaders != null && contentTypeHeaders.size() >0) { return contentTypeHeaders.getFirst().startsWith("multipart"); } else { return false; } } private void dispatchRequest(final HttpServerExchange exchange, final ServletRequestContext servletRequestContext, final ServletChain servletChain, final DispatcherType dispatcherType) throws Exception { servletRequestContext.setDispatcherType(dispatcherType); servletRequestContext.setCurrentServlet(servletChain); if (dispatcherType == DispatcherType.REQUEST || dispatcherType == DispatcherType.ASYNC) { firstRequestHandler.call(exchange, servletRequestContext); } else { next.handleRequest(exchange); } } private void handleFirstRequest(final HttpServerExchange exchange, ServletRequestContext servletRequestContext) throws Exception { ServletRequest request = servletRequestContext.getServletRequest(); ServletResponse response = servletRequestContext.getServletResponse(); //set request attributes from the connector //generally this is only applicable if apache is sending AJP_ prefixed environment variables Map attrs = exchange.getAttachment(HttpServerExchange.REQUEST_ATTRIBUTES); if(attrs != null) { for(Map.Entry entry : attrs.entrySet()) { request.setAttribute(entry.getKey(), entry.getValue()); } } servletRequestContext.setRunningInsideHandler(true); try { listeners.requestInitialized(request); next.handleRequest(exchange); AsyncContextImpl asyncContextInternal = servletRequestContext.getOriginalRequest().getAsyncContextInternal(); if(asyncContextInternal != null && asyncContextInternal.isCompletedBeforeInitialRequestDone()) { asyncContextInternal.handleCompletedBeforeInitialRequestDone(); } // if(servletRequestContext.getErrorCode() > 0) { servletRequestContext.getOriginalResponse().doErrorDispatch(servletRequestContext.getErrorCode(), servletRequestContext.getErrorMessage()); } } catch (Throwable t) { servletRequestContext.setRunningInsideHandler(false); AsyncContextImpl asyncContextInternal = servletRequestContext.getOriginalRequest().getAsyncContextInternal(); if(asyncContextInternal != null && asyncContextInternal.isCompletedBeforeInitialRequestDone()) { asyncContextInternal.handleCompletedBeforeInitialRequestDone(); } if(asyncContextInternal != null) { asyncContextInternal.initialRequestFailed(); } //by default this will just log the exception boolean handled = exceptionHandler.handleThrowable(exchange, request, response, t); if(handled) { exchange.endExchange(); } else if (request.isAsyncStarted() || request.getDispatcherType() == DispatcherType.ASYNC) { exchange.unDispatch(); servletRequestContext.getOriginalRequest().getAsyncContextInternal().handleError(t); } else { if (!exchange.isResponseStarted()) { response.reset(); //reset the response exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); exchange.getResponseHeaders().clear(); String location = servletContext.getDeployment().getErrorPages().getErrorLocation(t); if (location == null) { location = servletContext.getDeployment().getErrorPages().getErrorLocation(StatusCodes.INTERNAL_SERVER_ERROR); } if (location != null) { RequestDispatcherImpl dispatcher = new RequestDispatcherImpl(location, servletContext); try { dispatcher.error(servletRequestContext, request, response, servletRequestContext.getOriginalServletPathMatch().getServletChain().getManagedServlet().getServletInfo().getName(), t); } catch (Exception e) { UndertowLogger.REQUEST_LOGGER.exceptionGeneratingErrorPage(e, location); } } else { if (servletRequestContext.displayStackTraces()) { ServletDebugPageHandler.handleRequest(exchange, servletRequestContext, t); } else { servletRequestContext.getOriginalResponse().doErrorDispatch(StatusCodes.INTERNAL_SERVER_ERROR, StatusCodes.INTERNAL_SERVER_ERROR_STRING); } } } } } finally { servletRequestContext.setRunningInsideHandler(false); listeners.requestDestroyed(request); } //if it is not dispatched and is not a mock request if (!exchange.isDispatched() && !(exchange.getConnection() instanceof MockServerConnection)) { servletRequestContext.getOriginalResponse().responseDone(); } if(!exchange.isDispatched()) { AsyncContextImpl ctx = servletRequestContext.getOriginalRequest().getAsyncContextInternal(); if(ctx != null) { ctx.complete(); } } } public HttpHandler getNext() { return next; } private static class MockServerConnection extends ServerConnection { private final ByteBufferPool bufferPool; private SSLSessionInfo sslSessionInfo; private XnioBufferPoolAdaptor poolAdaptor; private MockServerConnection(ByteBufferPool bufferPool) { this.bufferPool = bufferPool; } @Override public Pool getBufferPool() { if(poolAdaptor == null) { poolAdaptor = new XnioBufferPoolAdaptor(getByteBufferPool()); } return poolAdaptor; } @Override public ByteBufferPool getByteBufferPool() { return bufferPool; } @Override public XnioWorker getWorker() { return null; } @Override public XnioIoThread getIoThread() { return null; } @Override public HttpServerExchange sendOutOfBandResponse(HttpServerExchange exchange) { throw UndertowMessages.MESSAGES.outOfBandResponseNotSupported(); } @Override public boolean isContinueResponseSupported() { return false; } @Override public void terminateRequestChannel(HttpServerExchange exchange) { } @Override public boolean isOpen() { return true; } @Override public boolean supportsOption(Option option) { return false; } @Override public T getOption(Option option) throws IOException { return null; } @Override public T setOption(Option option, T value) throws IllegalArgumentException, IOException { return null; } @Override public void close() throws IOException { } @Override public SocketAddress getPeerAddress() { return null; } @Override public A getPeerAddress(Class type) { return null; } @Override public ChannelListener.Setter getCloseSetter() { return null; } @Override public SocketAddress getLocalAddress() { return null; } @Override public A getLocalAddress(Class type) { return null; } @Override public OptionMap getUndertowOptions() { return OptionMap.EMPTY; } @Override public int getBufferSize() { return 1024; } @Override public SSLSessionInfo getSslSessionInfo() { return sslSessionInfo; } @Override public void setSslSessionInfo(SSLSessionInfo sessionInfo) { sslSessionInfo = sessionInfo; } @Override public void addCloseListener(CloseListener listener) { } @Override public StreamConnection upgradeChannel() { return null; } @Override public ConduitStreamSinkChannel getSinkChannel() { return null; } @Override public ConduitStreamSourceChannel getSourceChannel() { return new ConduitStreamSourceChannel(null, null); } @Override protected StreamSinkConduit getSinkConduit(HttpServerExchange exchange, StreamSinkConduit conduit) { return conduit; } @Override protected boolean isUpgradeSupported() { return false; } @Override protected boolean isConnectSupported() { return false; } @Override protected void exchangeComplete(HttpServerExchange exchange) { } @Override protected void setUpgradeListener(HttpUpgradeListener upgradeListener) { //ignore } @Override protected void setConnectListener(HttpUpgradeListener connectListener) { //ignore } @Override protected void maxEntitySizeUpdated(HttpServerExchange exchange) { } @Override public String getTransportProtocol() { return "mock"; } @Override public boolean isRequestTrailerFieldsSupported() { return false; } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/ServletPathMatch.java000066400000000000000000000063771420065311100324520ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers; import javax.servlet.http.MappingMatch; /** * @author Stuart Douglas */ public class ServletPathMatch { private final String matched; private final String remaining; private final boolean requiredWelcomeFileMatch; private final ServletChain servletChain; private final String rewriteLocation; private final Type type; public ServletPathMatch(final ServletChain target, final String uri, boolean requiredWelcomeFileMatch) { this.servletChain = target; this.requiredWelcomeFileMatch = requiredWelcomeFileMatch; this.type = Type.NORMAL; this.rewriteLocation = null; if (target.getServletPath() == null) { //the default servlet is always considered to have matched the full path. this.matched = uri; this.remaining = null; } else { this.matched = target.getServletPath(); if(uri.length() == matched.length()) { remaining = null; } else { remaining = uri.substring(matched.length()); } } } public ServletPathMatch(final ServletChain target, final String matched, final String remaining, final Type type, final String rewriteLocation) { this.servletChain = target; this.matched = matched; this.remaining = remaining; this.requiredWelcomeFileMatch = false; this.type = type; this.rewriteLocation = rewriteLocation; } public String getMatched() { return matched; } public String getRemaining() { return remaining; } public boolean isRequiredWelcomeFileMatch() { return requiredWelcomeFileMatch; } public ServletChain getServletChain() { return servletChain; } public String getRewriteLocation() { return rewriteLocation; } public Type getType() { return type; } public String getMatchString() { return servletChain.getPattern(); } public MappingMatch getMappingMatch() { return servletChain.getMappingMatch(); } public enum Type { /** * A normal servlet match, the invocation should proceed as normal */ NORMAL, /** * A redirect is required, as the path does not end with a trailing slash */ REDIRECT, /** * An internal rewrite is required, because the path matched a welcome file. * The provided match data is the match data after the rewrite. */ REWRITE; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/ServletPathMatches.java000066400000000000000000000711451420065311100327750ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers; import static io.undertow.servlet.handlers.ServletPathMatch.Type.REDIRECT; import static io.undertow.servlet.handlers.ServletPathMatch.Type.REWRITE; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.DispatcherType; import javax.servlet.http.MappingMatch; import io.undertow.UndertowLogger; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.handlers.cache.LRUCache; import io.undertow.server.handlers.resource.CachingResourceManager; import io.undertow.server.handlers.resource.Resource; import io.undertow.server.handlers.resource.ResourceChangeEvent; import io.undertow.server.handlers.resource.ResourceChangeListener; import io.undertow.server.handlers.resource.ResourceManager; import io.undertow.servlet.UndertowServletMessages; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.FilterMappingInfo; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.core.ManagedFilter; import io.undertow.servlet.core.ManagedFilters; import io.undertow.servlet.core.ManagedServlet; import io.undertow.servlet.core.ManagedServlets; import io.undertow.servlet.handlers.security.ServletSecurityRoleHandler; /** * Facade around {@link ServletPathMatchesData}. This facade is responsible for re-generating the matches if anything changes. * * @author Stuart Douglas */ public class ServletPathMatches { public static final String DEFAULT_SERVLET_NAME = "default"; private final Deployment deployment; private volatile String[] welcomePages; private final ResourceManager resourceManager; private volatile ServletPathMatchesData data; // cache for fixed data matches (no welcome files involved) private final LRUCache pathMatchCacheFixed; // cache for resource matches that can change private final LRUCache pathMatchCacheResources; public ServletPathMatches(final Deployment deployment) { this.deployment = deployment; this.welcomePages = deployment.getDeploymentInfo().getWelcomePages().toArray(new String[deployment.getDeploymentInfo().getWelcomePages().size()]); this.resourceManager = deployment.getDeploymentInfo().getResourceManager(); this.pathMatchCacheFixed = new LRUCache<>(1000, -1, true); this.pathMatchCacheResources = new LRUCache<>(1000, resourceManager instanceof CachingResourceManager? ((CachingResourceManager) resourceManager).getMaxAge() : -1, true); // add change listener for welcome pages if (this.resourceManager.isResourceChangeListenerSupported()) { try { this.resourceManager.registerResourceChangeListener(new ResourceChangeListener() { @Override public void handleChanges(Collection changes) { for (ResourceChangeEvent change : changes) { // only check added or removed resources that can change welcome files if (change.getType() != ResourceChangeEvent.Type.MODIFIED) { String path = "/" + change.getResource(); // remove direct match and directory match pathMatchCacheResources.remove(path); pathMatchCacheResources.remove(path + "/"); // check welcome pages for (String welcomePage: welcomePages) { if (path.endsWith("/" + welcomePage)) { String pathToUpdate = path.substring(0, path.length() - welcomePage.length()); pathMatchCacheResources.remove(pathToUpdate); } } } } } }); } catch (Exception e) { UndertowLogger.ROOT_LOGGER.couldNotRegisterChangeListener(e); } } } public void initData(){ getData(); } public ServletChain getServletHandlerByName(final String name) { return getData().getServletHandlerByName(name); } public ServletPathMatch getServletHandlerByPath(final String path) { ServletPathMatch existing = pathMatchCacheFixed.get(path); if (existing == null) { existing = pathMatchCacheResources.get(path); } if(existing != null) { return existing; } ServletPathMatch match = getData().getServletHandlerByPath(path); if (!match.isRequiredWelcomeFileMatch()) { pathMatchCacheFixed.add(path, match); return match; } try { String remaining = match.getRemaining() == null ? match.getMatched() : match.getRemaining(); Resource resource = resourceManager.getResource(remaining); if (resource == null || !resource.isDirectory()) { pathMatchCacheResources.add(path, match); return match; } boolean pathEndsWithSlash = remaining.endsWith("/"); final String pathWithTrailingSlash = pathEndsWithSlash ? remaining : remaining + "/"; ServletPathMatch welcomePage = findWelcomeFile(pathWithTrailingSlash, !pathEndsWithSlash); if (welcomePage != null) { pathMatchCacheResources.add(path, welcomePage); return welcomePage; } else { welcomePage = findWelcomeServlet(pathWithTrailingSlash, !pathEndsWithSlash); if (welcomePage != null) { pathMatchCacheResources.add(path, welcomePage); return welcomePage; } else if(pathEndsWithSlash) { pathMatchCacheResources.add(path, match); return match; } else { ServletPathMatch redirect = new ServletPathMatch(match.getServletChain(), match.getMatched(), match.getRemaining(), REDIRECT, "/"); pathMatchCacheResources.add(path, redirect); return redirect; } } } catch (IOException e) { throw new RuntimeException(e); } } public void invalidate() { this.data = null; this.pathMatchCacheResources.clear(); this.pathMatchCacheFixed.clear(); } private ServletPathMatchesData getData() { ServletPathMatchesData data = this.data; if (data != null) { return data; } synchronized (this) { if (this.data != null) { return this.data; } return this.data = setupServletChains(); } } private ServletPathMatch findWelcomeFile(final String path, boolean requiresRedirect) { if(File.separatorChar != '/' && path.contains(File.separator)) { return null; } StringBuilder sb = new StringBuilder(); for (String i : welcomePages) { try { sb.append(path); sb.append(i); final String mergedPath = sb.toString(); sb.setLength(0); Resource resource = resourceManager.getResource(mergedPath); if (resource != null) { final ServletPathMatch handler = data.getServletHandlerByPath(mergedPath); return new ServletPathMatch(handler.getServletChain(), mergedPath, null, requiresRedirect ? REDIRECT : REWRITE, mergedPath); } } catch (IOException e) { } } return null; } private ServletPathMatch findWelcomeServlet(final String path, boolean requiresRedirect) { StringBuilder sb = new StringBuilder(); for (String i : welcomePages) { sb.append(path); sb.append(i); final String mergedPath = sb.toString(); sb.setLength(0); final ServletPathMatch handler = data.getServletHandlerByPath(mergedPath); if (handler != null && !handler.isRequiredWelcomeFileMatch()) { return new ServletPathMatch(handler.getServletChain(), handler.getMatched(), handler.getRemaining(), requiresRedirect ? REDIRECT : REWRITE, mergedPath); } } return null; } public void setWelcomePages(List welcomePages) { this.welcomePages = welcomePages.toArray(new String[welcomePages.size()]); } /** * Sets up the handlers in the servlet chain. We setup a chain for every path + extension match possibility. * (i.e. if there a m path mappings and n extension mappings we have n*m chains). *

    * If a chain consists of only the default servlet then we add it as an async handler, so that resources can be * served up directly without using blocking operations. *

    * TODO: this logic is a bit convoluted at the moment, we should look at simplifying it */ private ServletPathMatchesData setupServletChains() { //create the default servlet ServletHandler defaultServlet = null; final ManagedServlets servlets = deployment.getServlets(); final ManagedFilters filters = deployment.getFilters(); final Map extensionServlets = new HashMap<>(); final Map pathServlets = new HashMap<>(); final Set pathMatches = new HashSet<>(); final Set extensionMatches = new HashSet<>(); DeploymentInfo deploymentInfo = deployment.getDeploymentInfo(); //loop through all filter mappings, and add them to the set of known paths for (FilterMappingInfo mapping : deploymentInfo.getFilterMappings()) { if (mapping.getMappingType() == FilterMappingInfo.MappingType.URL) { String path = mapping.getMapping(); if (path.equals("*")) { //UNDERTOW-95, support this non-standard filter mapping path = "/*"; } if (!path.startsWith("*.")) { pathMatches.add(path); } else { extensionMatches.add(path.substring(2)); } } } //now loop through all servlets. for (Map.Entry entry : servlets.getServletHandlers().entrySet()) { final ServletHandler handler = entry.getValue(); //add the servlet to the appropriate path maps for (String path : handler.getManagedServlet().getServletInfo().getMappings()) { if (path.equals("/")) { //the default servlet pathMatches.add("/*"); if (defaultServlet != null) { throw UndertowServletMessages.MESSAGES.twoServletsWithSameMapping(path); } defaultServlet = handler; } else if (!path.startsWith("*.")) { //either an exact or a /* based path match if (path.isEmpty()) { path = "/"; } pathMatches.add(path); if (pathServlets.containsKey(path)) { throw UndertowServletMessages.MESSAGES.twoServletsWithSameMapping(path); } pathServlets.put(path, handler); } else { //an extension match based servlet String ext = path.substring(2); extensionMatches.add(ext); if(extensionServlets.containsKey(ext)) { throw UndertowServletMessages.MESSAGES.twoServletsWithSameMapping(path); } extensionServlets.put(ext, handler); } } } ServletHandler managedDefaultServlet = servlets.getServletHandler(DEFAULT_SERVLET_NAME); if(managedDefaultServlet == null) { //we always create a default servlet, even if it is not going to have any path mappings registered managedDefaultServlet = servlets.addServlet(new ServletInfo(DEFAULT_SERVLET_NAME, DefaultServlet.class)); } if (defaultServlet == null) { //no explicit default servlet was specified, so we register our mapping pathMatches.add("/*"); defaultServlet = managedDefaultServlet; } final ServletPathMatchesData.Builder builder = ServletPathMatchesData.builder(); //we now loop over every path in the application, and build up the patches based on this path //these paths contain both /* and exact matches. for (final String path : pathMatches) { //resolve the target servlet, will return null if this is the default servlet MatchData targetServletMatch = resolveServletForPath(path, pathServlets, extensionServlets, defaultServlet); final Map> noExtension = new EnumMap<>(DispatcherType.class); final Map>> extension = new HashMap<>(); //initialize the extension map. This contains all the filers in the noExtension map, plus //any filters that match the extension key for (String ext : extensionMatches) { extension.put(ext, new EnumMap>(DispatcherType.class)); } //loop over all the filters, and add them to the appropriate map in the correct order for (final FilterMappingInfo filterMapping : deploymentInfo.getFilterMappings()) { ManagedFilter filter = filters.getManagedFilter(filterMapping.getFilterName()); if (filterMapping.getMappingType() == FilterMappingInfo.MappingType.SERVLET) { if (targetServletMatch.handler != null) { if (filterMapping.getMapping().equals(targetServletMatch.handler.getManagedServlet().getServletInfo().getName()) || filterMapping.getMapping().equals("*")) { addToListMap(noExtension, filterMapping.getDispatcher(), filter); } } for (Map.Entry>> entry : extension.entrySet()) { ServletHandler pathServlet = targetServletMatch.handler; boolean defaultServletMatch = targetServletMatch.defaultServlet; if (defaultServletMatch && extensionServlets.containsKey(entry.getKey())) { pathServlet = extensionServlets.get(entry.getKey()); } if (filterMapping.getMapping().equals(pathServlet.getManagedServlet().getServletInfo().getName()) || filterMapping.getMapping().equals("*")) { addToListMap(extension.get(entry.getKey()), filterMapping.getDispatcher(), filter); } } } else { if (filterMapping.getMapping().isEmpty() || !filterMapping.getMapping().startsWith("*.")) { if (isFilterApplicable(path, filterMapping.getMapping())) { addToListMap(noExtension, filterMapping.getDispatcher(), filter); for (Map> l : extension.values()) { addToListMap(l, filterMapping.getDispatcher(), filter); } } } else { addToListMap(extension.get(filterMapping.getMapping().substring(2)), filterMapping.getDispatcher(), filter); } } } //resolve any matches and add them to the builder if (path.endsWith("/*")) { String prefix = path.substring(0, path.length() - 2); //add the default non-extension match builder.addPrefixMatch(prefix, createHandler(deploymentInfo, targetServletMatch.handler, noExtension, targetServletMatch.matchedPath, targetServletMatch.defaultServlet, targetServletMatch.mappingMatch, targetServletMatch.userPath), targetServletMatch.defaultServlet || targetServletMatch.handler.getManagedServlet().getServletInfo().isRequireWelcomeFileMapping()); //build up the chain for each non-extension match for (Map.Entry>> entry : extension.entrySet()) { ServletHandler pathServlet = targetServletMatch.handler; String pathMatch = targetServletMatch.matchedPath; final boolean defaultServletMatch; final String servletMatchPattern; final MappingMatch mappingMatch; if (targetServletMatch.defaultServlet) { // Path matches always take precedence over extension matches, however the default servlet is matched // at a lower priority, after extension matches. The "/*" pattern is applied implicitly onto the // default servlet. If there's an extension match in addition to a non-default servlet path match, // the servlet path match is higher priority. However if the path match is the default servlets // default catch-all path, the extension match is a higher priority. ServletHandler extensionServletHandler = extensionServlets.get(entry.getKey()); if (extensionServletHandler != null) { defaultServletMatch = false; pathServlet = extensionServletHandler; servletMatchPattern = "*." + entry.getKey(); mappingMatch = MappingMatch.EXTENSION; } else { defaultServletMatch = true; servletMatchPattern = "/"; mappingMatch = MappingMatch.DEFAULT; } } else { defaultServletMatch = false; servletMatchPattern = path; mappingMatch = MappingMatch.PATH; } HttpHandler handler = pathServlet; if (!entry.getValue().isEmpty()) { handler = new FilterHandler(entry.getValue(), deploymentInfo.isAllowNonStandardWrappers(), handler); } builder.addExtensionMatch(prefix, entry.getKey(), servletChain(handler, pathServlet.getManagedServlet(), entry.getValue(), pathMatch, deploymentInfo, defaultServletMatch, mappingMatch, servletMatchPattern)); } } else if (path.isEmpty()) { //the context root match builder.addExactMatch("/", createHandler(deploymentInfo, targetServletMatch.handler, noExtension, targetServletMatch.matchedPath, targetServletMatch.defaultServlet, targetServletMatch.mappingMatch, targetServletMatch.userPath)); } else { //we need to check for an extension match, so paths like /exact.txt will have the correct filter applied int lastSegmentIndex = path.lastIndexOf('/'); String lastSegment; if(lastSegmentIndex > 0) { lastSegment = path.substring(lastSegmentIndex); } else { lastSegment = path; } if (lastSegment.contains(".")) { String ext = lastSegment.substring(lastSegment.lastIndexOf('.') + 1); if (extension.containsKey(ext)) { Map> extMap = extension.get(ext); builder.addExactMatch(path, createHandler(deploymentInfo, targetServletMatch.handler, extMap, targetServletMatch.matchedPath, targetServletMatch.defaultServlet, targetServletMatch.mappingMatch, targetServletMatch.userPath)); } else { builder.addExactMatch(path, createHandler(deploymentInfo, targetServletMatch.handler, noExtension, targetServletMatch.matchedPath, targetServletMatch.defaultServlet, targetServletMatch.mappingMatch, targetServletMatch.userPath)); } } else { builder.addExactMatch(path, createHandler(deploymentInfo, targetServletMatch.handler, noExtension, targetServletMatch.matchedPath, targetServletMatch.defaultServlet, targetServletMatch.mappingMatch, targetServletMatch.userPath)); } } } //now setup name based mappings //these are used for name based dispatch for (Map.Entry entry : servlets.getServletHandlers().entrySet()) { final Map> filtersByDispatcher = new EnumMap<>(DispatcherType.class); for (final FilterMappingInfo filterMapping : deploymentInfo.getFilterMappings()) { ManagedFilter filter = filters.getManagedFilter(filterMapping.getFilterName()); if (filterMapping.getMappingType() == FilterMappingInfo.MappingType.SERVLET) { if (filterMapping.getMapping().equals(entry.getKey())) { addToListMap(filtersByDispatcher, filterMapping.getDispatcher(), filter); } } } if (filtersByDispatcher.isEmpty()) { builder.addNameMatch(entry.getKey(), servletChain(entry.getValue(), entry.getValue().getManagedServlet(), filtersByDispatcher, null, deploymentInfo, false, MappingMatch.EXACT, "")); } else { builder.addNameMatch(entry.getKey(), servletChain(new FilterHandler(filtersByDispatcher, deploymentInfo.isAllowNonStandardWrappers(), entry.getValue()), entry.getValue().getManagedServlet(), filtersByDispatcher, null, deploymentInfo, false, MappingMatch.EXACT, "")); } } return builder.build(); } private ServletChain createHandler(final DeploymentInfo deploymentInfo, final ServletHandler targetServlet, final Map> noExtension, final String servletPath, final boolean defaultServlet, MappingMatch mappingMatch, String pattern) { final ServletChain initialHandler; if (noExtension.isEmpty()) { initialHandler = servletChain(targetServlet, targetServlet.getManagedServlet(), noExtension, servletPath, deploymentInfo, defaultServlet, mappingMatch, pattern); } else { FilterHandler handler = new FilterHandler(noExtension, deploymentInfo.isAllowNonStandardWrappers(), targetServlet); initialHandler = servletChain(handler, targetServlet.getManagedServlet(), noExtension, servletPath, deploymentInfo, defaultServlet, mappingMatch, pattern); } return initialHandler; } private static MatchData resolveServletForPath(final String path, final Map pathServlets, final Map extensionServlets, ServletHandler defaultServlet) { if (pathServlets.containsKey(path)) { if (path.endsWith("/*")) { final String base = path.substring(0, path.length() - 2); return new MatchData(pathServlets.get(path), base, path, MappingMatch.PATH, false); } else { if(path.equals("/")) { return new MatchData(pathServlets.get(path), path, "", MappingMatch.CONTEXT_ROOT, false); } return new MatchData(pathServlets.get(path), path, path, MappingMatch.EXACT, false); } } String match = null; ServletHandler servlet = null; String userPath = ""; for (final Map.Entry entry : pathServlets.entrySet()) { String key = entry.getKey(); if (key.endsWith("/*")) { final String base = key.substring(0, key.length() - 1); if (match == null || base.length() > match.length()) { if (path.startsWith(base) || path.equals(base.substring(0, base.length() - 1))) { match = base.substring(0, base.length() - 1); servlet = entry.getValue(); userPath = key; } } } } if (servlet != null) { return new MatchData(servlet, match, userPath, MappingMatch.PATH, false); } int index = path.lastIndexOf('.'); if (index != -1) { String ext = path.substring(index + 1); servlet = extensionServlets.get(ext); if (servlet != null) { return new MatchData(servlet, null, "*." + ext, MappingMatch.EXTENSION, false); } } return new MatchData(defaultServlet, null, "/", MappingMatch.DEFAULT, true); } private static boolean isFilterApplicable(final String path, final String filterPath) { String modifiedPath; if (filterPath.equals("*")) { modifiedPath = "/*"; } else { modifiedPath = filterPath; } if (path.isEmpty()) { return modifiedPath.equals("/*") || modifiedPath.equals("/"); } if (modifiedPath.endsWith("/*")) { String baseFilterPath = modifiedPath.substring(0, modifiedPath.length() - 1); String exactFilterPath = modifiedPath.substring(0, modifiedPath.length() - 2); return path.startsWith(baseFilterPath) || path.equals(exactFilterPath); } else { return modifiedPath.equals(path); } } private static void addToListMap(final Map> map, final K key, final V value) { List list = map.get(key); if (list == null) { map.put(key, list = new ArrayList<>()); } list.add(value); } private static ServletChain servletChain(HttpHandler next, final ManagedServlet managedServlet, Map> filters, final String servletPath, final DeploymentInfo deploymentInfo, boolean defaultServlet, MappingMatch mappingMatch, String pattern) { HttpHandler servletHandler = next; if(!deploymentInfo.isSecurityDisabled()) { servletHandler = new ServletSecurityRoleHandler(servletHandler, deploymentInfo.getAuthorizationManager()); } servletHandler = wrapHandlers(servletHandler, managedServlet.getServletInfo().getHandlerChainWrappers()); return new ServletChain(servletHandler, managedServlet, servletPath, defaultServlet, mappingMatch, pattern, filters); } private static HttpHandler wrapHandlers(final HttpHandler wrapee, final List wrappers) { HttpHandler current = wrapee; for (HandlerWrapper wrapper : wrappers) { current = wrapper.wrap(current); } return current; } private static class MatchData { final ServletHandler handler; final String matchedPath; final String userPath; final MappingMatch mappingMatch; final boolean defaultServlet; private MatchData(final ServletHandler handler, final String matchedPath, String userPath, MappingMatch mappingMatch, boolean defaultServlet) { this.handler = handler; this.matchedPath = matchedPath; this.userPath = userPath; this.mappingMatch = mappingMatch; this.defaultServlet = defaultServlet; } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/ServletPathMatchesData.java000066400000000000000000000135061420065311100335640ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers; import io.undertow.UndertowMessages; import io.undertow.util.SubstringMap; import java.util.HashMap; import java.util.Map; /** * Class that maintains the complete set of servlet path matches. * * * @author Stuart Douglas */ class ServletPathMatchesData { private final Map exactPathMatches; private final SubstringMap prefixMatches; private final Map nameMatches; ServletPathMatchesData(final Map exactPathMatches, final SubstringMap prefixMatches, final Map nameMatches) { this.prefixMatches = prefixMatches; this.nameMatches = nameMatches; Map newExactPathMatches = new HashMap<>(); for (Map.Entry entry : exactPathMatches.entrySet()) { newExactPathMatches.put(entry.getKey(), new ServletPathMatch(entry.getValue(), entry.getKey(), entry.getValue().isDefaultServletMapping())); } this.exactPathMatches = newExactPathMatches; } public ServletChain getServletHandlerByName(final String name) { return nameMatches.get(name); } public ServletPathMatch getServletHandlerByExactPath(final String path) { return exactPathMatches.get(path); } public ServletPathMatch getServletHandlerByPath(final String path) { ServletPathMatch exact = exactPathMatches.get(path); if (exact != null) { return exact; } SubstringMap.SubstringMatch match = prefixMatches.get(path, path.length()); if (match != null) { return handleMatch(path, match.getValue(), path.lastIndexOf('.')); } int extensionPos = -1; for (int i = path.length() - 1; i >= 0; --i) { final char c = path.charAt(i); if (c == '/') { match = prefixMatches.get(path, i); if (match != null) { return handleMatch(path, match.getValue(), extensionPos); } } else if (c == '.' && extensionPos == -1) { extensionPos = i; } } //this should never happen //as the default servlet is always registered under /* throw UndertowMessages.MESSAGES.servletPathMatchFailed(); } private ServletPathMatch handleMatch(final String path, final PathMatch match, final int extensionPos) { if (extensionPos == -1 || match.extensionMatches.isEmpty()) { return new ServletPathMatch(match.defaultHandler, path, match.requireWelcomeFileMatch); } final String ext = path.substring(extensionPos + 1); ServletChain handler = match.extensionMatches.get(ext); if (handler != null) { return new ServletPathMatch(handler, path, handler.getManagedServlet().getServletInfo().isRequireWelcomeFileMapping()); } return new ServletPathMatch(match.defaultHandler, path, match.requireWelcomeFileMatch); } public static Builder builder() { return new Builder(); } public static final class Builder { private final Map exactPathMatches = new HashMap<>(); private final SubstringMap prefixMatches = new SubstringMap(); private final Map nameMatches = new HashMap<>(); public void addExactMatch(final String exactMatch, final ServletChain match) { exactPathMatches.put(exactMatch, match); } public void addPrefixMatch(final String prefix, final ServletChain match, final boolean requireWelcomeFileMatch) { SubstringMap.SubstringMatch mt = prefixMatches.get(prefix); PathMatch m; if (mt == null) { prefixMatches.put(prefix, m = new PathMatch(match)); } else { m = mt.getValue(); } m.defaultHandler = match; m.requireWelcomeFileMatch = requireWelcomeFileMatch; } public void addExtensionMatch(final String prefix, final String extension, final ServletChain match) { SubstringMap.SubstringMatch mt = prefixMatches.get(prefix); PathMatch m; if (mt == null) { prefixMatches.put(prefix, m = new PathMatch(null)); } else { m = mt.getValue(); } m.extensionMatches.put(extension, match); } public void addNameMatch(final String name, final ServletChain match) { nameMatches.put(name, match); } public ServletPathMatchesData build() { return new ServletPathMatchesData(exactPathMatches, prefixMatches, nameMatches); } } private static class PathMatch { private final Map extensionMatches = new HashMap<>(); private volatile ServletChain defaultHandler; private volatile boolean requireWelcomeFileMatch; PathMatch(final ServletChain defaultHandler) { this.defaultHandler = defaultHandler; } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/ServletRequestContext.java000066400000000000000000000231331420065311100335630ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.List; import io.undertow.UndertowMessages; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.api.ServletStackTraces; import io.undertow.servlet.api.TransportGuaranteeType; import io.undertow.servlet.api.SingleConstraintMatch; import io.undertow.servlet.spec.HttpServletRequestImpl; import io.undertow.servlet.spec.HttpServletResponseImpl; import io.undertow.servlet.spec.HttpSessionImpl; import io.undertow.servlet.spec.ServletContextImpl; import io.undertow.util.AttachmentKey; import io.undertow.util.Headers; import javax.servlet.DispatcherType; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; /** * All the information that servlet needs to attach to the exchange. *

    * This is all stored under this class, rather than using individual attachments, as * this approach has significant performance advantages. *

    * The {@link ServletInitialHandler} also pushed this information to the {@link #CURRENT} * thread local, which allows it to be access even if the request or response have been * wrapped with non-compliant wrapper classes. * * @author Stuart Douglas */ public class ServletRequestContext { private static final RuntimePermission GET_CURRENT_REQUEST = new RuntimePermission("io.undertow.servlet.GET_CURRENT_REQUEST"); private static final RuntimePermission SET_CURRENT_REQUEST = new RuntimePermission("io.undertow.servlet.SET_CURRENT_REQUEST"); private static final ThreadLocal CURRENT = new ThreadLocal<>(); public static void setCurrentRequestContext(ServletRequestContext servletRequestContext) { SecurityManager sm = System.getSecurityManager(); if(sm != null) { sm.checkPermission(SET_CURRENT_REQUEST); } CURRENT.set(servletRequestContext); } public static void clearCurrentServletAttachments() { SecurityManager sm = System.getSecurityManager(); if(sm != null) { sm.checkPermission(SET_CURRENT_REQUEST); } CURRENT.remove(); } /** * Gets the {@link ServletRequestContext} assigned to the current thread. * * @return The current {@link ServletRequestContext} based on the calling thread * @throws IllegalStateException if the calling thread does not have a {@link ServletRequestContext} set * @see ServletRequestContext#current() */ public static ServletRequestContext requireCurrent() { ServletRequestContext attachments = current(); if (attachments == null) { throw UndertowMessages.MESSAGES.noRequestActive(); } return attachments; } /** * Gets the current threads {@link ServletRequestContext} if set, otherwise null. * * @return The current {@link ServletRequestContext} based on the calling thread, or null if unavailable */ public static ServletRequestContext current() { SecurityManager sm = System.getSecurityManager(); if(sm != null) { sm.checkPermission(GET_CURRENT_REQUEST); } return CURRENT.get(); } public static final AttachmentKey ATTACHMENT_KEY = AttachmentKey.create(ServletRequestContext.class); private final Deployment deployment; private final HttpServletRequestImpl originalRequest; private final HttpServletResponseImpl originalResponse; private final ServletPathMatch originalServletPathMatch; private ServletResponse servletResponse; private ServletRequest servletRequest; private DispatcherType dispatcherType; private ServletChain currentServlet; private ServletPathMatch servletPathMatch; private List requiredConstrains; private TransportGuaranteeType transportGuarenteeType; private HttpSessionImpl session; private ServletContextImpl currentServletContext; private String overridenSessionId; /** * If this is true the request is running inside the context of ServletInitialHandler */ private boolean runningInsideHandler = false; private int errorCode = -1; private String errorMessage; private boolean asyncSupported = true; public ServletRequestContext(final Deployment deployment, final HttpServletRequestImpl originalRequest, final HttpServletResponseImpl originalResponse, final ServletPathMatch originalServletPathMatch) { this.deployment = deployment; this.originalRequest = originalRequest; this.originalResponse = originalResponse; this.servletRequest = originalRequest; this.servletResponse = originalResponse; this.originalServletPathMatch = originalServletPathMatch; this.currentServletContext = deployment.getServletContext(); } public Deployment getDeployment() { return deployment; } public ServletChain getCurrentServlet() { return currentServlet; } public void setCurrentServlet(ServletChain currentServlet) { this.currentServlet = currentServlet; } public ServletPathMatch getServletPathMatch() { return servletPathMatch; } public void setServletPathMatch(ServletPathMatch servletPathMatch) { this.servletPathMatch = servletPathMatch; } public List getRequiredConstrains() { return requiredConstrains; } public void setRequiredConstrains(List requiredConstrains) { this.requiredConstrains = requiredConstrains; } public TransportGuaranteeType getTransportGuarenteeType() { return transportGuarenteeType; } public void setTransportGuarenteeType(TransportGuaranteeType transportGuarenteeType) { this.transportGuarenteeType = transportGuarenteeType; } public ServletResponse getServletResponse() { return servletResponse; } public void setServletResponse(ServletResponse servletResponse) { this.servletResponse = servletResponse; } public ServletRequest getServletRequest() { return servletRequest; } public void setServletRequest(ServletRequest servletRequest) { this.servletRequest = servletRequest; } public DispatcherType getDispatcherType() { return dispatcherType; } public void setDispatcherType(DispatcherType dispatcherType) { this.dispatcherType = dispatcherType; } public HttpServletRequestImpl getOriginalRequest() { return originalRequest; } public HttpServletResponseImpl getOriginalResponse() { return originalResponse; } public HttpSessionImpl getSession() { return session; } public void setSession(final HttpSessionImpl session) { this.session = session; } public HttpServerExchange getExchange() { return originalRequest.getExchange(); } public ServletPathMatch getOriginalServletPathMatch() { return originalServletPathMatch; } public ServletContextImpl getCurrentServletContext() { return currentServletContext; } public void setCurrentServletContext(ServletContextImpl currentServletContext) { this.currentServletContext = currentServletContext; } public boolean displayStackTraces() { ServletStackTraces mode = deployment.getDeploymentInfo().getServletStackTraces(); if (mode == ServletStackTraces.NONE) { return false; } else if (mode == ServletStackTraces.ALL) { return true; } else { InetSocketAddress localAddress = getExchange().getSourceAddress(); if(localAddress == null) { return false; } InetAddress address = localAddress.getAddress(); if(address == null) { return false; } if(!address.isLoopbackAddress()) { return false; } return !getExchange().getRequestHeaders().contains(Headers.X_FORWARDED_FOR); } } public void setError(int sc, String msg) { this.errorCode = sc; this.errorMessage = msg; } public int getErrorCode() { return errorCode; } public String getErrorMessage() { return errorMessage; } public boolean isRunningInsideHandler() { return runningInsideHandler; } public void setRunningInsideHandler(boolean runningInsideHandler) { this.runningInsideHandler = runningInsideHandler; } public boolean isAsyncSupported() { return asyncSupported; } public String getOverridenSessionId() { return overridenSessionId; } public void setOverridenSessionId(String overridenSessionId) { this.overridenSessionId = overridenSessionId; } public void setAsyncSupported(boolean asyncSupported) { this.asyncSupported = asyncSupported; } } SessionRestoringHandler.java000066400000000000000000000162531420065311100337650ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.session.Session; import io.undertow.server.session.SessionManager; import io.undertow.servlet.UndertowServletLogger; import io.undertow.servlet.api.SessionPersistenceManager; import io.undertow.servlet.core.Lifecycle; import io.undertow.servlet.spec.HttpSessionImpl; import io.undertow.servlet.spec.ServletContextImpl; import javax.servlet.http.HttpSessionActivationListener; import javax.servlet.http.HttpSessionEvent; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import static io.undertow.servlet.api.SessionPersistenceManager.PersistentSession; /** * A handler that restores persistent HTTP session state for requests in development mode. *

    * This handler should not be used in production environments. * * @author Stuart Douglas */ public class SessionRestoringHandler implements HttpHandler, Lifecycle { private final String deploymentName; private final Map data; private final SessionManager sessionManager; private final ServletContextImpl servletContext; private final HttpHandler next; private final SessionPersistenceManager sessionPersistenceManager; private volatile boolean started = false; public SessionRestoringHandler(String deploymentName, SessionManager sessionManager, ServletContextImpl servletContext, HttpHandler next, SessionPersistenceManager sessionPersistenceManager) { this.deploymentName = deploymentName; this.sessionManager = sessionManager; this.servletContext = servletContext; this.next = next; this.sessionPersistenceManager = sessionPersistenceManager; this.data = new ConcurrentHashMap<>(); } public void start() { ClassLoader old = getTccl(); try { setTccl(servletContext.getClassLoader()); try { final Map sessionData = sessionPersistenceManager.loadSessionAttributes(deploymentName, servletContext.getClassLoader()); if (sessionData != null) { this.data.putAll(sessionData); } } catch (Exception e) { UndertowServletLogger.ROOT_LOGGER.failedtoLoadPersistentSessions(e); } this.started = true; } finally { setTccl(old); } } public void stop() { ClassLoader old = getTccl(); try { setTccl(servletContext.getClassLoader()); this.started = false; final Map objectData = new HashMap<>(); for (String sessionId : sessionManager.getTransientSessions()) { Session session = sessionManager.getSession(sessionId); if (session != null) { final HttpSessionEvent event = new HttpSessionEvent(SecurityActions.forSession(session, servletContext, false)); final Map sessionData = new HashMap<>(); for (String attr : session.getAttributeNames()) { final Object attribute = session.getAttribute(attr); sessionData.put(attr, attribute); if (attribute instanceof HttpSessionActivationListener) { ((HttpSessionActivationListener) attribute).sessionWillPassivate(event); } } objectData.put(sessionId, new PersistentSession(new Date(session.getLastAccessedTime() + (session.getMaxInactiveInterval() * 1000)), sessionData)); } } sessionPersistenceManager.persistSessions(deploymentName, objectData); this.data.clear(); } finally { setTccl(old); } } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { final String incomingSessionId = servletContext.getSessionConfig().findSessionId(exchange); if (incomingSessionId == null || !data.containsKey(incomingSessionId)) { next.handleRequest(exchange); return; } //we have some old data PersistentSession result = data.remove(incomingSessionId); if (result != null) { long time = System.currentTimeMillis(); if (time < result.getExpiration().getTime()) { final HttpSessionImpl session = servletContext.getSession(exchange, true); final HttpSessionEvent event = new HttpSessionEvent(session); for (Map.Entry entry : result.getSessionData().entrySet()) { if (entry.getValue() instanceof HttpSessionActivationListener) { ((HttpSessionActivationListener) entry.getValue()).sessionDidActivate(event); } if(entry.getKey().startsWith(HttpSessionImpl.IO_UNDERTOW)) { session.getSession().setAttribute(entry.getKey(), entry.getValue()); } else { session.setAttribute(entry.getKey(), entry.getValue()); } } } } next.handleRequest(exchange); } @Override public boolean isStarted() { return started; } private ClassLoader getTccl() { if (System.getSecurityManager() == null) { return Thread.currentThread().getContextClassLoader(); } else { return AccessController.doPrivileged(new PrivilegedAction() { @Override public ClassLoader run() { return Thread.currentThread().getContextClassLoader(); } }); } } private void setTccl(final ClassLoader classLoader) { if (System.getSecurityManager() == null) { Thread.currentThread().setContextClassLoader(classLoader); } else { AccessController.doPrivileged(new PrivilegedAction() { @Override public Void run() { Thread.currentThread().setContextClassLoader(classLoader); return null; } }); } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/security/000077500000000000000000000000001420065311100302235ustar00rootroot00000000000000CachedAuthenticatedSessionHandler.java000066400000000000000000000165701420065311100375340ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers.security; import io.undertow.security.api.AuthenticatedSessionManager; import io.undertow.security.api.AuthenticatedSessionManager.AuthenticatedSession; import io.undertow.security.api.NotificationReceiver; import io.undertow.security.api.SecurityContext; import io.undertow.security.api.SecurityNotification; import io.undertow.security.api.SecurityNotification.EventType; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.session.Session; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.servlet.spec.HttpSessionImpl; import io.undertow.servlet.spec.ServletContextImpl; import io.undertow.servlet.util.SavedRequest; import java.security.AccessController; import javax.servlet.http.HttpSession; /** * {@link HttpHandler} responsible for setting up the {@link AuthenticatedSessionManager} for cached authentications and * registering a {@link NotificationReceiver} to receive the security notifications. * * This handler also forces the session to change its session ID on sucessful authentication. * * @author Darran Lofthouse */ public class CachedAuthenticatedSessionHandler implements HttpHandler { public static final String ATTRIBUTE_NAME = CachedAuthenticatedSessionHandler.class.getName() + ".AuthenticatedSession"; public static final String NO_ID_CHANGE_REQUIRED = CachedAuthenticatedSessionHandler.class.getName() + ".NoIdChangeRequired"; private final NotificationReceiver NOTIFICATION_RECEIVER = new SecurityNotificationReceiver(); private final AuthenticatedSessionManager SESSION_MANAGER = new ServletAuthenticatedSessionManager(); private final HttpHandler next; private final ServletContextImpl servletContext; public CachedAuthenticatedSessionHandler(final HttpHandler next, final ServletContextImpl servletContext) { this.next = next; this.servletContext = servletContext; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { SecurityContext securityContext = exchange.getSecurityContext(); securityContext.registerNotificationReceiver(NOTIFICATION_RECEIVER); HttpSession session = servletContext.getSession(exchange, false); // If there was no existing HttpSession then there could not be a cached AuthenticatedSession so don't bother setting // the AuthenticatedSessionManager. if (session != null) { exchange.putAttachment(AuthenticatedSessionManager.ATTACHMENT_KEY, SESSION_MANAGER); SavedRequest.tryRestoreRequest(exchange, session); //not sure if this is where it belongs } next.handleRequest(exchange); } private class SecurityNotificationReceiver implements NotificationReceiver { @Override public void handleNotification(SecurityNotification notification) { EventType eventType = notification.getEventType(); HttpSessionImpl httpSession = servletContext.getSession(notification.getExchange(), false); switch (eventType) { case AUTHENTICATED: if (isCacheable(notification)) { if(servletContext.getDeployment().getDeploymentInfo().isChangeSessionIdOnLogin()) { if (httpSession != null) { Session session = underlyingSession(httpSession); if (!httpSession.isNew() && !httpSession.isInvalid() && session.getAttribute(NO_ID_CHANGE_REQUIRED) == null) { ServletRequestContext src = notification.getExchange().getAttachment(ServletRequestContext.ATTACHMENT_KEY); src.getOriginalRequest().changeSessionId(); } if(session.getAttribute(NO_ID_CHANGE_REQUIRED) == null) { session.setAttribute(NO_ID_CHANGE_REQUIRED, true); } } } if(httpSession == null) { httpSession = servletContext.getSession(notification.getExchange(), true); } Session session = underlyingSession(httpSession); // It is normal for this notification to be received when using a previously cached session - in that // case the IDM would have been given an opportunity to re-load the Account so updating here ready for // the next request is desired. session.setAttribute(ATTRIBUTE_NAME, new AuthenticatedSession(notification.getAccount(), notification.getMechanism())); } break; case LOGGED_OUT: if (httpSession != null) { Session session = underlyingSession(httpSession); session.removeAttribute(ATTRIBUTE_NAME); session.removeAttribute(NO_ID_CHANGE_REQUIRED); } break; } } } protected Session underlyingSession(HttpSessionImpl httpSession) { Session session; if (System.getSecurityManager() == null) { session = httpSession.getSession(); } else { session = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(httpSession)); } return session; } private class ServletAuthenticatedSessionManager implements AuthenticatedSessionManager { @Override public AuthenticatedSession lookupSession(HttpServerExchange exchange) { HttpSessionImpl httpSession = servletContext.getSession(exchange, false); if (httpSession != null) { Session session = underlyingSession(httpSession); return (AuthenticatedSession) session.getAttribute(ATTRIBUTE_NAME); } return null; } @Override public void clearSession(HttpServerExchange exchange) { HttpSessionImpl httpSession = servletContext.getSession(exchange, false); if (httpSession != null) { Session session = underlyingSession(httpSession); session.removeAttribute(ATTRIBUTE_NAME); } } } private boolean isCacheable(final SecurityNotification notification) { return notification.isProgramatic() || notification.isCachingRequired(); } } SSLInformationAssociationHandler.java000066400000000000000000000110401420065311100373450ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers.security; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import javax.servlet.ServletRequest; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.SSLSessionInfo; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.util.HexConverter; /** * Handler that associates SSL metadata with request *

    * cipher suite - javax.servlet.request.cipher_suite String * bit size of the algorithm - javax.servlet.request.key_size Integer * SSL session id - javax.servlet.request.ssl_session_id String * * @author Tomaz Cerar (c) 2013 Red Hat Inc. */ public class SSLInformationAssociationHandler implements HttpHandler { private final HttpHandler next; public SSLInformationAssociationHandler(final HttpHandler next) { this.next = next; } /** * Given the name of a TLS/SSL cipher suite, return an int representing it effective stream * cipher key strength. i.e. How much entropy material is in the key material being fed into the * encryption routines. *

    * http://www.thesprawl.org/research/tls-and-ssl-cipher-suites/ *

    * * @param cipherSuite String name of the TLS cipher suite. * @return int indicating the effective key entropy bit-length. */ public static int getKeyLength(String cipherSuite) { return SSLSessionInfo.calculateKeySize(cipherSuite); } /* ------------------------------------------------------------ */ /** * Return the chain of X509 certificates used to negotiate the SSL Session. * * @param session the javax.net.ssl.SSLSession to use as the source of the cert chain. * @return the chain of java.security.cert.X509Certificates used to * negotiate the SSL connection.
    * Will be null if the chain is missing or empty. */ private X509Certificate[] getCerts(SSLSessionInfo session) { try { Certificate[] javaCerts = session.getPeerCertificates(); if (javaCerts == null) { return null; } int x509Certs = 0; for (Certificate javaCert : javaCerts) { if (javaCert instanceof X509Certificate) { ++x509Certs; } } if (x509Certs == 0) { return null; } int resultIndex = 0; X509Certificate[] results = new X509Certificate[x509Certs]; for (Certificate certificate : javaCerts) { if (certificate instanceof X509Certificate) { results[resultIndex++] = (X509Certificate) certificate; } } return results; } catch (Exception e) { return null; } } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { ServletRequest request = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getServletRequest(); SSLSessionInfo ssl = exchange.getConnection().getSslSessionInfo(); if (ssl != null) { String cipherSuite = ssl.getCipherSuite(); byte[] sessionId = ssl.getSessionId(); request.setAttribute("javax.servlet.request.cipher_suite", cipherSuite); request.setAttribute("javax.servlet.request.key_size", ssl.getKeySize()); request.setAttribute("javax.servlet.request.ssl_session_id", sessionId != null? HexConverter.convertToHexString(sessionId) : null); X509Certificate[] certs = getCerts(ssl); if (certs != null) { request.setAttribute("javax.servlet.request.X509Certificate", certs); } } next.handleRequest(exchange); } } SecurityPathMatch.java000066400000000000000000000027231420065311100344140ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers.security; import io.undertow.servlet.api.SingleConstraintMatch; import io.undertow.servlet.api.TransportGuaranteeType; /** * @author Stuart Douglas */ class SecurityPathMatch { private final TransportGuaranteeType transportGuaranteeType; private final SingleConstraintMatch mergedConstraint; SecurityPathMatch(final TransportGuaranteeType transportGuaranteeType, final SingleConstraintMatch mergedConstraint) { this.transportGuaranteeType = transportGuaranteeType; this.mergedConstraint = mergedConstraint; } TransportGuaranteeType getTransportGuaranteeType() { return transportGuaranteeType; } SingleConstraintMatch getMergedConstraint() { return mergedConstraint; } } SecurityPathMatches.java000066400000000000000000000401671420065311100347500ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers.security; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import io.undertow.servlet.UndertowServletLogger; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.SecurityInfo; import io.undertow.servlet.api.SingleConstraintMatch; import io.undertow.servlet.api.TransportGuaranteeType; import io.undertow.servlet.api.WebResourceCollection; import io.undertow.util.Methods; /** * @author Stuart Douglas */ public class SecurityPathMatches { private static Set KNOWN_METHODS; static { Set methods = new HashSet<>(); methods.add(Methods.GET_STRING); methods.add(Methods.POST_STRING); methods.add(Methods.PUT_STRING); methods.add(Methods.DELETE_STRING); methods.add(Methods.OPTIONS_STRING); methods.add(Methods.HEAD_STRING); methods.add(Methods.TRACE_STRING); methods.add(Methods.CONNECT_STRING); KNOWN_METHODS = Collections.unmodifiableSet(methods); } private final boolean denyUncoveredHttpMethods; private final PathSecurityInformation defaultPathSecurityInformation; private final Map exactPathRoleInformation; private final Map prefixPathRoleInformation; private final Map extensionRoleInformation; private SecurityPathMatches(final boolean denyUncoveredHttpMethods, final PathSecurityInformation defaultPathSecurityInformation, final Map exactPathRoleInformation, final Map prefixPathRoleInformation, final Map extensionRoleInformation) { this.denyUncoveredHttpMethods = denyUncoveredHttpMethods; this.defaultPathSecurityInformation = defaultPathSecurityInformation; this.exactPathRoleInformation = exactPathRoleInformation; this.prefixPathRoleInformation = prefixPathRoleInformation; this.extensionRoleInformation = extensionRoleInformation; } /** * @return true If no security path information has been defined */ public boolean isEmpty() { return defaultPathSecurityInformation.excludedMethodRoles.isEmpty() && defaultPathSecurityInformation.perMethodRequiredRoles.isEmpty() && defaultPathSecurityInformation.defaultRequiredRoles.isEmpty() && exactPathRoleInformation.isEmpty() && prefixPathRoleInformation.isEmpty() && extensionRoleInformation.isEmpty(); } public SecurityPathMatch getSecurityInfo(final String path, final String method) { RuntimeMatch currentMatch = new RuntimeMatch(); handleMatch(method, defaultPathSecurityInformation, currentMatch); PathSecurityInformation match = exactPathRoleInformation.get(path); PathSecurityInformation extensionMatch = null; if (match != null) { handleMatch(method, match, currentMatch); return new SecurityPathMatch(currentMatch.type, mergeConstraints(currentMatch)); } match = prefixPathRoleInformation.get(path); if (match != null) { handleMatch(method, match, currentMatch); return new SecurityPathMatch(currentMatch.type, mergeConstraints(currentMatch)); } int qsPos = -1; boolean extension = false; for (int i = path.length() - 1; i >= 0; --i) { final char c = path.charAt(i); if (c == '?') { //there was a query string, check the exact matches again final String part = path.substring(0, i); match = exactPathRoleInformation.get(part); if (match != null) { handleMatch(method, match, currentMatch); return new SecurityPathMatch(currentMatch.type, mergeConstraints(currentMatch)); } qsPos = i; extension = false; } else if (c == '/') { extension = true; final String part = path.substring(0, i); match = prefixPathRoleInformation.get(part); if (match != null) { handleMatch(method, match, currentMatch); return new SecurityPathMatch(currentMatch.type, mergeConstraints(currentMatch)); } } else if (c == '.') { if (!extension) { extension = true; final String ext; if (qsPos == -1) { ext = path.substring(i + 1, path.length()); } else { ext = path.substring(i + 1, qsPos); } extensionMatch = extensionRoleInformation.get(ext); } } } if (extensionMatch != null) { handleMatch(method, extensionMatch, currentMatch); return new SecurityPathMatch(currentMatch.type, mergeConstraints(currentMatch)); } return new SecurityPathMatch(currentMatch.type, mergeConstraints(currentMatch)); } /** * merge all constraints, as per 13.8.1 Combining Constraints */ private SingleConstraintMatch mergeConstraints(final RuntimeMatch currentMatch) { if (currentMatch.uncovered && denyUncoveredHttpMethods) { return new SingleConstraintMatch(SecurityInfo.EmptyRoleSemantic.DENY, Collections.emptySet()); } final Set allowedRoles = new HashSet<>(); for (SingleConstraintMatch match : currentMatch.constraints) { if (match.getRequiredRoles().isEmpty()) { return new SingleConstraintMatch(match.getEmptyRoleSemantic(), Collections.emptySet()); } else { allowedRoles.addAll(match.getRequiredRoles()); } } return new SingleConstraintMatch(SecurityInfo.EmptyRoleSemantic.PERMIT, allowedRoles); } private void handleMatch(final String method, final PathSecurityInformation exact, RuntimeMatch currentMatch) { List roles = exact.defaultRequiredRoles; for (SecurityInformation role : roles) { transport(currentMatch, role.transportGuaranteeType); currentMatch.constraints.add(new SingleConstraintMatch(role.emptyRoleSemantic, role.roles)); if (role.emptyRoleSemantic == SecurityInfo.EmptyRoleSemantic.DENY || !role.roles.isEmpty()) { currentMatch.uncovered = false; } } List methodInfo = exact.perMethodRequiredRoles.get(method); if (methodInfo != null) { currentMatch.uncovered = false; for (SecurityInformation role : methodInfo) { transport(currentMatch, role.transportGuaranteeType); currentMatch.constraints.add(new SingleConstraintMatch(role.emptyRoleSemantic, role.roles)); } } for (ExcludedMethodRoles excluded : exact.excludedMethodRoles) { if (!excluded.methods.contains(method)) { currentMatch.uncovered = false; transport(currentMatch, excluded.securityInformation.transportGuaranteeType); currentMatch.constraints.add(new SingleConstraintMatch(excluded.securityInformation.emptyRoleSemantic, excluded.securityInformation.roles)); } } } private void transport(RuntimeMatch match, TransportGuaranteeType other) { if (other.ordinal() > match.type.ordinal()) { match.type = other; } } public void logWarningsAboutUncoveredMethods() { if(!denyUncoveredHttpMethods) { logWarningsAboutUncoveredMethods(exactPathRoleInformation, "", ""); logWarningsAboutUncoveredMethods(prefixPathRoleInformation, "", "/*"); logWarningsAboutUncoveredMethods(extensionRoleInformation, "*.", ""); } } private void logWarningsAboutUncoveredMethods(Map matches, String prefix, String suffix) { //according to the spec we should be logging warnings about paths with uncovered HTTP methods for (Map.Entry entry : matches.entrySet()) { if (entry.getValue().perMethodRequiredRoles.isEmpty() && entry.getValue().excludedMethodRoles.isEmpty()) { continue; } Set missing = new HashSet<>(KNOWN_METHODS); for (String m : entry.getValue().perMethodRequiredRoles.keySet()) { missing.remove(m); } Iterator it = missing.iterator(); while (it.hasNext()) { String val = it.next(); for (ExcludedMethodRoles excluded : entry.getValue().excludedMethodRoles) { if (!excluded.methods.contains(val)) { it.remove(); break; } } } if (!missing.isEmpty()) { UndertowServletLogger.ROOT_LOGGER.unsecuredMethodsOnPath(prefix + entry.getKey() + suffix, missing); } } } public static Builder builder(final DeploymentInfo deploymentInfo) { return new Builder(deploymentInfo); } public static class Builder { private final DeploymentInfo deploymentInfo; private final PathSecurityInformation defaultPathSecurityInformation = new PathSecurityInformation(); private final Map exactPathRoleInformation = new HashMap<>(); private final Map prefixPathRoleInformation = new HashMap<>(); private final Map extensionRoleInformation = new HashMap<>(); private Builder(final DeploymentInfo deploymentInfo) { this.deploymentInfo = deploymentInfo; } public void addSecurityConstraint(final SecurityConstraint securityConstraint) { final Set roles = expandRolesAllowed(securityConstraint.getRolesAllowed()); final SecurityInformation securityInformation = new SecurityInformation(roles, securityConstraint.getTransportGuaranteeType(), securityConstraint.getEmptyRoleSemantic()); for (final WebResourceCollection webResources : securityConstraint.getWebResourceCollections()) { if (webResources.getUrlPatterns().isEmpty()) { //default that is applied to everything setupPathSecurityInformation(defaultPathSecurityInformation, securityInformation, webResources); } for (String pattern : webResources.getUrlPatterns()) { if (pattern.endsWith("/*")) { String part = pattern.substring(0, pattern.length() - 2); PathSecurityInformation info = prefixPathRoleInformation.get(part); if (info == null) { prefixPathRoleInformation.put(part, info = new PathSecurityInformation()); } setupPathSecurityInformation(info, securityInformation, webResources); } else if (pattern.startsWith("*.")) { String part = pattern.substring(2, pattern.length()); PathSecurityInformation info = extensionRoleInformation.get(part); if (info == null) { extensionRoleInformation.put(part, info = new PathSecurityInformation()); } setupPathSecurityInformation(info, securityInformation, webResources); } else { PathSecurityInformation info = exactPathRoleInformation.get(pattern); if (info == null) { exactPathRoleInformation.put(pattern, info = new PathSecurityInformation()); } setupPathSecurityInformation(info, securityInformation, webResources); } } } } private Set expandRolesAllowed(final Set rolesAllowed) { final Set roles = new HashSet<>(rolesAllowed); if (roles.contains("*")) { roles.remove("*"); roles.addAll(deploymentInfo.getSecurityRoles()); } return roles; } private void setupPathSecurityInformation(final PathSecurityInformation info, final SecurityInformation securityConstraint, final WebResourceCollection webResources) { if (webResources.getHttpMethods().isEmpty() && webResources.getHttpMethodOmissions().isEmpty()) { info.defaultRequiredRoles.add(securityConstraint); } else if (!webResources.getHttpMethods().isEmpty()) { for (String method : webResources.getHttpMethods()) { List securityInformations = info.perMethodRequiredRoles.get(method); if (securityInformations == null) { info.perMethodRequiredRoles.put(method, securityInformations = new ArrayList<>()); } securityInformations.add(securityConstraint); } } else if (!webResources.getHttpMethodOmissions().isEmpty()) { info.excludedMethodRoles.add(new ExcludedMethodRoles(webResources.getHttpMethodOmissions(), securityConstraint)); } } public SecurityPathMatches build() { return new SecurityPathMatches(deploymentInfo.isDenyUncoveredHttpMethods(), defaultPathSecurityInformation, exactPathRoleInformation, prefixPathRoleInformation, extensionRoleInformation); } } private static class PathSecurityInformation { final List defaultRequiredRoles = new ArrayList<>(); final Map> perMethodRequiredRoles = new HashMap<>(); final List excludedMethodRoles = new ArrayList<>(); } private static final class ExcludedMethodRoles { final Set methods; final SecurityInformation securityInformation; ExcludedMethodRoles(final Set methods, final SecurityInformation securityInformation) { this.methods = methods; this.securityInformation = securityInformation; } } private static final class SecurityInformation { final Set roles; final TransportGuaranteeType transportGuaranteeType; final SecurityInfo.EmptyRoleSemantic emptyRoleSemantic; private SecurityInformation(final Set roles, final TransportGuaranteeType transportGuaranteeType, final SecurityInfo.EmptyRoleSemantic emptyRoleSemantic) { this.emptyRoleSemantic = emptyRoleSemantic; this.roles = new HashSet<>(roles); this.transportGuaranteeType = transportGuaranteeType; } } private static final class RuntimeMatch { TransportGuaranteeType type = TransportGuaranteeType.NONE; final List constraints = new ArrayList<>(); boolean uncovered = true; } } ServletAuthenticationCallHandler.java000066400000000000000000000052611420065311100374310ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers.security; import io.undertow.security.api.SecurityContext; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.util.StatusCodes; /** * This is the final {@link io.undertow.server.HttpHandler} in the security chain, it's purpose is to act as a barrier at the end of the chain to * ensure authenticate is called after the mechanisms have been associated with the context and the constraint checked. * * This handler uses the Servlet {@link javax.servlet.http.HttpServletResponse#sendError(int)} method to make * sure the correct error page is displayed. * * @author Darran Lofthouse */ public class ServletAuthenticationCallHandler implements HttpHandler { private final HttpHandler next; public ServletAuthenticationCallHandler(final HttpHandler next) { this.next = next; } /** * Only allow the request through if successfully authenticated or if authentication is not required. * * @see io.undertow.server.HttpHandler#handleRequest(io.undertow.server.HttpServerExchange) */ @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { if(exchange.isInIoThread()) { exchange.dispatch(this); return; } SecurityContext context = exchange.getSecurityContext(); if (context.authenticate()) { if(!exchange.isComplete()) { next.handleRequest(exchange); } } else { if(exchange.getStatusCode() >= StatusCodes.BAD_REQUEST && !exchange.isComplete()) { ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); src.getOriginalResponse().sendError(exchange.getStatusCode()); } else { exchange.endExchange(); } } } } ServletAuthenticationConstraintHandler.java000066400000000000000000000061271420065311100407040ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers.security; import java.util.List; import io.undertow.UndertowLogger; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.security.handlers.AuthenticationConstraintHandler; import io.undertow.servlet.api.SecurityInfo.EmptyRoleSemantic; import io.undertow.servlet.api.SingleConstraintMatch; import io.undertow.servlet.handlers.ServletRequestContext; /** * A simple handler that just sets the auth type to REQUIRED after iterating each of the {@link SingleConstraintMatch} instances * and identifying if any require authentication. * * @author Stuart Douglas * @author Darran Lofthouse */ public class ServletAuthenticationConstraintHandler extends AuthenticationConstraintHandler { public ServletAuthenticationConstraintHandler(final HttpHandler next) { super(next); } @Override protected boolean isAuthenticationRequired(final HttpServerExchange exchange) { //j_security_check always requires auth if (exchange.getRelativePath().endsWith(ServletFormAuthenticationMechanism.DEFAULT_POST_LOCATION)) { return true; } List constraints = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getRequiredConstrains(); /* * Even once this is set to true the reason we allow the loop to continue is in case an empty role with a semantic of * deny is found as that will override everything. */ boolean authenticationRequired = false; for (SingleConstraintMatch constraint : constraints) { if (constraint.getRequiredRoles().isEmpty()) { if (constraint.getEmptyRoleSemantic() == EmptyRoleSemantic.DENY) { /* * For this case we return false as we know it can never be satisfied. */ return false; } else if (constraint.getEmptyRoleSemantic() == EmptyRoleSemantic.AUTHENTICATE) { authenticationRequired = true; } } else { authenticationRequired = true; } } if(authenticationRequired) { UndertowLogger.SECURITY_LOGGER.debugf("Authenticating required for request %s", exchange); } return authenticationRequired; } } ServletConfidentialityConstraintHandler.java000066400000000000000000000110141420065311100410410ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers.security; import static io.undertow.servlet.UndertowServletMessages.MESSAGES; import io.undertow.security.handlers.SinglePortConfidentialityHandler; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.api.AuthorizationManager; import io.undertow.servlet.api.ConfidentialPortManager; import io.undertow.servlet.api.TransportGuaranteeType; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.util.StatusCodes; import javax.servlet.http.HttpServletResponse; import java.net.URI; import java.net.URISyntaxException; /** * Servlet specific extension to {@link SinglePortConfidentialityHandler} * * @author Darran Lofthouse */ public class ServletConfidentialityConstraintHandler extends SinglePortConfidentialityHandler { private final ConfidentialPortManager portManager; public ServletConfidentialityConstraintHandler(final ConfidentialPortManager portManager, final HttpHandler next) { super(next, -1); this.portManager = portManager; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); final AuthorizationManager authorizationManager = servletRequestContext.getDeployment().getDeploymentInfo().getAuthorizationManager(); TransportGuaranteeType connectionGuarantee = servletRequestContext.getOriginalRequest().isSecure() ? TransportGuaranteeType.CONFIDENTIAL : TransportGuaranteeType.NONE; TransportGuaranteeType transportGuarantee = authorizationManager.transportGuarantee(connectionGuarantee, servletRequestContext.getTransportGuarenteeType(), servletRequestContext.getOriginalRequest()); servletRequestContext.setTransportGuarenteeType(transportGuarantee); if (TransportGuaranteeType.REJECTED == transportGuarantee) { HttpServletResponse response = (HttpServletResponse) servletRequestContext.getServletResponse(); response.sendError(StatusCodes.FORBIDDEN); return; } super.handleRequest(exchange); } @Override protected boolean confidentialityRequired(HttpServerExchange exchange) { TransportGuaranteeType transportGuarantee = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getTransportGuarenteeType(); // TODO - We may be able to add more flexibility here especially with authentication mechanisms such as Digest for // INTEGRAL - for now just use SSL. return (TransportGuaranteeType.CONFIDENTIAL == transportGuarantee || TransportGuaranteeType.INTEGRAL == transportGuarantee); } @Override protected URI getRedirectURI(HttpServerExchange exchange) throws URISyntaxException { int port = portManager.getConfidentialPort(exchange); if (port < 0) { throw MESSAGES.noConfidentialPortAvailable(); } return super.getRedirectURI(exchange, port); } /** * Use the HttpServerExchange supplied to check if this request is already 'sufficiently' confidential. * * Here we say 'sufficiently' as sub-classes can override this and maybe even go so far as querying the actual SSLSession. * * @param exchange - The {@link HttpServerExchange} for the request being processed. * @return true if the request is 'sufficiently' confidential, false otherwise. */ protected boolean isConfidential(final HttpServerExchange exchange) { ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if(src != null) { return src.getOriginalRequest().isSecure(); } return super.isConfidential(exchange); } } ServletFormAuthenticationMechanism.java000066400000000000000000000301731420065311100400100ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers.security; import static io.undertow.util.StatusCodes.OK; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.AuthenticationMechanismFactory; import io.undertow.security.idm.IdentityManager; import io.undertow.security.impl.FormAuthenticationMechanism; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.form.FormParserFactory; import io.undertow.server.session.Session; import io.undertow.server.session.SessionListener; import io.undertow.server.session.SessionManager; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.servlet.spec.HttpSessionImpl; import io.undertow.servlet.util.SavedRequest; import io.undertow.util.Headers; import io.undertow.util.RedirectBuilder; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import java.io.IOException; import java.security.AccessController; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; /** * Servlet handler for FORM authentication. Instead of using a redirect it * serves up error and login pages immediately using a forward * * @author Stuart Douglas */ public class ServletFormAuthenticationMechanism extends FormAuthenticationMechanism { public static final AuthenticationMechanismFactory FACTORY = new Factory(); private static final String SESSION_KEY = "io.undertow.servlet.form.auth.redirect.location"; public static final String SAVE_ORIGINAL_REQUEST = "save-original-request"; private final boolean saveOriginalRequest; // Use weak references to prevent memory leaks following undeployment private final Set seenSessionManagers = Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap())); private final String defaultPage; private final boolean overrideInitial; private static final SessionListener LISTENER = new SessionListener() { @Override public void sessionCreated(Session session, HttpServerExchange exchange) { } @Override public void sessionDestroyed(Session session, HttpServerExchange exchange, SessionDestroyedReason reason) { } @Override public void attributeAdded(Session session, String name, Object value) { } @Override public void attributeUpdated(Session session, String name, Object newValue, Object oldValue) { } @Override public void attributeRemoved(Session session, String name, Object oldValue) { } @Override public void sessionIdChanged(Session session, String oldSessionId) { String oldLocation = (String)session.getAttribute(SESSION_KEY); if(oldLocation != null) { //todo: in theory this could break if there are multiple path parameters //but this is such an edge case this is probably fine String oldPart = ";jsessionid=" + oldSessionId; if (oldLocation.contains(oldPart)) { session.setAttribute(ServletFormAuthenticationMechanism.SESSION_KEY, oldLocation.replace(oldPart, ";jsessionid=" + session.getId())); } } } }; @Deprecated public ServletFormAuthenticationMechanism(final String name, final String loginPage, final String errorPage) { super(name, loginPage, errorPage); this.saveOriginalRequest = true; this.defaultPage = null; this.overrideInitial = false; } @Deprecated public ServletFormAuthenticationMechanism(final String name, final String loginPage, final String errorPage, final String postLocation) { super(name, loginPage, errorPage, postLocation); this.saveOriginalRequest = true; this.defaultPage = null; this.overrideInitial = false; } public ServletFormAuthenticationMechanism(FormParserFactory formParserFactory, String name, String loginPage, String errorPage, String postLocation) { super(formParserFactory, name, loginPage, errorPage, postLocation); this.saveOriginalRequest = true; this.defaultPage = null; this.overrideInitial = false; } public ServletFormAuthenticationMechanism(FormParserFactory formParserFactory, String name, String loginPage, String errorPage) { super(formParserFactory, name, loginPage, errorPage); this.saveOriginalRequest = true; this.defaultPage = null; this.overrideInitial = false; } public ServletFormAuthenticationMechanism(FormParserFactory formParserFactory, String name, String loginPage, String errorPage, IdentityManager identityManager) { super(formParserFactory, name, loginPage, errorPage, identityManager); this.saveOriginalRequest = true; this.defaultPage = null; this.overrideInitial = false; } public ServletFormAuthenticationMechanism(FormParserFactory formParserFactory, String name, String loginPage, String errorPage, IdentityManager identityManager, boolean saveOriginalRequest) { super(formParserFactory, name, loginPage, errorPage, identityManager); this.saveOriginalRequest = true; this.defaultPage = null; this.overrideInitial = false; } public ServletFormAuthenticationMechanism(FormParserFactory formParserFactory, String name, String loginPage, String errorPage, String defaultPage, boolean overrideInitial, IdentityManager identityManager, boolean saveOriginalRequest) { super(formParserFactory, name, loginPage, errorPage, identityManager); this.saveOriginalRequest = saveOriginalRequest; this.defaultPage = defaultPage; this.overrideInitial = overrideInitial; } @Override protected Integer servePage(final HttpServerExchange exchange, final String location) { final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); ServletRequest req = servletRequestContext.getServletRequest(); ServletResponse resp = servletRequestContext.getServletResponse(); RequestDispatcher disp = req.getRequestDispatcher(location); //make sure the login page is never cached exchange.getResponseHeaders().add(Headers.CACHE_CONTROL, "no-cache, no-store, must-revalidate"); exchange.getResponseHeaders().add(Headers.PRAGMA, "no-cache"); exchange.getResponseHeaders().add(Headers.EXPIRES, "0"); final FormResponseWrapper respWrapper = exchange.getStatusCode() != OK && resp instanceof HttpServletResponse ? new FormResponseWrapper((HttpServletResponse) resp) : null; try { disp.forward(req, respWrapper != null ? respWrapper : resp); } catch (ServletException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } return respWrapper != null ? respWrapper.getStatus() : null; } @Override protected void storeInitialLocation(final HttpServerExchange exchange) { storeInitialLocation(exchange, null, 0); } /** * This method doesn't save content of request but instead uses data from parameter. * This should be used in case that data from request was already read and therefore it is not possible to save them. * * @param exchange * @param bytes * @param contentLength */ protected void storeInitialLocation(final HttpServerExchange exchange, byte[] bytes, int contentLength) { if(!saveOriginalRequest) { return; } final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); HttpSessionImpl httpSession = servletRequestContext.getCurrentServletContext().getSession(exchange, true); Session session; if (System.getSecurityManager() == null) { session = httpSession.getSession(); } else { session = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(httpSession)); } SessionManager manager = session.getSessionManager(); if (seenSessionManagers.add(manager)) { manager.registerSessionListener(LISTENER); } session.setAttribute(SESSION_KEY, RedirectBuilder.redirect(exchange, exchange.getRelativePath())); if(bytes == null) { SavedRequest.trySaveRequest(exchange); } else { SavedRequest.trySaveRequest(exchange, bytes, contentLength); } } @Override protected void handleRedirectBack(final HttpServerExchange exchange) { final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); HttpServletResponse resp = (HttpServletResponse) servletRequestContext.getServletResponse(); HttpSessionImpl httpSession = servletRequestContext.getCurrentServletContext().getSession(exchange, false); if (httpSession != null) { Session session; if (System.getSecurityManager() == null) { session = httpSession.getSession(); } else { session = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(httpSession)); } String path = (String) session.getAttribute(SESSION_KEY); if ((path == null || overrideInitial) && defaultPage != null) { path = defaultPage; } if (path != null) { try { resp.sendRedirect(path); } catch (IOException e) { throw new RuntimeException(e); } } } } private static class FormResponseWrapper extends HttpServletResponseWrapper { private int status = OK; private FormResponseWrapper(final HttpServletResponse wrapped) { super(wrapped); } @Override public void setStatus(int sc, String sm) { status = sc; } @Override public void setStatus(int sc) { status = sc; } @Override public int getStatus() { return status; } } public static class Factory implements AuthenticationMechanismFactory { @Deprecated public Factory(IdentityManager identityManager) {} public Factory() {} @Override public AuthenticationMechanism create(String mechanismName, IdentityManager identityManager, FormParserFactory formParserFactory, Map properties) { final String loginPage = properties.get(LOGIN_PAGE); final String errorPage = properties.get(ERROR_PAGE); final String defaultPage = properties.get(DEFAULT_PAGE); final boolean overrideInitial = properties.containsKey(OVERRIDE_INITIAL) ? Boolean.parseBoolean(properties.get(OVERRIDE_INITIAL)): false; boolean saveOriginal = true; if (properties.containsKey(SAVE_ORIGINAL_REQUEST)) { saveOriginal = Boolean.parseBoolean(properties.get(SAVE_ORIGINAL_REQUEST)); } return new ServletFormAuthenticationMechanism(formParserFactory, mechanismName, loginPage, errorPage, defaultPage, overrideInitial, identityManager, saveOriginal); } } } ServletSecurityConstraintHandler.java000066400000000000000000000051331420065311100375300ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers.security; import io.undertow.UndertowLogger; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.api.SingleConstraintMatch; import io.undertow.servlet.api.TransportGuaranteeType; import io.undertow.servlet.handlers.ServletRequestContext; import java.util.ArrayList; import java.util.List; /** * @author Stuart Douglas */ public class ServletSecurityConstraintHandler implements HttpHandler { private final SecurityPathMatches securityPathMatches; private final HttpHandler next; public ServletSecurityConstraintHandler(final SecurityPathMatches securityPathMatches, final HttpHandler next) { this.securityPathMatches = securityPathMatches; this.next = next; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final String path = exchange.getRelativePath(); SecurityPathMatch securityMatch = securityPathMatches.getSecurityInfo(path, exchange.getRequestMethod().toString()); final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); List list = servletRequestContext.getRequiredConstrains(); if (list == null) { servletRequestContext.setRequiredConstrains(list = new ArrayList<>()); } list.add(securityMatch.getMergedConstraint()); TransportGuaranteeType type = servletRequestContext.getTransportGuarenteeType(); if (type == null || type.ordinal() < securityMatch.getTransportGuaranteeType().ordinal()) { servletRequestContext.setTransportGuarenteeType(securityMatch.getTransportGuaranteeType()); } UndertowLogger.SECURITY_LOGGER.debugf("Security constraints for request %s are %s", exchange.getRequestURI(), list); next.handleRequest(exchange); } } ServletSecurityRoleHandler.java000066400000000000000000000052521420065311100363070ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers.security; import io.undertow.security.api.SecurityContext; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.api.AuthorizationManager; import io.undertow.servlet.api.SingleConstraintMatch; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.util.StatusCodes; import javax.servlet.DispatcherType; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.List; /** * Servlet role handler * * @author Stuart Douglas */ public class ServletSecurityRoleHandler implements HttpHandler { private final HttpHandler next; private final AuthorizationManager authorizationManager; public ServletSecurityRoleHandler(final HttpHandler next, AuthorizationManager authorizationManager) { this.next = next; this.authorizationManager = authorizationManager; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); ServletRequest request = servletRequestContext.getServletRequest(); if (request.getDispatcherType() == DispatcherType.REQUEST) { List constraints = servletRequestContext.getRequiredConstrains(); SecurityContext sc = exchange.getSecurityContext(); if (!authorizationManager.canAccessResource(constraints, sc.getAuthenticatedAccount(), servletRequestContext.getCurrentServlet().getManagedServlet().getServletInfo(), servletRequestContext.getOriginalRequest(), servletRequestContext.getDeployment())) { HttpServletResponse response = (HttpServletResponse) servletRequestContext.getServletResponse(); response.sendError(StatusCodes.FORBIDDEN); return; } } next.handleRequest(exchange); } } ServletSingleSignOnAuthenticationMechainism.java000066400000000000000000000022151420065311100416110ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers.security; import io.undertow.security.impl.SingleSignOnManager; /** * This class name has a type, kept for backwards compatibility reasons * * @author Stuart Douglas */ @Deprecated public class ServletSingleSignOnAuthenticationMechainism extends ServletSingleSignOnAuthenticationMechanism { public ServletSingleSignOnAuthenticationMechainism(SingleSignOnManager storage) { super(storage); } } ServletSingleSignOnAuthenticationMechanism.java000066400000000000000000000036761420065311100414540ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/handlers/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.handlers.security; import io.undertow.security.impl.SingleSignOnAuthenticationMechanism; import io.undertow.security.impl.SingleSignOnManager; import io.undertow.server.HttpServerExchange; import io.undertow.server.session.Session; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.servlet.spec.HttpSessionImpl; import java.security.AccessController; /** * Servlet version of the single sign on authentication mechanism. * * @author Stuart Douglas */ public class ServletSingleSignOnAuthenticationMechanism extends SingleSignOnAuthenticationMechanism { public ServletSingleSignOnAuthenticationMechanism(SingleSignOnManager storage) { super(storage); } @Override protected Session getSession(HttpServerExchange exchange) { ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); final HttpSessionImpl session = servletRequestContext.getCurrentServletContext().getSession(exchange, true); if(System.getSecurityManager() == null) { return session.getSession(); } else { return AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(session)); } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/predicate/000077500000000000000000000000001420065311100265145ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/predicate/DirectoryPredicate.java000066400000000000000000000065361420065311100331560ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.predicate; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributes; import io.undertow.predicate.Predicate; import io.undertow.predicate.PredicateBuilder; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.resource.Resource; import io.undertow.server.handlers.resource.ResourceManager; import io.undertow.servlet.handlers.ServletRequestContext; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * Predicate that returns true if the given location corresponds to a directory. * * @author Stuart Douglas */ public class DirectoryPredicate implements Predicate { private final ExchangeAttribute location; public DirectoryPredicate(final ExchangeAttribute location) { this.location = location; } @Override public boolean resolve(final HttpServerExchange value) { String location = this.location.readAttribute(value); ServletRequestContext src = value.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if(src == null) { return false; } ResourceManager manager = src.getDeployment().getDeploymentInfo().getResourceManager(); if(manager == null) { return false; } try { Resource resource = manager.getResource(location); if(resource == null) { return false; } return resource.isDirectory(); } catch (IOException e) { throw new RuntimeException(e); } } @Override public String toString() { return "directory( " + location.toString() + " )"; } public static class Builder implements PredicateBuilder { @Override public String name() { return "directory"; } @Override public Map> parameters() { final Map> params = new HashMap<>(); params.put("value", ExchangeAttribute.class); return params; } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return "value"; } @Override public Predicate build(final Map config) { ExchangeAttribute value = (ExchangeAttribute) config.get("value"); if(value == null) { value = ExchangeAttributes.relativePath(); } return new DirectoryPredicate(value); } } } DispatcherTypePredicate.java000066400000000000000000000062131420065311100340530ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/predicate/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.predicate; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.servlet.DispatcherType; import io.undertow.predicate.Predicate; import io.undertow.predicate.PredicateBuilder; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.handlers.ServletRequestContext; /** * Predicate that returns true if the dispatcher type matches the specified type. * * @author Stuart Douglas */ public class DispatcherTypePredicate implements Predicate { public static final DispatcherTypePredicate FORWARD = new DispatcherTypePredicate(DispatcherType.FORWARD); public static final DispatcherTypePredicate INCLUDE = new DispatcherTypePredicate(DispatcherType.INCLUDE); public static final DispatcherTypePredicate REQUEST = new DispatcherTypePredicate(DispatcherType.REQUEST); public static final DispatcherTypePredicate ASYNC = new DispatcherTypePredicate(DispatcherType.ASYNC); public static final DispatcherTypePredicate ERROR = new DispatcherTypePredicate(DispatcherType.ERROR); private final DispatcherType dispatcherType; public DispatcherTypePredicate(final DispatcherType dispatcherType) { this.dispatcherType = dispatcherType; } @Override public boolean resolve(final HttpServerExchange value) { return value.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getDispatcherType() == dispatcherType; } @Override public String toString() { return "dispatcher( " + dispatcherType.toString() + " )"; } public static class Builder implements PredicateBuilder { @Override public String name() { return "dispatcher"; } @Override public Map> parameters() { final Map> params = new HashMap<>(); params.put("value", String.class); return params; } @Override public Set requiredParameters() { final Set params = new HashSet<>(); params.add("value"); return params; } @Override public String defaultParameter() { return "value"; } @Override public Predicate build(final Map config) { String value = (String) config.get("value"); return new DispatcherTypePredicate(DispatcherType.valueOf(value)); } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/predicate/FilePredicate.java000066400000000000000000000076721420065311100320730ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.predicate; import io.undertow.attribute.ExchangeAttribute; import io.undertow.attribute.ExchangeAttributes; import io.undertow.predicate.Predicate; import io.undertow.predicate.PredicateBuilder; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.resource.Resource; import io.undertow.server.handlers.resource.ResourceManager; import io.undertow.servlet.handlers.ServletRequestContext; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * Predicate that returns true if the given location corresponds to a regular file. * * @author Stuart Douglas */ public class FilePredicate implements Predicate { private final ExchangeAttribute location; private final boolean requireContent; public FilePredicate(final ExchangeAttribute location) { this(location, false); } public FilePredicate(final ExchangeAttribute location, boolean requireContent) { this.location = location; this.requireContent = requireContent; } @Override public boolean resolve(final HttpServerExchange value) { String location = this.location.readAttribute(value); ServletRequestContext src = value.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if(src == null) { return false; } ResourceManager manager = src.getDeployment().getDeploymentInfo().getResourceManager(); if(manager == null) { return false; } try { Resource resource = manager.getResource(location); if(resource == null) { return false; } if(resource.isDirectory()) { return false; } if(requireContent){ return resource.getContentLength() != null && resource.getContentLength() > 0; } else { return true; } } catch (IOException e) { throw new RuntimeException(e); } } @Override public String toString() { return "file( " + location.toString() + " )"; } public static class Builder implements PredicateBuilder { @Override public String name() { return "file"; } @Override public Map> parameters() { final Map> params = new HashMap<>(); params.put("value", ExchangeAttribute.class); params.put("require-content", Boolean.class); return params; } @Override public Set requiredParameters() { return Collections.emptySet(); } @Override public String defaultParameter() { return "value"; } @Override public Predicate build(final Map config) { ExchangeAttribute value = (ExchangeAttribute) config.get("value"); Boolean requireContent = (Boolean)config.get("require-content"); if(value == null) { value = ExchangeAttributes.relativePath(); } return new FilePredicate(value, requireContent == null ? false : requireContent); } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/000077500000000000000000000000001420065311100255065ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/AsyncContextImpl.java000066400000000000000000000746401420065311100316300ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import java.io.IOException; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; import javax.servlet.DispatcherType; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.xnio.IoUtils; import org.xnio.XnioExecutor; import io.undertow.UndertowLogger; import io.undertow.server.Connectors; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.UndertowServletLogger; import io.undertow.servlet.UndertowServletMessages; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ExceptionHandler; import io.undertow.servlet.api.InstanceFactory; import io.undertow.servlet.api.InstanceHandle; import io.undertow.servlet.api.LoggingExceptionHandler; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletDispatcher; import io.undertow.servlet.handlers.ServletDebugPageHandler; import io.undertow.servlet.handlers.ServletPathMatch; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.util.Headers; import io.undertow.util.SameThreadExecutor; import io.undertow.util.StatusCodes; import io.undertow.util.WorkerUtils; /** * @author Stuart Douglas */ public class AsyncContextImpl implements AsyncContext { private final List asyncListeners = new CopyOnWriteArrayList<>(); private final HttpServerExchange exchange; private final ServletRequest servletRequest; private final ServletResponse servletResponse; private final TimeoutTask timeoutTask = new TimeoutTask(); private final ServletRequestContext servletRequestContext; private final boolean requestSupplied; private AsyncContextImpl previousAsyncContext; //the previous async context //todo: make default configurable private volatile long timeout = 30000; private volatile XnioExecutor.Key timeoutKey; private boolean dispatched; private boolean initialRequestDone; private Thread initiatingThread; private final Deque asyncTaskQueue = new ArrayDeque<>(); private boolean processingAsyncTask = false; private volatile boolean complete = false; private volatile boolean completedBeforeInitialRequestDone = false; public AsyncContextImpl(final HttpServerExchange exchange, final ServletRequest servletRequest, final ServletResponse servletResponse, final ServletRequestContext servletRequestContext, boolean requestSupplied, final AsyncContextImpl previousAsyncContext) { this.exchange = exchange; this.servletRequest = servletRequest; this.servletResponse = servletResponse; this.servletRequestContext = servletRequestContext; this.requestSupplied = requestSupplied; this.previousAsyncContext = previousAsyncContext; initiatingThread = Thread.currentThread(); exchange.dispatch(SameThreadExecutor.INSTANCE, new Runnable() { @Override public void run() { exchange.setDispatchExecutor(null); initialRequestDone(); } }); } public void updateTimeout() { XnioExecutor.Key key = this.timeoutKey; if (key != null) { if (!key.remove()) { return; } else { this.timeoutKey = null; } } if (timeout > 0 && !complete) { this.timeoutKey = WorkerUtils.executeAfter(exchange.getIoThread(), timeoutTask, timeout, TimeUnit.MILLISECONDS); } } @Override public ServletRequest getRequest() { return servletRequest; } @Override public ServletResponse getResponse() { return servletResponse; } @Override public boolean hasOriginalRequestAndResponse() { return servletRequest instanceof HttpServletRequestImpl && servletResponse instanceof HttpServletResponseImpl; } public boolean isInitialRequestDone() { return initialRequestDone; } @Override public void dispatch() { if (dispatched) { throw UndertowServletMessages.MESSAGES.asyncRequestAlreadyDispatched(); } final HttpServletRequestImpl requestImpl = this.servletRequestContext.getOriginalRequest(); Deployment deployment = requestImpl.getServletContext().getDeployment(); if (requestSupplied && servletRequest instanceof HttpServletRequest) { ServletContainer container = deployment.getServletContainer(); final String requestURI = ((HttpServletRequest) servletRequest).getRequestURI(); DeploymentManager context = container.getDeploymentByPath(requestURI); if (context == null) { throw UndertowServletMessages.MESSAGES.couldNotFindContextToDispatchTo(requestImpl.getOriginalContextPath()); } String toDispatch = requestURI.substring(context.getDeployment().getServletContext().getContextPath().length()); String qs = ((HttpServletRequest) servletRequest).getQueryString(); if (qs != null && !qs.isEmpty()) { toDispatch = toDispatch + "?" + qs; } dispatch(context.getDeployment().getServletContext(), toDispatch); } else { //original request ServletContainer container = deployment.getServletContainer(); DeploymentManager context = container.getDeploymentByPath(requestImpl.getOriginalContextPath()); if (context == null) { //this should never happen throw UndertowServletMessages.MESSAGES.couldNotFindContextToDispatchTo(requestImpl.getOriginalContextPath()); } //UNDERTOW-1591 use original, decoded value to dispatch, this should match: ServletInitialHandler.handleRequest String toDispatch = requestImpl.getExchange().getRelativePath(); String qs = requestImpl.getOriginalQueryString(); if (qs != null && !qs.isEmpty()) { toDispatch = toDispatch + "?" + qs; } dispatch(context.getDeployment().getServletContext(), toDispatch); } } private void dispatchAsyncRequest(final ServletDispatcher servletDispatcher, final ServletPathMatch pathInfo, final HttpServerExchange exchange) { doDispatch(new Runnable() { @Override public void run() { Connectors.executeRootHandler(new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); src.setServletRequest(servletRequest); src.setServletResponse(servletResponse); servletDispatcher.dispatchToPath(exchange, pathInfo, DispatcherType.ASYNC); } }, exchange); } }); } @Override public void dispatch(final String path) { dispatch(servletRequest.getServletContext(), path); } @Override public void dispatch(final ServletContext context, final String path) { if (dispatched) { throw UndertowServletMessages.MESSAGES.asyncRequestAlreadyDispatched(); } HttpServletRequestImpl requestImpl = servletRequestContext.getOriginalRequest(); HttpServletResponseImpl responseImpl = servletRequestContext.getOriginalResponse(); final HttpServerExchange exchange = requestImpl.getExchange(); exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).setDispatcherType(DispatcherType.ASYNC); requestImpl.setAttribute(ASYNC_REQUEST_URI, requestImpl.getOriginalRequestURI()); requestImpl.setAttribute(ASYNC_CONTEXT_PATH, requestImpl.getOriginalContextPath()); requestImpl.setAttribute(ASYNC_SERVLET_PATH, requestImpl.getOriginalServletPath()); requestImpl.setAttribute(ASYNC_QUERY_STRING, requestImpl.getOriginalQueryString()); String newQueryString = ""; int qsPos = path.indexOf("?"); String newServletPath = path; if (qsPos != -1) { newQueryString = newServletPath.substring(qsPos + 1); newServletPath = newServletPath.substring(0, qsPos); } String newRequestUri = context.getContextPath() + newServletPath; //todo: a more efficient impl Map> newQueryParameters = new HashMap<>(); for (String part : newQueryString.split("&")) { String name = part; String value = ""; int equals = part.indexOf('='); if (equals != -1) { name = part.substring(0, equals); value = part.substring(equals + 1); } Deque queue = newQueryParameters.get(name); if (queue == null) { newQueryParameters.put(name, queue = new ArrayDeque<>(1)); } queue.add(value); } requestImpl.setQueryParameters(newQueryParameters); requestImpl.getExchange().setRelativePath(newServletPath); requestImpl.getExchange().setQueryString(newQueryString); requestImpl.getExchange().setRequestPath(newRequestUri); requestImpl.getExchange().setRequestURI(newRequestUri); requestImpl.setServletContext((ServletContextImpl) context); responseImpl.setServletContext((ServletContextImpl) context); Deployment deployment = requestImpl.getServletContext().getDeployment(); ServletPathMatch info = deployment.getServletPaths().getServletHandlerByPath(newServletPath); requestImpl.getExchange().getAttachment(ServletRequestContext.ATTACHMENT_KEY).setServletPathMatch(info); dispatchAsyncRequest(deployment.getServletDispatcher(), info, exchange); } @Override public synchronized void complete() { if (complete) { UndertowLogger.REQUEST_LOGGER.trace("Ignoring call to AsyncContext.complete() as it has already been called"); return; } complete = true; if (timeoutKey != null) { timeoutKey.remove(); timeoutKey = null; } if (!dispatched) { completeInternal(false); } else { onAsyncComplete(); } if (previousAsyncContext != null) { previousAsyncContext.complete(); } } public synchronized void completeInternal(boolean forceComplete) { Thread currentThread = Thread.currentThread(); if (!forceComplete && !initialRequestDone && currentThread == initiatingThread) { completedBeforeInitialRequestDone = true; if (dispatched) { throw UndertowServletMessages.MESSAGES.asyncRequestAlreadyDispatched(); } } else { servletRequestContext.getOriginalRequest().asyncRequestDispatched(); if (forceComplete || currentThread == exchange.getIoThread()) { //the thread safety semantics here are a bit weird. //basically if we are doing async IO we can't do a dispatch here, as then the IO thread can be racing //with the dispatch thread. //at all other times the dispatch is desirable onAsyncCompleteAndRespond(); } else { servletRequestContext.getOriginalRequest().asyncRequestDispatched(); doDispatch(new Runnable() { @Override public void run() { onAsyncCompleteAndRespond(); } }); } } } @Override public void start(final Runnable run) { Executor executor = asyncExecutor(); executor.execute(new Runnable() { @Override public void run() { servletRequestContext.getCurrentServletContext().invokeRunnable(exchange, run); } }); } private Executor asyncExecutor() { Executor executor = servletRequestContext.getDeployment().getAsyncExecutor(); if (executor == null) { executor = servletRequestContext.getDeployment().getExecutor(); } if (executor == null) { executor = exchange.getConnection().getWorker(); } return executor; } @Override public void addListener(final AsyncListener listener) { asyncListeners.add(new BoundAsyncListener(listener, servletRequest, servletResponse)); } @Override public void addListener(final AsyncListener listener, final ServletRequest servletRequest, final ServletResponse servletResponse) { asyncListeners.add(new BoundAsyncListener(listener, servletRequest, servletResponse)); } public boolean isDispatched() { return dispatched; } public boolean isCompletedBeforeInitialRequestDone() { return completedBeforeInitialRequestDone; } @Override public T createListener(final Class clazz) throws ServletException { try { InstanceFactory factory = ((ServletContextImpl) this.servletRequest.getServletContext()).getDeployment().getDeploymentInfo().getClassIntrospecter().createInstanceFactory(clazz); final InstanceHandle instance = factory.createInstance(); exchange.addExchangeCompleteListener(new ExchangeCompletionListener() { @Override public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { try { instance.release(); } finally { nextListener.proceed(); } } }); return instance.getInstance(); } catch (Exception e) { throw new ServletException(e); } } @Override public synchronized void setTimeout(final long timeout) { if (initialRequestDone) { throw UndertowServletMessages.MESSAGES.asyncRequestAlreadyReturnedToContainer(); } this.timeout = timeout; } @Override public long getTimeout() { return timeout; } public void handleError(final Throwable error) { dispatched = false; //we reset the dispatched state onAsyncError(error); if (!dispatched) { if(!exchange.isResponseStarted()) { exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); exchange.getResponseHeaders().clear(); } servletRequest.setAttribute(RequestDispatcher.ERROR_EXCEPTION, error); if(!exchange.isResponseStarted()) { try { boolean errorPage = servletRequestContext.displayStackTraces(); if (errorPage) { ServletDebugPageHandler.handleRequest(exchange, servletRequestContext, error); } else { if (servletResponse instanceof HttpServletResponse) { ((HttpServletResponse) servletResponse).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } else { servletRequestContext.getOriginalResponse().sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); } catch (Throwable t) { UndertowLogger.REQUEST_IO_LOGGER.handleUnexpectedFailure(t); } } else if (error instanceof IOException) { UndertowLogger.REQUEST_IO_LOGGER.ioException((IOException) error); } else { ExceptionHandler exceptionHandler = servletRequestContext.getDeployment().getDeploymentInfo().getExceptionHandler(); if(exceptionHandler == null) { exceptionHandler = LoggingExceptionHandler.DEFAULT; } boolean handled = exceptionHandler.handleThrowable(exchange, getRequest(), getResponse(), error); if(!handled) { exchange.endExchange(); } } if (!dispatched) { complete(); } } } /** * Called by the container when the initial request is finished. * If this request has a dispatch or complete call pending then * this will be started. */ public synchronized void initialRequestDone() { initialRequestDone = true; if (previousAsyncContext != null) { previousAsyncContext.onAsyncStart(this); previousAsyncContext = null; } if (!processingAsyncTask) { processAsyncTask(); } initiatingThread = null; } public synchronized void initialRequestFailed() { initialRequestDone = true; } private synchronized void doDispatch(final Runnable runnable) { if (dispatched) { throw UndertowServletMessages.MESSAGES.asyncRequestAlreadyDispatched(); } dispatched = true; final HttpServletRequestImpl request = servletRequestContext.getOriginalRequest(); addAsyncTask(new Runnable() { @Override public void run() { request.asyncRequestDispatched(); runnable.run(); } }); if (timeoutKey != null) { timeoutKey.remove(); } } public void handleCompletedBeforeInitialRequestDone() { assert completedBeforeInitialRequestDone; completeInternal(true); dispatched = true; } private final class TimeoutTask implements Runnable { @Override public void run() { synchronized (AsyncContextImpl.this) { if (!dispatched && !complete) { addAsyncTask(new Runnable() { @Override public void run() { final boolean setupRequired = SecurityActions.currentServletRequestContext() == null; UndertowServletLogger.REQUEST_LOGGER.debug("Async request timed out"); servletRequestContext.getCurrentServletContext().invokeRunnable(servletRequestContext.getExchange(), new Runnable() { @Override public void run() { //now run request listeners setupRequestContext(setupRequired); try { onAsyncTimeout(); if (!dispatched) { if (!getResponse().isCommitted()) { //close the connection on timeout exchange.setPersistent(false); exchange.getResponseHeaders().put(Headers.CONNECTION, Headers.CLOSE.toString()); Connectors.executeRootHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { //servlet try { if (servletResponse instanceof HttpServletResponse) { ((HttpServletResponse) servletResponse).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } else { servletRequestContext.getOriginalResponse().sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); } catch (Throwable t) { UndertowLogger.REQUEST_IO_LOGGER.handleUnexpectedFailure(t); } } }, exchange); } else { //not much we can do, just break the connection IoUtils.safeClose(exchange.getConnection()); } if (!dispatched) { complete(); } } } finally { tearDownRequestContext(setupRequired); } } }); } }); } } } } private synchronized void processAsyncTask() { if (!initialRequestDone) { return; } updateTimeout(); final Runnable task = asyncTaskQueue.poll(); if (task != null) { processingAsyncTask = true; asyncExecutor().execute(new TaskDispatchRunnable(task)); } else { processingAsyncTask = false; } } /** * Adds a task to be run to the async context. These tasks are run one at a time, * after the initial request is finished. If the request is dispatched before the initial * request is complete then these tasks will not be run *

    * This method is intended to be used to queue read and write tasks for async streams, * to make sure that multiple threads do not end up working on the same exchange at once * * @param runnable The runnable */ public synchronized void addAsyncTask(final Runnable runnable) { asyncTaskQueue.add(runnable); if (!processingAsyncTask) { processAsyncTask(); } } private class TaskDispatchRunnable implements Runnable { private final Runnable task; private TaskDispatchRunnable(final Runnable task) { this.task = task; } @Override public void run() { try { task.run(); } finally { processAsyncTask(); } } } private void onAsyncCompleteAndRespond() { final HttpServletResponseImpl response = servletRequestContext.getOriginalResponse(); try { onAsyncComplete(); } catch (RuntimeException e) { //handleError(e); /*try { response.sendError(StatusCodes.INTERNAL_SERVER_ERROR, e.getMessage()); } catch (IOException ioException) { UndertowLogger.REQUEST_IO_LOGGER.ioException(ioException); response.responseDone(); } throw e;*/ UndertowServletLogger.REQUEST_LOGGER.failureDispatchingAsyncEvent(e); } finally { response.responseDone(); try { servletRequestContext.getOriginalRequest().closeAndDrainRequest(); } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); } catch (Throwable t) { UndertowLogger.REQUEST_IO_LOGGER.handleUnexpectedFailure(t); } } } private void onAsyncComplete() { final boolean setupRequired = SecurityActions.currentServletRequestContext() == null; servletRequestContext.getCurrentServletContext().invokeRunnable(servletRequestContext.getExchange(), new Runnable() { @Override public void run() { //now run request listeners setupRequestContext(setupRequired); try { for (final BoundAsyncListener listener : asyncListeners) { AsyncEvent event = new AsyncEvent(AsyncContextImpl.this, listener.servletRequest, listener.servletResponse); try { listener.asyncListener.onComplete(event); } catch (IOException e) { UndertowServletLogger.REQUEST_LOGGER.ioExceptionDispatchingAsyncEvent(e); } catch (Throwable t) { UndertowServletLogger.REQUEST_LOGGER.failureDispatchingAsyncEvent(t); } } } finally { tearDownRequestContext(setupRequired); } } }); } private void onAsyncTimeout() { for (final BoundAsyncListener listener : asyncListeners) { AsyncEvent event = new AsyncEvent(this, listener.servletRequest, listener.servletResponse); try { listener.asyncListener.onTimeout(event); } catch (IOException e) { UndertowServletLogger.REQUEST_LOGGER.ioExceptionDispatchingAsyncEvent(e); } catch (Throwable t) { UndertowServletLogger.REQUEST_LOGGER.failureDispatchingAsyncEvent(t); } } } private void onAsyncStart(final AsyncContext newAsyncContext) { final boolean setupRequired = SecurityActions.currentServletRequestContext() == null; servletRequestContext.getCurrentServletContext().invokeRunnable(servletRequestContext.getExchange(), new Runnable() { @Override public void run() { //now run request listeners setupRequestContext(setupRequired); try { for (final BoundAsyncListener listener : asyncListeners) { //make sure we use the new async context AsyncEvent event = new AsyncEvent(newAsyncContext, listener.servletRequest, listener.servletResponse); try { listener.asyncListener.onStartAsync(event); } catch (IOException e) { UndertowServletLogger.REQUEST_LOGGER.ioExceptionDispatchingAsyncEvent(e); } catch (Throwable t) { UndertowServletLogger.REQUEST_LOGGER.failureDispatchingAsyncEvent(t); } } } finally { tearDownRequestContext(setupRequired); } } }); } private void onAsyncError(final Throwable t) { final boolean setupRequired = SecurityActions.currentServletRequestContext() == null; servletRequestContext.getCurrentServletContext().invokeRunnable(servletRequestContext.getExchange(), new Runnable() { @Override public void run() { setupRequestContext(setupRequired); try { for (final BoundAsyncListener listener : asyncListeners) { AsyncEvent event = new AsyncEvent(AsyncContextImpl.this, listener.servletRequest, listener.servletResponse, t); try { listener.asyncListener.onError(event); } catch (IOException e) { UndertowServletLogger.REQUEST_LOGGER.ioExceptionDispatchingAsyncEvent(e); } catch (Throwable t) { UndertowServletLogger.REQUEST_LOGGER.failureDispatchingAsyncEvent(t); } } } finally { tearDownRequestContext(setupRequired); } } }); } private void setupRequestContext(final boolean setupRequired) { if (setupRequired) { servletRequestContext.getDeployment().getApplicationListeners().requestInitialized(servletRequest); SecurityActions.setCurrentRequestContext(servletRequestContext); } } private void tearDownRequestContext(final boolean setupRequired) { if (setupRequired) { servletRequestContext.getDeployment().getApplicationListeners().requestDestroyed(servletRequest); SecurityActions.clearCurrentServletAttachments(); } } private static final class BoundAsyncListener { final AsyncListener asyncListener; final ServletRequest servletRequest; final ServletResponse servletResponse; private BoundAsyncListener(final AsyncListener asyncListener, final ServletRequest servletRequest, final ServletResponse servletResponse) { this.asyncListener = asyncListener; this.servletRequest = servletRequest; this.servletResponse = servletResponse; } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/ContentTypeInfo.java000066400000000000000000000024151420065311100314430ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; /** * @author Stuart Douglas */ class ContentTypeInfo { private final String header; private final String charset; private final String contentType; ContentTypeInfo(String header, String charset, String contentType) { this.header = header; this.charset = charset; this.contentType = contentType; } public String getHeader() { return header; } public String getCharset() { return charset; } public String getContentType() { return contentType; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/FilterConfigImpl.java000066400000000000000000000034231420065311100315500ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import java.util.Enumeration; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.util.IteratorEnumeration; /** * @author Stuart Douglas */ public class FilterConfigImpl implements FilterConfig { private final FilterInfo filterInfo; private final ServletContext servletContext; public FilterConfigImpl(final FilterInfo filterInfo, final ServletContext servletContext) { this.filterInfo = filterInfo; this.servletContext = servletContext; } @Override public String getFilterName() { return filterInfo.getName(); } @Override public ServletContext getServletContext() { return servletContext; } @Override public String getInitParameter(final String name) { return filterInfo.getInitParams().get(name); } @Override public Enumeration getInitParameterNames() { return new IteratorEnumeration<>(filterInfo.getInitParams().keySet().iterator()); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/FilterRegistrationImpl.java000066400000000000000000000110021420065311100330050ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import java.util.ArrayList; import java.util.Collection; import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.DispatcherType; import javax.servlet.FilterRegistration; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.FilterMappingInfo; /** * @author Stuart Douglas */ public class FilterRegistrationImpl implements FilterRegistration, FilterRegistration.Dynamic { private final FilterInfo filterInfo; private final Deployment deployment; private final ServletContextImpl servletContext; public FilterRegistrationImpl(final FilterInfo filterInfo, final Deployment deployment, ServletContextImpl servletContext) { this.filterInfo = filterInfo; this.deployment = deployment; this.servletContext = servletContext; } @Override public void addMappingForServletNames(final EnumSet dispatcherTypes, final boolean isMatchAfter, final String... servletNames) { servletContext.addMappingForServletNames(filterInfo, dispatcherTypes, isMatchAfter, servletNames); } @Override public Collection getServletNameMappings() { DeploymentInfo deploymentInfo = deployment.getDeploymentInfo(); final List ret = new ArrayList<>(); for(final FilterMappingInfo mapping : deploymentInfo.getFilterMappings()) { if(mapping.getMappingType() == FilterMappingInfo.MappingType.SERVLET) { if(mapping.getFilterName().equals(filterInfo.getName())) { ret.add(mapping.getMapping()); } } } return ret; } @Override public void addMappingForUrlPatterns(final EnumSet dispatcherTypes, final boolean isMatchAfter, final String... urlPatterns) { servletContext.addMappingForUrlPatterns(filterInfo, dispatcherTypes, isMatchAfter, urlPatterns); } @Override public Collection getUrlPatternMappings() { DeploymentInfo deploymentInfo = deployment.getDeploymentInfo(); final List ret = new ArrayList<>(); for(final FilterMappingInfo mapping : deploymentInfo.getFilterMappings()) { if(mapping.getMappingType() == FilterMappingInfo.MappingType.URL) { if(mapping.getFilterName().equals(filterInfo.getName())) { ret.add(mapping.getMapping()); } } } return ret; } @Override public String getName() { return filterInfo.getName(); } @Override public String getClassName() { return filterInfo.getFilterClass().getName(); } @Override public boolean setInitParameter(final String name, final String value) { if(filterInfo.getInitParams().containsKey(name)) { return false; } filterInfo.addInitParam(name, value); return true; } @Override public String getInitParameter(final String name) { return filterInfo.getInitParams().get(name); } @Override public Set setInitParameters(final Map initParameters) { final Set ret = new HashSet<>(); for(Map.Entry entry : initParameters.entrySet()) { if(!setInitParameter(entry.getKey(), entry.getValue())) { ret.add(entry.getKey()); } } return ret; } @Override public Map getInitParameters() { return filterInfo.getInitParams(); } @Override public void setAsyncSupported(final boolean isAsyncSupported) { filterInfo.setAsyncSupported(isAsyncSupported); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/HttpServletRequestImpl.java000066400000000000000000001271041420065311100330350ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import io.undertow.security.api.SecurityContext; import io.undertow.security.idm.Account; import io.undertow.server.HttpServerExchange; import io.undertow.server.RequestTooBigException; import io.undertow.server.handlers.form.FormData; import io.undertow.server.handlers.form.FormDataParser; import io.undertow.server.handlers.form.MultiPartParserDefinition; import io.undertow.server.protocol.http.HttpAttachments; import io.undertow.server.session.Session; import io.undertow.server.session.SessionConfig; import io.undertow.servlet.UndertowServletMessages; import io.undertow.servlet.api.AuthorizationManager; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.api.InstanceFactory; import io.undertow.servlet.api.InstanceHandle; import io.undertow.servlet.core.ManagedServlet; import io.undertow.servlet.core.ServletUpgradeListener; import io.undertow.servlet.handlers.ServletChain; import io.undertow.servlet.handlers.ServletPathMatch; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.servlet.util.EmptyEnumeration; import io.undertow.servlet.util.IteratorEnumeration; import io.undertow.util.AttachmentKey; import io.undertow.util.DateUtils; import io.undertow.util.HeaderMap; import io.undertow.util.HeaderValues; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.LocaleUtils; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.charset.UnsupportedCharsetException; import java.security.AccessController; import java.security.Principal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Deque; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import javax.servlet.AsyncContext; import javax.servlet.DispatcherType; import javax.servlet.MultipartConfigElement; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletRequestWrapper; import javax.servlet.ServletResponse; import javax.servlet.ServletResponseWrapper; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletMapping; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpUpgradeHandler; import javax.servlet.http.Part; import javax.servlet.http.PushBuilder; /** * The http servlet request implementation. This class is not thread safe * * @author Stuart Douglas * @author Richard Opalka */ public final class HttpServletRequestImpl implements HttpServletRequest { @Deprecated public static final AttachmentKey SECURE_REQUEST = HttpServerExchange.SECURE_REQUEST; static final AttachmentKey REQUESTED_SESSION_ID_SET = AttachmentKey.create(Boolean.class); static final AttachmentKey REQUESTED_SESSION_ID = AttachmentKey.create(String.class); private final HttpServerExchange exchange; private final ServletContextImpl originalServletContext; private ServletContextImpl servletContext; private Map attributes = null; private ServletInputStream servletInputStream; private BufferedReader reader; private Cookie[] cookies; private List parts = null; private volatile boolean asyncStarted = false; private volatile AsyncContextImpl asyncContext = null; private Map> queryParameters; private FormData parsedFormData; private RuntimeException formParsingException; private Charset characterEncoding; private boolean readStarted; private SessionConfig.SessionCookieSource sessionCookieSource; public HttpServletRequestImpl(final HttpServerExchange exchange, final ServletContextImpl servletContext) { this.exchange = exchange; this.servletContext = servletContext; this.originalServletContext = servletContext; } public HttpServerExchange getExchange() { return exchange; } @Override public String getAuthType() { SecurityContext securityContext = exchange.getSecurityContext(); return securityContext != null ? securityContext.getMechanismName() : null; } @Override public Cookie[] getCookies() { if (cookies == null) { Iterable cookies = exchange.requestCookies(); int count = 0; for (io.undertow.server.handlers.Cookie cookie : cookies) { count++; } if (count == 0) { return null; } Cookie[] value = new Cookie[count]; int i = 0; for (io.undertow.server.handlers.Cookie cookie : cookies) { try { Cookie c = new Cookie(cookie.getName(), cookie.getValue()); if (cookie.getDomain() != null) { c.setDomain(cookie.getDomain()); } c.setHttpOnly(cookie.isHttpOnly()); if (cookie.getMaxAge() != null) { c.setMaxAge(cookie.getMaxAge()); } if (cookie.getPath() != null) { c.setPath(cookie.getPath()); } c.setSecure(cookie.isSecure()); c.setVersion(cookie.getVersion()); value[i++] = c; } catch (IllegalArgumentException e) { // Ignore bad cookie } } if( i < count ) { Cookie[] shrunkCookies = new Cookie[i]; System.arraycopy(value, 0, shrunkCookies, 0, i); value = shrunkCookies; } this.cookies = value; } return cookies; } @Override public long getDateHeader(final String name) { String header = exchange.getRequestHeaders().getFirst(name); if (header == null) { return -1; } Date date = DateUtils.parseDate(header); if (date == null) { throw UndertowServletMessages.MESSAGES.headerCannotBeConvertedToDate(header); } return date.getTime(); } @Override public String getHeader(final String name) { HeaderMap headers = exchange.getRequestHeaders(); return headers.getFirst(name); } public String getHeader(final HttpString name) { HeaderMap headers = exchange.getRequestHeaders(); return headers.getFirst(name); } @Override public Enumeration getHeaders(final String name) { List headers = exchange.getRequestHeaders().get(name); if (headers == null) { return EmptyEnumeration.instance(); } return new IteratorEnumeration<>(headers.iterator()); } @Override public Enumeration getHeaderNames() { final Set headers = new HashSet<>(); for (final HttpString i : exchange.getRequestHeaders().getHeaderNames()) { headers.add(i.toString()); } return new IteratorEnumeration<>(headers.iterator()); } @Override public HttpServletMapping getHttpServletMapping() { ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); ServletPathMatch match = src.getOriginalServletPathMatch(); final DispatcherType dispatcherType = getDispatcherType(); //UNDERTOW-1899 - ERROR is essentially forward operation if(dispatcherType == DispatcherType.FORWARD || dispatcherType == DispatcherType.ERROR) { match = src.getServletPathMatch(); } String matchValue; switch (match.getMappingMatch()) { case EXACT: matchValue = match.getMatched(); if(matchValue.startsWith("/")) { matchValue = matchValue.substring(1); } break; case DEFAULT: case CONTEXT_ROOT: matchValue = ""; break; case PATH: matchValue = match.getRemaining(); if (matchValue == null) { matchValue = ""; } else if (matchValue.startsWith("/")) { matchValue = matchValue.substring(1); } break; case EXTENSION: String matched = match.getMatched(); String matchString = match.getMatchString(); int startIndex = matched.startsWith("/") ? 1 : 0; int endIndex = matched.length() - matchString.length() + 1; matchValue = matched.substring(startIndex, endIndex); break; default: matchValue = match.getRemaining(); } return new MappingImpl(matchValue, match.getMatchString(), match.getMappingMatch(), match.getServletChain().getManagedServlet().getServletInfo().getName()); } @Override public int getIntHeader(final String name) { String header = getHeader(name); if (header == null) { return -1; } return Integer.parseInt(header); } @Override public String getMethod() { return exchange.getRequestMethod().toString(); } @Override public String getPathInfo() { ServletPathMatch match = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getServletPathMatch(); if (match != null) { return match.getRemaining(); } return null; } @Override public String getPathTranslated() { return getRealPath(getPathInfo()); } @Override public String getContextPath() { return servletContext.getContextPath(); } @Override public String getQueryString() { return exchange.getQueryString().isEmpty() ? null : exchange.getQueryString(); } @Override public String getRemoteUser() { Principal userPrincipal = getUserPrincipal(); return userPrincipal != null ? userPrincipal.getName() : null; } @Override public boolean isUserInRole(final String role) { if (role == null) { return false; } //according to the servlet spec this aways returns false if (role.equals("*")) { return false; } SecurityContext sc = exchange.getSecurityContext(); Account account = sc != null ? sc.getAuthenticatedAccount() : null; if (account == null) { return false; } ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (role.equals("**")) { Set roles = servletRequestContext.getDeployment().getDeploymentInfo().getSecurityRoles(); if (!roles.contains("**")) { return true; } } final ServletChain servlet = servletRequestContext.getCurrentServlet(); final Deployment deployment = servletContext.getDeployment(); final AuthorizationManager authorizationManager = deployment.getDeploymentInfo().getAuthorizationManager(); return authorizationManager.isUserInRole(role, account, servlet.getManagedServlet().getServletInfo(), this, deployment); } @Override public Principal getUserPrincipal() { SecurityContext securityContext = exchange.getSecurityContext(); Principal result = null; Account account = null; if (securityContext != null && (account = securityContext.getAuthenticatedAccount()) != null) { result = account.getPrincipal(); } return result; } @Override public String getRequestedSessionId() { Boolean isRequestedSessionIdSaved = exchange.getAttachment(REQUESTED_SESSION_ID_SET); if (isRequestedSessionIdSaved != null && isRequestedSessionIdSaved) { return exchange.getAttachment(REQUESTED_SESSION_ID); } SessionConfig config = originalServletContext.getSessionConfig(); if(config instanceof ServletContextImpl.ServletContextSessionConfig) { return ((ServletContextImpl.ServletContextSessionConfig)config).getDelegate().findSessionId(exchange); } return config.findSessionId(exchange); } @Override public String changeSessionId() { HttpSessionImpl session = servletContext.getSession(originalServletContext, exchange, false); if (session == null) { throw UndertowServletMessages.MESSAGES.noSession(); } String oldId = session.getId(); Session underlyingSession; if(System.getSecurityManager() == null) { underlyingSession = session.getSession(); } else { underlyingSession = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(session)); } String newId = underlyingSession.changeSessionId(exchange, originalServletContext.getSessionConfig()); servletContext.getDeployment().getApplicationListeners().httpSessionIdChanged(session, oldId); return newId; } @Override public String getRequestURI() { //we need the non-decoded string, which means we need to use exchange.getRequestURI() if(exchange.isHostIncludedInRequestURI()) { //we need to strip out the host part String uri = exchange.getRequestURI(); int slashes =0; for(int i = 0; i < uri.length(); ++i) { if(uri.charAt(i) == '/') { if(++slashes == 3) { return uri.substring(i); } } } return "/"; } else { return exchange.getRequestURI(); } } @Override public StringBuffer getRequestURL() { return new StringBuffer(exchange.getRequestURL()); } @Override public String getServletPath() { ServletPathMatch match = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getServletPathMatch(); if (match != null) { return match.getMatched(); } return ""; } @Override public HttpSession getSession(final boolean create) { return servletContext.getSession(originalServletContext, exchange, create); } @Override public HttpSession getSession() { return getSession(true); } @Override public boolean isRequestedSessionIdValid() { HttpSessionImpl session = servletContext.getSession(originalServletContext, exchange, false); if(session == null) { return false; } if(session.isInvalid()) { return false; } return session.getId().equals(getRequestedSessionId()); } @Override public boolean isRequestedSessionIdFromCookie() { return sessionCookieSource() == SessionConfig.SessionCookieSource.COOKIE; } @Override public boolean isRequestedSessionIdFromURL() { return sessionCookieSource() == SessionConfig.SessionCookieSource.URL; } @Override public boolean isRequestedSessionIdFromUrl() { return isRequestedSessionIdFromURL(); } @Override public boolean authenticate(final HttpServletResponse response) throws IOException, ServletException { if (response.isCommitted()) { throw UndertowServletMessages.MESSAGES.responseAlreadyCommited(); } SecurityContext sc = exchange.getSecurityContext(); if (sc == null) { throw UndertowServletMessages.MESSAGES.noSecurityContextAvailable(); } sc.setAuthenticationRequired(); // TODO: this will set the status code and headers without going through any potential // wrappers, is this a problem? if (sc.authenticate()) { if (sc.isAuthenticated()) { return true; } else { throw UndertowServletMessages.MESSAGES.authenticationFailed(); } } else { if(!exchange.isResponseStarted() && exchange.getStatusCode() == 200) { throw UndertowServletMessages.MESSAGES.authenticationFailed(); } else { return false; } } } @Override public void login(final String username, final String password) throws ServletException { if (username == null || password == null) { throw UndertowServletMessages.MESSAGES.loginFailed(); } SecurityContext sc = exchange.getSecurityContext(); if (sc == null) { throw UndertowServletMessages.MESSAGES.noSecurityContextAvailable(); } else if (sc.isAuthenticated()) { throw UndertowServletMessages.MESSAGES.userAlreadyLoggedIn(); } boolean login = false; try { login = sc.login(username, password); } catch (SecurityException se) { if (se.getCause() instanceof ServletException) throw (ServletException) se.getCause(); throw new ServletException(se); } if (!login) { throw UndertowServletMessages.MESSAGES.loginFailed(); } } @Override public void logout() throws ServletException { SecurityContext sc = exchange.getSecurityContext(); if (sc == null) { throw UndertowServletMessages.MESSAGES.noSecurityContextAvailable(); } sc.logout(); if(servletContext.getDeployment().getDeploymentInfo().isInvalidateSessionOnLogout()) { HttpSession session = getSession(false); if(session != null) { session.invalidate(); } } } @Override public Collection getParts() throws IOException, ServletException { verifyMultipartServlet(); if (parts == null) { loadParts(); } return parts; } private void verifyMultipartServlet() { ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); MultipartConfigElement multipart = src.getServletPathMatch().getServletChain().getManagedServlet().getMultipartConfig(); if(multipart == null) { throw UndertowServletMessages.MESSAGES.multipartConfigNotPresent(); } } @Override public Part getPart(final String name) throws IOException, ServletException { verifyMultipartServlet(); if (parts == null) { loadParts(); } for (Part part : parts) { if (part.getName().equals(name)) { return part; } } return null; } @Override public T upgrade(final Class handlerClass) throws IOException { try { InstanceFactory factory = servletContext.getDeployment().getDeploymentInfo().getClassIntrospecter().createInstanceFactory(handlerClass); final InstanceHandle instance = factory.createInstance(); exchange.upgradeChannel(new ServletUpgradeListener<>(instance, servletContext.getDeployment(), exchange)); return instance.getInstance(); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } private void loadParts() throws IOException, ServletException { final ServletRequestContext requestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (parts == null) { final List parts = new ArrayList<>(); String mimeType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE); if (mimeType != null && mimeType.startsWith(MultiPartParserDefinition.MULTIPART_FORM_DATA)) { FormData formData = parseFormData(); if(formData != null) { for (final String namedPart : formData) { for (FormData.FormValue part : formData.get(namedPart)) { parts.add(new PartImpl(namedPart, part, requestContext.getOriginalServletPathMatch().getServletChain().getManagedServlet().getMultipartConfig(), servletContext, this)); } } } } else { throw UndertowServletMessages.MESSAGES.notAMultiPartRequest(); } this.parts = parts; } } @Override public Object getAttribute(final String name) { if (attributes == null) { return null; } return attributes.get(name); } @Override public Enumeration getAttributeNames() { if (attributes == null) { return EmptyEnumeration.instance(); } return new IteratorEnumeration<>(attributes.keySet().iterator()); } @Override public String getCharacterEncoding() { if (characterEncoding != null) { return characterEncoding.name(); } String characterEncodingFromHeader = getCharacterEncodingFromHeader(); if (characterEncodingFromHeader != null) { return characterEncodingFromHeader; } // first check, web-app context level default request encoding if (servletContext.getDeployment().getDeploymentInfo().getDefaultRequestEncoding() != null) { return servletContext.getDeployment().getDeploymentInfo().getDefaultRequestEncoding(); } // now check the container level default encoding if (servletContext.getDeployment().getDeploymentInfo().getDefaultEncoding() != null) { return servletContext.getDeployment().getDeploymentInfo().getDefaultEncoding(); } return null; } private String getCharacterEncodingFromHeader() { String contentType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE); if (contentType == null) { return null; } return Headers.extractQuotedValueFromHeader(contentType, "charset"); } @Override public void setCharacterEncoding(final String env) throws UnsupportedEncodingException { if (readStarted) { return; } try { characterEncoding = Charset.forName(env); final ManagedServlet originalServlet = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getOriginalServletPathMatch().getServletChain().getManagedServlet(); final FormDataParser parser = originalServlet.getFormParserFactory().createParser(exchange); if (parser != null) { parser.setCharacterEncoding(env); } } catch (UnsupportedCharsetException e) { throw new UnsupportedEncodingException(); } } @Override public int getContentLength() { long length = getContentLengthLong(); if(length > Integer.MAX_VALUE) { return -1; } return (int)length; } @Override public long getContentLengthLong() { final String contentLength = getHeader(Headers.CONTENT_LENGTH); if (contentLength == null || contentLength.isEmpty()) { return -1; } return Long.parseLong(contentLength); } @Override public String getContentType() { return getHeader(Headers.CONTENT_TYPE); } @Override public ServletInputStream getInputStream() throws IOException { if (reader != null) { throw UndertowServletMessages.MESSAGES.getReaderAlreadyCalled(); } if(servletInputStream == null) { servletInputStream = new ServletInputStreamImpl(this); } readStarted = true; return servletInputStream; } public void closeAndDrainRequest() throws IOException { if (reader != null) { reader.close(); } if (servletInputStream == null) { servletInputStream = new ServletInputStreamImpl(this); } servletInputStream.close(); } /** * Frees any resources (namely buffers) that may be associated with this request. * */ public void freeResources() throws IOException { try { if(reader != null) { reader.close(); } if(servletInputStream != null) { servletInputStream.close(); } } finally { clearAttributes(); } } @Override public String getParameter(final String name) { if(queryParameters == null) { queryParameters = exchange.getQueryParameters(); } Deque params = queryParameters.get(name); if (params == null) { final FormData parsedFormData = parseFormData(); if (parsedFormData != null) { FormData.FormValue res = parsedFormData.getFirst(name); if (res == null || res.isFileItem()) { return null; } else { return res.getValue(); } } return null; } return params.getFirst(); } @Override public Enumeration getParameterNames() { if (queryParameters == null) { queryParameters = exchange.getQueryParameters(); } final Set parameterNames = new HashSet<>(queryParameters.keySet()); final FormData parsedFormData = parseFormData(); if (parsedFormData != null) { Iterator it = parsedFormData.iterator(); while (it.hasNext()) { String name = it.next(); for(FormData.FormValue param : parsedFormData.get(name)) { if(!param.isFileItem()) { parameterNames.add(name); break; } } } } return new IteratorEnumeration<>(parameterNames.iterator()); } @Override public String[] getParameterValues(final String name) { if (queryParameters == null) { queryParameters = exchange.getQueryParameters(); } final List ret = new ArrayList<>(); Deque params = queryParameters.get(name); if (params != null) { for (String param : params) { ret.add(param); } } final FormData parsedFormData = parseFormData(); if (parsedFormData != null) { Deque res = parsedFormData.get(name); if (res != null) { for (FormData.FormValue value : res) { if(!value.isFileItem()) { ret.add(value.getValue()); } } } } if (ret.isEmpty()) { return null; } return ret.toArray(new String[ret.size()]); } @Override public Map getParameterMap() { if (queryParameters == null) { queryParameters = exchange.getQueryParameters(); } final Map> arrayMap = new HashMap<>(); for (Map.Entry> entry : queryParameters.entrySet()) { arrayMap.put(entry.getKey(), new ArrayList<>(entry.getValue())); } final FormData parsedFormData = parseFormData(); if (parsedFormData != null) { Iterator it = parsedFormData.iterator(); while (it.hasNext()) { final String name = it.next(); Deque val = parsedFormData.get(name); if (arrayMap.containsKey(name)) { ArrayList existing = arrayMap.get(name); for (final FormData.FormValue v : val) { if(!v.isFileItem()) { existing.add(v.getValue()); } } } else { final ArrayList values = new ArrayList<>(); for (final FormData.FormValue v : val) { if(!v.isFileItem()) { values.add(v.getValue()); } } if (!values.isEmpty()) { arrayMap.put(name, values); } } } } final Map ret = new HashMap<>(); for(Map.Entry> entry : arrayMap.entrySet()) { ret.put(entry.getKey(), entry.getValue().toArray(new String[entry.getValue().size()])); } return ret; } private FormData parseFormData() { if(formParsingException != null) { throw formParsingException; } if (parsedFormData == null) { if (readStarted) { return null; } final ManagedServlet originalServlet = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getCurrentServlet().getManagedServlet(); final FormDataParser parser = originalServlet.getFormParserFactory().createParser(exchange); if (parser == null) { return null; } readStarted = true; try { return parsedFormData = parser.parseBlocking(); } catch (RequestTooBigException | MultiPartParserDefinition.FileTooLargeException e) { throw formParsingException = new IllegalStateException(e); } catch (RuntimeException e) { throw formParsingException = e; } catch (IOException e) { throw formParsingException = new RuntimeException(e); } } return parsedFormData; } @Override public String getProtocol() { return exchange.getProtocol().toString(); } @Override public String getScheme() { return exchange.getRequestScheme(); } @Override public String getServerName() { return exchange.getHostName(); } @Override public int getServerPort() { return exchange.getHostPort(); } @Override public BufferedReader getReader() throws IOException { if (reader == null) { if (servletInputStream != null) { throw UndertowServletMessages.MESSAGES.getInputStreamAlreadyCalled(); } Charset charSet = null; if (this.characterEncoding != null) { charSet = this.characterEncoding; } else { final String c = getCharacterEncoding(); if (c != null) { try { charSet = Charset.forName(c); } catch (UnsupportedCharsetException e) { throw new UnsupportedEncodingException(e.getMessage()); } } } reader = new BufferedReader(charSet == null ? new InputStreamReader(exchange.getInputStream(), StandardCharsets.ISO_8859_1) : new InputStreamReader(exchange.getInputStream(), charSet)); } readStarted = true; return reader; } @Override public String getRemoteAddr() { InetSocketAddress sourceAddress = exchange.getSourceAddress(); if(sourceAddress == null) { return ""; } InetAddress address = sourceAddress.getAddress(); if(address == null) { //this is unresolved, so we just return the host name //not exactly spec, but if the name should be resolved then a PeerNameResolvingHandler should be used //and this is probably better than just returning null return sourceAddress.getHostString(); } return address.getHostAddress(); } @Override public String getRemoteHost() { InetSocketAddress sourceAddress = exchange.getSourceAddress(); if(sourceAddress == null) { return ""; } return sourceAddress.getHostString(); } @Override public void setAttribute(final String name, final Object object) { if(object == null) { removeAttribute(name); return; } if (attributes == null) { attributes = new HashMap<>(); } Object existing = attributes.put(name, object); if (existing != null) { servletContext.getDeployment().getApplicationListeners().servletRequestAttributeReplaced(this, name, existing); } else { servletContext.getDeployment().getApplicationListeners().servletRequestAttributeAdded(this, name, object); } } @Override public void removeAttribute(final String name) { if (attributes == null) { return; } Object exiting = attributes.remove(name); servletContext.getDeployment().getApplicationListeners().servletRequestAttributeRemoved(this, name, exiting); } @Override public Locale getLocale() { return getLocales().nextElement(); } @Override public Enumeration getLocales() { final List acceptLanguage = exchange.getRequestHeaders().get(Headers.ACCEPT_LANGUAGE); List ret = LocaleUtils.getLocalesFromHeader(acceptLanguage); if(ret.isEmpty()) { return new IteratorEnumeration<>(Collections.singletonList(Locale.getDefault()).iterator()); } return new IteratorEnumeration<>(ret.iterator()); } @Override public boolean isSecure() { return exchange.isSecure(); } @Override public RequestDispatcher getRequestDispatcher(final String path) { if (path == null) { return null; } String realPath; if (path.startsWith("/")) { realPath = path; } else { String current = exchange.getRelativePath(); int lastSlash = current.lastIndexOf("/"); if (lastSlash != -1) { current = current.substring(0, lastSlash + 1); } realPath = current + path; } return servletContext.getRequestDispatcher(realPath); } @Override public String getRealPath(final String path) { return servletContext.getRealPath(path); } @Override public int getRemotePort() { return exchange.getSourceAddress().getPort(); } /** * String java.net.InetAddress.getHostName() * Gets the host name for this IP address. * If this InetAddress was created with a host name, this host name will be remembered and returned; otherwise, a reverse name lookup will be performed and the result will be returned based on the system configured name lookup service. If a lookup of the name service is required, call getCanonicalHostName. * If there is a security manager, its checkConnect method is first called with the hostname and -1 as its arguments to see if the operation is allowed. If the operation is not allowed, it will return the textual representation of the IP address. * @see InetAddres#getHostName */ @Override public String getLocalName() { return exchange.getDestinationAddress().getHostName(); } @Override public String getLocalAddr() { InetSocketAddress destinationAddress = exchange.getDestinationAddress(); if (destinationAddress == null) { return ""; } InetAddress address = destinationAddress.getAddress(); if (address == null) { //this is unresolved, so we just return the host name return destinationAddress.getHostString(); } return address.getHostAddress(); } @Override public int getLocalPort() { return exchange.getDestinationAddress().getPort(); } @Override public ServletContextImpl getServletContext() { return servletContext; } @Override public AsyncContext startAsync() throws IllegalStateException { if (!isAsyncSupported()) { throw UndertowServletMessages.MESSAGES.startAsyncNotAllowed(); } else if (asyncStarted) { throw UndertowServletMessages.MESSAGES.asyncAlreadyStarted(); } asyncStarted = true; final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); return asyncContext = new AsyncContextImpl(exchange, servletRequestContext.getServletRequest(), servletRequestContext.getServletResponse(), servletRequestContext, false, asyncContext); } @Override public AsyncContext startAsync(final ServletRequest servletRequest, final ServletResponse servletResponse) throws IllegalStateException { final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (!servletContext.getDeployment().getDeploymentInfo().isAllowNonStandardWrappers()) { if (servletRequestContext.getOriginalRequest() != servletRequest) { if (!(servletRequest instanceof ServletRequestWrapper)) { throw UndertowServletMessages.MESSAGES.requestWasNotOriginalOrWrapper(servletRequest); } } if (servletRequestContext.getOriginalResponse() != servletResponse) { if (!(servletResponse instanceof ServletResponseWrapper)) { throw UndertowServletMessages.MESSAGES.responseWasNotOriginalOrWrapper(servletResponse); } } } if (!isAsyncSupported()) { throw UndertowServletMessages.MESSAGES.startAsyncNotAllowed(); } else if (asyncStarted) { throw UndertowServletMessages.MESSAGES.asyncAlreadyStarted(); } asyncStarted = true; servletRequestContext.setServletRequest(servletRequest); servletRequestContext.setServletResponse(servletResponse); return asyncContext = new AsyncContextImpl(exchange, servletRequest, servletResponse, servletRequestContext, true, asyncContext); } @Override public boolean isAsyncStarted() { return asyncStarted; } @Override public boolean isAsyncSupported() { return exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).isAsyncSupported(); } @Override public AsyncContextImpl getAsyncContext() { if (!isAsyncStarted()) { throw UndertowServletMessages.MESSAGES.asyncNotStarted(); } return asyncContext; } public AsyncContextImpl getAsyncContextInternal() { return asyncContext; } @Override public DispatcherType getDispatcherType() { return exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getDispatcherType(); } public Map> getQueryParameters() { if (queryParameters == null) { queryParameters = exchange.getQueryParameters(); } return queryParameters; } public void setQueryParameters(final Map> queryParameters) { this.queryParameters = queryParameters; } public void setServletContext(final ServletContextImpl servletContext) { this.servletContext = servletContext; } void asyncRequestDispatched() { asyncStarted = false; } public String getOriginalRequestURI() { String uri = (String) getAttribute(RequestDispatcher.FORWARD_REQUEST_URI); if(uri != null) { return uri; } uri = (String) getAttribute(AsyncContext.ASYNC_REQUEST_URI); if(uri != null) { return uri; } return getRequestURI(); } public String getOriginalServletPath() { String uri = (String) getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH); if(uri != null) { return uri; } uri = (String) getAttribute(AsyncContext.ASYNC_SERVLET_PATH); if(uri != null) { return uri; } return getServletPath(); } public String getOriginalPathInfo() { String uri = (String) getAttribute(RequestDispatcher.FORWARD_PATH_INFO); if(uri != null) { return uri; } uri = (String) getAttribute(AsyncContext.ASYNC_PATH_INFO); if(uri != null) { return uri; } return getPathInfo(); } public String getOriginalContextPath() { String uri = (String) getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH); if(uri != null) { return uri; } uri = (String) getAttribute(AsyncContext.ASYNC_CONTEXT_PATH); if(uri != null) { return uri; } return getContextPath(); } public String getOriginalQueryString() { String uri = (String) getAttribute(RequestDispatcher.FORWARD_QUERY_STRING); if(uri != null) { return uri; } uri = (String) getAttribute(AsyncContext.ASYNC_QUERY_STRING); if(uri != null) { return uri; } return getQueryString(); } private SessionConfig.SessionCookieSource sessionCookieSource() { HttpSession session = getSession(false); if(session == null) { return SessionConfig.SessionCookieSource.NONE; } if(sessionCookieSource == null) { sessionCookieSource = originalServletContext.getSessionConfig().sessionCookieSource(exchange); } return sessionCookieSource; } @Override public String toString() { return "HttpServletRequestImpl [ " + getMethod() + ' ' + getRequestURI() + " ]"; } public void clearAttributes() { if(attributes != null) { this.attributes.clear(); } } @Override public PushBuilder newPushBuilder() { if(exchange.getConnection().isPushSupported()) { return new PushBuilderImpl(this); } return null; } @Override public Map getTrailerFields() { HeaderMap trailers = exchange.getAttachment(HttpAttachments.REQUEST_TRAILERS); if(trailers == null) { return Collections.emptyMap(); } Map ret = new HashMap<>(); for(HeaderValues entry : trailers) { ret.put(entry.getHeaderName().toString().toLowerCase(Locale.ENGLISH), entry.getFirst()); } return ret; } @Override public boolean isTrailerFieldsReady() { if(exchange.isRequestComplete()) { return true; } return !exchange.getConnection().isRequestTrailerFieldsSupported(); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/HttpServletResponseImpl.java000066400000000000000000000665101420065311100332060ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import java.io.IOException; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.function.Supplier; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.SessionTrackingMode; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import io.undertow.UndertowLogger; import io.undertow.server.HttpServerExchange; import io.undertow.server.protocol.http.HttpAttachments; import io.undertow.servlet.UndertowServletMessages; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.util.CanonicalPathUtils; import io.undertow.util.DateUtils; import io.undertow.util.HeaderMap; import io.undertow.util.HeaderValues; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.Protocols; import io.undertow.util.RedirectBuilder; import io.undertow.util.StatusCodes; import static io.undertow.util.URLUtils.isAbsoluteUrl; /** * @author Stuart Douglas * @author Richard Opalka */ public final class HttpServletResponseImpl implements HttpServletResponse { private final HttpServerExchange exchange; private final ServletContextImpl originalServletContext; private volatile ServletContextImpl servletContext; private ServletOutputStreamImpl servletOutputStream; private ResponseState responseState = ResponseState.NONE; private PrintWriter writer; private Integer bufferSize; private long contentLength = -1; private boolean insideInclude = false; private Locale locale; private boolean responseDone = false; private boolean ignoredFlushPerformed = false; private boolean treatAsCommitted = false; private boolean charsetSet = false; //if a content type has been set either implicitly or implicitly private String contentType; private String charset; private Supplier> trailerSupplier; public HttpServletResponseImpl(final HttpServerExchange exchange, final ServletContextImpl servletContext) { this.exchange = exchange; this.servletContext = servletContext; this.originalServletContext = servletContext; } public HttpServerExchange getExchange() { return exchange; } @Override public void addCookie(final Cookie newCookie) { if (insideInclude) { return; } final ServletCookieAdaptor servletCookieAdaptor = new ServletCookieAdaptor(newCookie); if (newCookie.getVersion() == 0) { servletCookieAdaptor.setVersion(servletContext.getDeployment().getDeploymentInfo().getDefaultCookieVersion()); } exchange.setResponseCookie(servletCookieAdaptor); } @Override public boolean containsHeader(final String name) { return exchange.getResponseHeaders().contains(name); } @Override public String encodeUrl(final String url) { return encodeURL(url); } @Override public String encodeRedirectUrl(final String url) { return encodeRedirectURL(url); } @Override public void sendError(final int sc, final String msg) throws IOException { if(insideInclude) { //not 100% sure this is the correct action return; } ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (responseStarted()) { if(src.getErrorCode() > 0) { return; //error already set } throw UndertowServletMessages.MESSAGES.responseAlreadyCommited(); } if(servletContext.getDeployment().getDeploymentInfo().isSendCustomReasonPhraseOnError()) { exchange.setReasonPhrase(msg); } writer = null; responseState = ResponseState.NONE; exchange.setStatusCode(sc); if(src.isRunningInsideHandler()) { //all we do is set the error on the context, we handle it when the request is returned treatAsCommitted = true; src.setError(sc, msg); } else { //if the src is null there is no outer handler, as we are in an asnc request doErrorDispatch(sc, msg); } } public void doErrorDispatch(int sc, String error) throws IOException { writer = null; responseState = ResponseState.NONE; resetBuffer(); treatAsCommitted = false; final String location = servletContext.getDeployment().getErrorPages().getErrorLocation(sc); if (location != null) { RequestDispatcherImpl requestDispatcher = new RequestDispatcherImpl(location, servletContext); final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); try { requestDispatcher.error(servletRequestContext, servletRequestContext.getServletRequest(), servletRequestContext.getServletResponse(), exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getCurrentServlet().getManagedServlet().getServletInfo().getName(), error); } catch (ServletException e) { throw new RuntimeException(e); } } else if (error != null) { setContentType("text/html"); setCharacterEncoding("UTF-8"); if(servletContext.getDeployment().getDeploymentInfo().isEscapeErrorMessage()) { getWriter().write("Error" + escapeHtml(error) + ""); } else { getWriter().write("Error" + error + ""); } getWriter().close(); } responseDone(); } @Override public void sendError(final int sc) throws IOException { sendError(sc, StatusCodes.getReason(sc)); } @Override public void sendRedirect(final String location) throws IOException { if (responseStarted()) { throw UndertowServletMessages.MESSAGES.responseAlreadyCommited(); } resetBuffer(); setStatus(StatusCodes.FOUND); String realPath; if (isAbsoluteUrl(location)) {//absolute url exchange.getResponseHeaders().put(Headers.LOCATION, location); } else { if (location.startsWith("/")) { realPath = location; } else { //Match AsyncContext.dispatch, could use 'exchange.getResolvedPath().length()' instead of servlet context String current = exchange.getRequestURI().substring(getServletContext().getContextPath().length()); int lastSlash = current.lastIndexOf("/"); if (lastSlash != -1) { current = current.substring(0, lastSlash + 1); } realPath = CanonicalPathUtils.canonicalize(servletContext.getContextPath() + current + location); } String loc = exchange.getRequestScheme() + "://" + exchange.getHostAndPort() + realPath; exchange.getResponseHeaders().put(Headers.LOCATION, loc); } responseDone(); } @Override public void setDateHeader(final String name, final long date) { setHeader(name, DateUtils.toDateString(new Date(date))); } @Override public void addDateHeader(final String name, final long date) { addHeader(name, DateUtils.toDateString(new Date(date))); } @Override public void setHeader(final String name, final String value) { if(name == null) { throw UndertowServletMessages.MESSAGES.headerNameWasNull(); } setHeader(HttpString.tryFromString(name), value); } public void setHeader(final HttpString name, final String value) { if(name == null) { throw UndertowServletMessages.MESSAGES.headerNameWasNull(); } if (insideInclude || ignoredFlushPerformed) { return; } if(name.equals(Headers.CONTENT_TYPE)) { setContentType(value); } else { exchange.getResponseHeaders().put(name, value); } } @Override public void addHeader(final String name, final String value) { if(name == null) { throw UndertowServletMessages.MESSAGES.headerNameWasNull(); } addHeader(HttpString.tryFromString(name), value); } public void addHeader(final HttpString name, final String value) { if(name == null) { throw UndertowServletMessages.MESSAGES.headerNameWasNull(); } if (insideInclude || ignoredFlushPerformed || treatAsCommitted) { return; } if(name.equals(Headers.CONTENT_TYPE) && !exchange.getResponseHeaders().contains(Headers.CONTENT_TYPE)) { setContentType(value); } else { exchange.getResponseHeaders().add(name, value); } } @Override public void setIntHeader(final String name, final int value) { setHeader(name, Integer.toString(value)); } @Override public void addIntHeader(final String name, final int value) { addHeader(name, Integer.toString(value)); } @Override public void setStatus(final int sc) { if (insideInclude || treatAsCommitted) { return; } if (responseStarted()) { return; } exchange.setStatusCode(sc); } @Override public void setStatus(final int sc, final String sm) { setStatus(sc); if(!insideInclude && servletContext.getDeployment().getDeploymentInfo().isSendCustomReasonPhraseOnError()) { exchange.setReasonPhrase(sm); } } @Override public int getStatus() { return exchange.getStatusCode(); } @Override public String getHeader(final String name) { return exchange.getResponseHeaders().getFirst(name); } @Override public Collection getHeaders(final String name) { HeaderValues headers = exchange.getResponseHeaders().get(name); if(headers == null) { return Collections.emptySet(); } return new ArrayList<>(headers); } @Override public Collection getHeaderNames() { final Set headers = new HashSet<>(); for (final HttpString i : exchange.getResponseHeaders().getHeaderNames()) { headers.add(i.toString()); } return headers; } @Override public String getCharacterEncoding() { if (charset != null) { return charset; } // first check, web-app context level default response encoding if (servletContext.getDeployment().getDeploymentInfo().getDefaultResponseEncoding() != null) { return servletContext.getDeployment().getDeploymentInfo().getDefaultResponseEncoding(); } // now check the container level default encoding if (servletContext.getDeployment().getDeploymentInfo().getDefaultEncoding() != null) { return servletContext.getDeployment().getDeploymentInfo().getDefaultEncoding(); } // if no explicit encoding is specified, this method is supposed to return ISO-8859-1 as per the // expectation of this API return StandardCharsets.ISO_8859_1.name(); } @Override public String getContentType() { if (contentType != null) { if (charsetSet) { return contentType + ";charset=" + getCharacterEncoding(); } else { return contentType; } } return null; } @Override public ServletOutputStream getOutputStream() { if (responseState == ResponseState.WRITER) { throw UndertowServletMessages.MESSAGES.getWriterAlreadyCalled(); } responseState = ResponseState.STREAM; createOutputStream(); return servletOutputStream; } @Override public PrintWriter getWriter() throws IOException { if (writer == null) { if (!charsetSet) { //servet 5.5 setCharacterEncoding(getCharacterEncoding()); } if (responseState == ResponseState.STREAM) { throw UndertowServletMessages.MESSAGES.getOutputStreamAlreadyCalled(); } responseState = ResponseState.WRITER; createOutputStream(); final ServletPrintWriter servletPrintWriter = new ServletPrintWriter(servletOutputStream, getCharacterEncoding()); writer = ServletPrintWriterDelegate.newInstance(servletPrintWriter); } return writer; } private void createOutputStream() { if (servletOutputStream == null) { if (bufferSize == null) { servletOutputStream = new ServletOutputStreamImpl(exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY)); } else { servletOutputStream = new ServletOutputStreamImpl(exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY), bufferSize); } } } @Override public void setCharacterEncoding(final String charset) { if (insideInclude || responseStarted() || writer != null || isCommitted()) { return; } charsetSet = charset != null; this.charset = charset; if (contentType != null) { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, getContentType()); } } @Override public void setContentLength(final int len) { setContentLengthLong((long) len); } @Override public void setContentLengthLong(final long len) { if (insideInclude || responseStarted()) { return; } if(len >= 0) { exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, Long.toString(len)); } else { exchange.getResponseHeaders().remove(Headers.CONTENT_LENGTH); } this.contentLength = len; } boolean isIgnoredFlushPerformed() { return ignoredFlushPerformed; } void setIgnoredFlushPerformed(boolean ignoredFlushPerformed) { this.ignoredFlushPerformed = ignoredFlushPerformed; } private boolean responseStarted() { return exchange.isResponseStarted() || ignoredFlushPerformed || treatAsCommitted; } @Override public void setContentType(final String type) { if (type == null || insideInclude || responseStarted()) { return; } ContentTypeInfo ct = servletContext.parseContentType(type); contentType = ct.getContentType(); boolean useCharset = false; if(ct.getCharset() != null && writer == null && !isCommitted()) { charset = ct.getCharset(); charsetSet = true; useCharset = true; } if(useCharset || !charsetSet) { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, ct.getHeader()); } else if(ct.getCharset() == null) { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, ct.getHeader() + "; charset=" + charset); }else { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, ct.getContentType() + "; charset=" + charset); } } @Override public void setBufferSize(final int size) { if (servletOutputStream != null) { servletOutputStream.setBufferSize(size); } this.bufferSize = size; } @Override public int getBufferSize() { if (bufferSize == null) { return exchange.getConnection().getBufferSize(); } return bufferSize; } @Override public void flushBuffer() throws IOException { if (writer != null) { writer.flush(); } else if (servletOutputStream != null) { servletOutputStream.flush(); } else { createOutputStream(); servletOutputStream.flush(); } } public void closeStreamAndWriter() throws IOException { if(treatAsCommitted) { return; } if (writer != null) { writer.close(); } else { if (servletOutputStream == null) { createOutputStream(); } //close also flushes servletOutputStream.close(); } } public void freeResources() throws IOException { if(writer != null) { writer.close(); } if(servletOutputStream != null) { servletOutputStream.close(); } } @Override public void resetBuffer() { if (servletOutputStream != null) { servletOutputStream.resetBuffer(); } if (writer != null) { final ServletPrintWriter servletPrintWriter; try { servletPrintWriter = new ServletPrintWriter(servletOutputStream, getCharacterEncoding()); writer = ServletPrintWriterDelegate.newInstance(servletPrintWriter); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); //should never happen } } } @Override public boolean isCommitted() { return responseStarted(); } @Override public void reset() { if (servletOutputStream != null) { servletOutputStream.resetBuffer(); } writer = null; responseState = ResponseState.NONE; exchange.getResponseHeaders().clear(); exchange.setStatusCode(StatusCodes.OK); treatAsCommitted = false; } @Override public void setLocale(final Locale loc) { if (insideInclude || responseStarted()) { return; } this.locale = loc; exchange.getResponseHeaders().put(Headers.CONTENT_LANGUAGE, loc.getLanguage() + "-" + loc.getCountry()); if (!charsetSet && writer == null) { final Map localeCharsetMapping = servletContext.getDeployment().getDeploymentInfo().getLocaleCharsetMapping(); // Match full language_country_variant first, then language_country, // then language only String charset = localeCharsetMapping.get(locale.toString()); if (charset == null) { charset = localeCharsetMapping.get(locale.getLanguage() + "_" + locale.getCountry()); if (charset == null) { charset = localeCharsetMapping.get(locale.getLanguage()); } } if (charset != null) { this.charset = charset; if (contentType != null) { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, getContentType()); } } } } @Override public Locale getLocale() { if (locale != null) { return locale; } return Locale.getDefault(); } public void responseDone() { if (responseDone || treatAsCommitted) { return; } responseDone = true; try { closeStreamAndWriter(); } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); } finally { servletContext.updateSessionAccessTime(exchange); } } public boolean isInsideInclude() { return insideInclude; } public void setInsideInclude(final boolean insideInclude) { this.insideInclude = insideInclude; } public void setServletContext(final ServletContextImpl servletContext) { this.servletContext = servletContext; } public ServletContextImpl getServletContext() { return servletContext; } public String encodeURL(String url) { String absolute = toAbsolute(url); if (isEncodeable(absolute)) { // W3c spec clearly said if (url.equalsIgnoreCase("")) { url = absolute; } return originalServletContext.getSessionConfig().rewriteUrl(url, servletContext.getSession(originalServletContext, exchange, true).getId()); } else { return (url); } } /** * Encode the session identifier associated with this response * into the specified redirect URL, if necessary. * * @param url URL to be encoded */ public String encodeRedirectURL(String url) { if (isEncodeable(toAbsolute(url))) { return originalServletContext.getSessionConfig().rewriteUrl(url, servletContext.getSession(originalServletContext, exchange, true).getId()); } else { return url; } } /** * Convert (if necessary) and return the absolute URL that represents the * resource referenced by this possibly relative URL. If this URL is * already absolute, return it unchanged. * * @param location URL to be (possibly) converted and then returned * @throws IllegalArgumentException if a MalformedURLException is * thrown when converting the relative URL to an absolute one */ private String toAbsolute(String location) { if (location == null) { return location; } boolean leadingSlash = location.startsWith("/"); if (leadingSlash || !hasScheme(location)) { return RedirectBuilder.redirect(exchange, location, false); } else { return location; } } /** * Determine if a URI string has a scheme component. */ private boolean hasScheme(String uri) { int len = uri.length(); for (int i = 0; i < len; i++) { char c = uri.charAt(i); if (c == ':') { return i > 0; } else if (!Character.isLetterOrDigit(c) && (c != '+' && c != '-' && c != '.')) { return false; } } return false; } /** * Return true if the specified URL should be encoded with * a session identifier. This will be true if all of the following * conditions are met: *

      *
    • The request we are responding to asked for a valid session *
    • The requested session ID was not received via a cookie *
    • The specified URL points back to somewhere within the web * application that is responding to this request *
    * * @param location Absolute URL to be validated */ private boolean isEncodeable(final String location) { if (location == null) return (false); // Is this an intra-document reference? if (location.startsWith("#")) return (false); // Are we in a valid session that is not using cookies? final HttpServletRequestImpl hreq = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getOriginalRequest(); // Is URL encoding permitted if (!originalServletContext.getEffectiveSessionTrackingModes().contains(SessionTrackingMode.URL)) { return false; } final HttpSession session = hreq.getSession(false); if (session == null) { return false; } else if(hreq.isRequestedSessionIdFromCookie()) { return false; } else if (!hreq.isRequestedSessionIdFromURL() && !session.isNew()) { return false; } return doIsEncodeable(hreq, session, location); } private boolean doIsEncodeable(HttpServletRequestImpl hreq, HttpSession session, String location) { // Is this a valid absolute URL? URL url = null; try { url = new URL(location); } catch (MalformedURLException e) { return false; } // Does this URL match down to (and including) the context path? if (!hreq.getScheme().equalsIgnoreCase(url.getProtocol())) { return false; } if (!hreq.getServerName().equalsIgnoreCase(url.getHost())) { return false; } int serverPort = hreq.getServerPort(); if (serverPort == -1) { if ("https".equals(hreq.getScheme())) { serverPort = 443; } else { serverPort = 80; } } int urlPort = url.getPort(); if (urlPort == -1) { if ("https".equals(url.getProtocol())) { urlPort = 443; } else { urlPort = 80; } } if (serverPort != urlPort) { return false; } String file = url.getFile(); if (file == null) { return false; } String tok = originalServletContext.getSessionCookieConfig().getName().toLowerCase() + "=" + session.getId(); if (file.contains(tok)) { return false; } // This URL belongs to our web application, so it is encodeable return true; } public long getContentLength() { return contentLength; } public enum ResponseState { NONE, STREAM, WRITER } private static String escapeHtml(String msg) { return msg.replace("<", "<").replace(">", ">").replace("&", "&"); } public boolean isTreatAsCommitted() { return treatAsCommitted; } @Override public void setTrailerFields(Supplier> supplier) { if(exchange.isResponseStarted()) { throw UndertowServletMessages.MESSAGES.responseAlreadyCommited(); } if(exchange.getProtocol() == Protocols.HTTP_1_0) { throw UndertowServletMessages.MESSAGES.trailersNotSupported("HTTP/1.0 request"); } else if(exchange.getProtocol() == Protocols.HTTP_1_1) { if(exchange.getResponseHeaders().contains(Headers.CONTENT_LENGTH)) { throw UndertowServletMessages.MESSAGES.trailersNotSupported("not chunked"); } } this.trailerSupplier = supplier; exchange.putAttachment(HttpAttachments.RESPONSE_TRAILER_SUPPLIER, () -> { HeaderMap trailers = new HeaderMap(); Map map = supplier.get(); for(Map.Entry e : map.entrySet()) { trailers.put(new HttpString(e.getKey()), e.getValue()); } return trailers; }); } @Override public Supplier> getTrailerFields() { return trailerSupplier; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/HttpSessionImpl.java000066400000000000000000000175061420065311100314670ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import java.security.PrivilegedAction; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionContext; import io.undertow.server.session.Session; import io.undertow.servlet.UndertowServletMessages; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.servlet.util.IteratorEnumeration; /** * The HTTP session implementation. * * Note that for security reasons no attribute names that start with io.undertow are allowed. * * @author Stuart Douglas */ public class HttpSessionImpl implements HttpSession { private static final RuntimePermission PERMISSION = new RuntimePermission("io.undertow.servlet.spec.UNWRAP_HTTP_SESSION"); public static final String IO_UNDERTOW = "io.undertow"; private final Session session; private final ServletContext servletContext; private final boolean newSession; private volatile boolean invalid; private final ServletRequestContext servletRequestContext; private HttpSessionImpl(final Session session, final ServletContext servletContext, final boolean newSession, ServletRequestContext servletRequestContext) { this.session = session; this.servletContext = servletContext; this.newSession = newSession; this.servletRequestContext = servletRequestContext; } public static HttpSessionImpl forSession(final Session session, final ServletContext servletContext, final boolean newSession) { // forSession is called by privileged actions only so no need to do it again ServletRequestContext current = ServletRequestContext.current(); if (current == null) { return new HttpSessionImpl(session, servletContext, newSession, null); } else { HttpSessionImpl httpSession = current.getSession(); if (httpSession == null) { httpSession = new HttpSessionImpl(session, servletContext, newSession, current); current.setSession(httpSession); } else { if(httpSession.session != session) { //in some rare cases it may be that there are two different service contexts involved in the one request //in this case we just return a new session rather than using the thread local version httpSession = new HttpSessionImpl(session, servletContext, newSession, current); } } return httpSession; } } @Override public long getCreationTime() { return session.getCreationTime(); } @Override public String getId() { return session.getId(); } @Override public long getLastAccessedTime() { return session.getLastAccessedTime(); } @Override public ServletContext getServletContext() { return servletContext; } @Override public void setMaxInactiveInterval(final int interval) { session.setMaxInactiveInterval(interval); } @Override public int getMaxInactiveInterval() { return session.getMaxInactiveInterval(); } @Override public HttpSessionContext getSessionContext() { return null; } @Override public Object getAttribute(final String name) { if(name.startsWith(IO_UNDERTOW)) { throw new SecurityException(); } return session.getAttribute(name); } @Override public Object getValue(final String name) { if(name.startsWith(IO_UNDERTOW)) { throw new SecurityException(); } return getAttribute(name); } @Override public Enumeration getAttributeNames() { Set attributeNames = getFilteredAttributeNames(); return new IteratorEnumeration<>(attributeNames.iterator()); } private Set getFilteredAttributeNames() { Set attributeNames = new HashSet<>(session.getAttributeNames()); Iterator it = attributeNames.iterator(); while (it.hasNext()) { if(it.next().startsWith(IO_UNDERTOW)) { it.remove(); } } return attributeNames; } @Override public String[] getValueNames() { Set names = getFilteredAttributeNames(); String[] ret = new String[names.size()]; int i = 0; for (String name : names) { ret[i++] = name; } return ret; } @Override public void setAttribute(final String name, final Object value) { if(name.startsWith(IO_UNDERTOW)) { throw new SecurityException(); } if (value == null) { removeAttribute(name); } else { session.setAttribute(name, value); } } @Override public void putValue(final String name, final Object value) { setAttribute(name, value); } @Override public void removeAttribute(final String name) { if(name.startsWith(IO_UNDERTOW)) { throw new SecurityException(); } session.removeAttribute(name); } @Override public void removeValue(final String name) { removeAttribute(name); } @Override public void invalidate() { invalid = true; if (servletRequestContext == null) { session.invalidate(null); } else { if(servletRequestContext.getOriginalRequest().getServletContext() == servletContext) { session.invalidate(servletRequestContext.getOriginalRequest().getExchange()); } else { session.invalidate(null); } } } @Override public boolean isNew() { if (invalid) { throw UndertowServletMessages.MESSAGES.sessionIsInvalid(); } return newSession; } public Session getSession() { SecurityManager sm = System.getSecurityManager(); if(sm != null) { sm.checkPermission(PERMISSION); } return session; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; HttpSessionImpl that = (HttpSessionImpl) o; return session.getId().equals(that.session.getId()); } @Override public int hashCode() { return session.getId().hashCode(); } public boolean isInvalid() { if(invalid) { return true; } try { //TODO: in 1.5 we need to add session.isValid() session.getMaxInactiveInterval(); return false; } catch (IllegalStateException e) { return true; } } public static class UnwrapSessionAction implements PrivilegedAction { private final HttpSessionImpl session; public UnwrapSessionAction(HttpSession session) { this.session = (HttpSessionImpl) session; } @Override public Session run() { return session.getSession(); } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/MappingImpl.java000066400000000000000000000032141420065311100305660ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import javax.servlet.http.HttpServletMapping; import javax.servlet.http.MappingMatch; /** * @author Stuart Douglas */ public class MappingImpl implements HttpServletMapping { private final String matchValue; private final String pattern; private final MappingMatch matchType; private final String servletName; public MappingImpl(String matchValue, String pattern, MappingMatch matchType, String servletName) { this.matchValue = matchValue; this.pattern = pattern; this.matchType = matchType; this.servletName = servletName; } @Override public String getMatchValue() { return matchValue; } @Override public String getPattern() { return pattern; } @Override public String getServletName() { return servletName; } @Override public MappingMatch getMappingMatch() { return matchType; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/PartImpl.java000066400000000000000000000115471420065311100301110ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import io.undertow.server.handlers.form.FormData; import io.undertow.servlet.UndertowServletMessages; import io.undertow.util.HeaderValues; import io.undertow.util.Headers; import io.undertow.util.HttpString; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; import javax.servlet.MultipartConfigElement; import javax.servlet.http.Part; /** * @author Stuart Douglas */ public class PartImpl implements Part { private final String name; private final FormData.FormValue formValue; private final MultipartConfigElement config; private final ServletContextImpl servletContext; private final HttpServletRequestImpl servletRequest; public PartImpl(final String name, final FormData.FormValue formValue, MultipartConfigElement config, ServletContextImpl servletContext, HttpServletRequestImpl servletRequest) { this.name = name; this.formValue = formValue; this.config = config; this.servletContext = servletContext; this.servletRequest = servletRequest; } @Override public InputStream getInputStream() throws IOException { if (formValue.isFileItem()) { return formValue.getFileItem().getInputStream(); } else { String charset; if (formValue.getCharset() != null) { charset = formValue.getCharset(); } else if (servletRequest.getCharacterEncoding() != null) { charset = servletRequest.getCharacterEncoding(); } else { charset = servletContext.getDeployment().getDefaultRequestCharset().name(); } return new ByteArrayInputStream(formValue.getValue().getBytes(charset)); } } @Override public String getContentType() { return formValue.getHeaders().getFirst(Headers.CONTENT_TYPE); } @Override public String getName() { return name; } @Override public String getSubmittedFileName() { return formValue.getFileName(); } @Override public long getSize() { try { if (formValue.isFileItem()) { return formValue.getFileItem().getFileSize(); } else if (formValue.getCharset() != null) { return formValue.getValue().getBytes(formValue.getCharset()).length; } else { return formValue.getValue().length(); } } catch (IOException e) { throw new RuntimeException(e); } } @Override public void write(final String fileName) throws IOException { Path target = Paths.get(fileName); if(!target.isAbsolute()) { if(config.getLocation().isEmpty()) { target = servletContext.getDeployment().getDeploymentInfo().getTempPath().resolve(fileName); } else { target = Paths.get(config.getLocation(), fileName); } } if (formValue.isFileItem()) { formValue.getFileItem().write(target); } } @Override public void delete() throws IOException { if (formValue.isFileItem()) { try { formValue.getFileItem().delete(); } catch (IOException e) { throw UndertowServletMessages.MESSAGES.deleteFailed(formValue.getPath()); } } } @Override public String getHeader(final String name) { return formValue.getHeaders().getFirst(new HttpString(name)); } @Override public Collection getHeaders(final String name) { HeaderValues values = formValue.getHeaders().get(new HttpString(name)); return values == null ? Collections.emptyList() : values; } @Override public Collection getHeaderNames() { final Set ret = new HashSet<>(); for (HttpString i : formValue.getHeaders().getHeaderNames()) { ret.add(i.toString()); } return ret; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/PushBuilderImpl.java000066400000000000000000000175511420065311100314320ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import io.undertow.server.ServerConnection; import io.undertow.server.handlers.Cookie; import io.undertow.servlet.UndertowServletMessages; import io.undertow.util.HeaderMap; import io.undertow.util.HeaderValues; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.Methods; import javax.servlet.http.HttpSession; import javax.servlet.http.PushBuilder; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Set; /** * @author Stuart Douglas * @author Richard Opalka */ public class PushBuilderImpl implements PushBuilder { private static final Set IGNORE; private static final Set CONDITIONAL; private static final Set INVALID_METHOD; static { final Set ignore = new HashSet<>(); ignore.add(Headers.IF_MATCH); ignore.add(Headers.IF_NONE_MATCH); ignore.add(Headers.IF_MODIFIED_SINCE); ignore.add(Headers.IF_UNMODIFIED_SINCE); ignore.add(Headers.IF_RANGE); ignore.add(Headers.RANGE); ignore.add(Headers.ACCEPT_RANGES); ignore.add(Headers.EXPECT); ignore.add(Headers.REFERER); IGNORE = Collections.unmodifiableSet(ignore); final Set conditional = new HashSet<>(); conditional.add(Headers.IF_MATCH); conditional.add(Headers.IF_NONE_MATCH); conditional.add(Headers.IF_MODIFIED_SINCE); conditional.add(Headers.IF_UNMODIFIED_SINCE); conditional.add(Headers.IF_RANGE); CONDITIONAL = Collections.unmodifiableSet(conditional); final Set invalid = new HashSet<>(); invalid.add(Methods.OPTIONS_STRING); invalid.add(Methods.PUT_STRING); invalid.add(Methods.POST_STRING); invalid.add(Methods.DELETE_STRING); invalid.add(Methods.CONNECT_STRING); invalid.add(Methods.TRACE_STRING); invalid.add(""); INVALID_METHOD = Collections.unmodifiableSet(invalid); } private final HttpServletRequestImpl servletRequest; private String method; private String queryString; private String sessionId; private final HeaderMap headers = new HeaderMap(); private String path; public PushBuilderImpl(HttpServletRequestImpl servletRequest) { //TODO: auth this.servletRequest = servletRequest; this.method = "GET"; this.queryString = servletRequest.getQueryString(); HttpSession session = servletRequest.getSession(false); if(session != null) { this.sessionId = session.getId(); } else { this.sessionId = servletRequest.getRequestedSessionId(); } for(HeaderValues header : servletRequest.getExchange().getRequestHeaders()) { if(!IGNORE.contains(header.getHeaderName())) { headers.addAll(header.getHeaderName(), header); } } if(servletRequest.getQueryString() == null) { this.headers.add(Headers.REFERER, servletRequest.getRequestURL().toString()); } else { this.headers.add(Headers.REFERER, servletRequest.getRequestURL() + "?" + servletRequest.getQueryString()); } this.path = null; for (Cookie cookie : servletRequest.getExchange().responseCookies()) { if(cookie.getMaxAge() != null && cookie.getMaxAge() <= 0) { //remove cookie HeaderValues existing = headers.get(Headers.COOKIE); if(existing != null) { Iterator it = existing.iterator(); while (it.hasNext()) { String val = it.next(); if(val.startsWith(cookie.getName() + "=")) { it.remove(); } } } } else if(!cookie.getName().equals(servletRequest.getServletContext().getSessionCookieConfig().getName())){ headers.add(Headers.COOKIE, cookie.getName() + "=" + cookie.getValue()); } } } @Override public PushBuilder method(String method) { if(method == null) { throw UndertowServletMessages.MESSAGES.paramCannotBeNullNPE("method"); } if(INVALID_METHOD.contains(method)) { throw UndertowServletMessages.MESSAGES.invalidMethodForPushRequest(method); } this.method = method; return this; } @Override public PushBuilder queryString(String queryString) { this.queryString = queryString; return this; } @Override public PushBuilder sessionId(String sessionId) { this.sessionId = sessionId; return this; } @Override public PushBuilder setHeader(String name, String value) { headers.put(new HttpString(name), value); return this; } @Override public PushBuilder addHeader(String name, String value) { headers.add(new HttpString(name), value); return this; } @Override public PushBuilder removeHeader(String name) { headers.remove(name); return this; } @Override public PushBuilder path(String path) { this.path = path; return this; } @Override public void push() { if(path == null) { throw UndertowServletMessages.MESSAGES.pathWasNotSet(); } ServerConnection con = servletRequest.getExchange().getConnection(); if (con.isPushSupported()) { HeaderMap newHeaders = new HeaderMap(); for (HeaderValues entry : headers) { newHeaders.addAll(entry.getHeaderName(), entry); } if (sessionId != null) { newHeaders.put(Headers.COOKIE, "JSESSIONID=" + sessionId); //TODO: do this properly, may be a different tracking method or a different cookie name } String path = this.path; if(!path.startsWith("/")) { path = servletRequest.getContextPath() + "/" + path; } if (queryString != null && !queryString.isEmpty()) { if(path.contains("?")) { path += "&" + queryString; } else { path += "?" + queryString; } } con.pushResource(path, new HttpString(method), newHeaders); } path = null; for(HttpString h : CONDITIONAL) { headers.remove(h); } } @Override public String getMethod() { return method; } @Override public String getQueryString() { return queryString; } @Override public String getSessionId() { return sessionId; } @Override public Set getHeaderNames() { Set names = new HashSet<>(); for(HeaderValues name : headers) { names.add(name.getHeaderName().toString()); } return names; } @Override public String getHeader(String name) { return headers.getFirst(name); } @Override public String getPath() { return path; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/RequestDispatcherImpl.java000066400000000000000000000654521420065311100326460ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import java.io.IOException; import java.io.PrintWriter; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.Deque; import java.util.Map; import javax.servlet.DispatcherType; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletRequestWrapper; import javax.servlet.ServletResponse; import javax.servlet.ServletResponseWrapper; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import io.undertow.UndertowLogger; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.UndertowServletLogger; import io.undertow.servlet.UndertowServletMessages; import io.undertow.servlet.api.ThreadSetupHandler; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.servlet.handlers.ServletChain; import io.undertow.servlet.handlers.ServletPathMatch; import io.undertow.util.QueryParameterUtils; /** * @author Stuart Douglas */ public class RequestDispatcherImpl implements RequestDispatcher { private final String path; private final ServletContextImpl servletContext; private final ServletChain chain; private final ServletPathMatch pathMatch; private final boolean named; public RequestDispatcherImpl(final String path, final ServletContextImpl servletContext) { this.path = path; this.servletContext = servletContext; String basePath = path; int qPos = basePath.indexOf("?"); if (qPos != -1) { basePath = basePath.substring(0, qPos); } int mPos = basePath.indexOf(";"); if(mPos != -1) { basePath = basePath.substring(0, mPos); } this.pathMatch = servletContext.getDeployment().getServletPaths().getServletHandlerByPath(basePath); this.chain = pathMatch.getServletChain(); this.named = false; } public RequestDispatcherImpl(final ServletChain chain, final ServletContextImpl servletContext) { this.chain = chain; this.named = true; this.servletContext = servletContext; this.path = null; this.pathMatch = null; } @Override public void forward(final ServletRequest request, final ServletResponse response) throws ServletException, IOException { if(System.getSecurityManager() != null) { try { AccessController.doPrivileged(new PrivilegedExceptionAction() { @Override public Object run() throws Exception { forwardImplSetup(request, response); return null; } }); } catch (PrivilegedActionException e) { if(e.getCause() instanceof ServletException) { throw (ServletException)e.getCause(); } else if(e.getCause() instanceof IOException) { throw (IOException)e.getCause(); } else if(e.getCause() instanceof RuntimeException) { throw (RuntimeException)e.getCause(); } else { throw new RuntimeException(e.getCause()); } } } else { forwardImplSetup(request, response); } } private void forwardImplSetup(final ServletRequest request, final ServletResponse response) throws ServletException, IOException { final ServletRequestContext servletRequestContext = SecurityActions.currentServletRequestContext(); if(servletRequestContext == null) { UndertowLogger.REQUEST_LOGGER.debugf("No servlet request context for %s, dispatching mock request", request); mock(request, response); return; } ServletContextImpl oldServletContext = null; HttpSessionImpl oldSession = null; if (servletRequestContext.getCurrentServletContext() != this.servletContext) { try { //cross context request, we need to run the thread setup actions oldServletContext = servletRequestContext.getCurrentServletContext(); oldSession = servletRequestContext.getSession(); servletRequestContext.setSession(null); servletRequestContext.setCurrentServletContext(this.servletContext); this.servletContext.invokeAction(servletRequestContext.getExchange(), new ThreadSetupHandler.Action() { @Override public Void call(HttpServerExchange exchange, Object context) throws Exception { forwardImpl(request, response, servletRequestContext); return null; } }); } finally { servletRequestContext.setSession(oldSession); servletRequestContext.setCurrentServletContext(oldServletContext); // update time in old context and run the requestDone for the session servletRequestContext.getCurrentServletContext().updateSessionAccessTime(servletRequestContext.getExchange()); } } else { forwardImpl(request, response, servletRequestContext); } } private void forwardImpl(ServletRequest request, ServletResponse response, ServletRequestContext servletRequestContext) throws ServletException, IOException { final HttpServletRequestImpl requestImpl = servletRequestContext.getOriginalRequest(); final HttpServletResponseImpl responseImpl = servletRequestContext.getOriginalResponse(); if (!servletContext.getDeployment().getDeploymentInfo().isAllowNonStandardWrappers()) { if (servletRequestContext.getOriginalRequest() != request) { if (!(request instanceof ServletRequestWrapper)) { throw UndertowServletMessages.MESSAGES.requestWasNotOriginalOrWrapper(request); } } if (servletRequestContext.getOriginalResponse() != response) { if (!(response instanceof ServletResponseWrapper)) { throw UndertowServletMessages.MESSAGES.responseWasNotOriginalOrWrapper(response); } } } response.resetBuffer(); final ServletRequest oldRequest = servletRequestContext.getServletRequest(); final ServletResponse oldResponse = servletRequestContext.getServletResponse(); Map> queryParameters = requestImpl.getQueryParameters(); request.removeAttribute(INCLUDE_REQUEST_URI); request.removeAttribute(INCLUDE_CONTEXT_PATH); request.removeAttribute(INCLUDE_SERVLET_PATH); request.removeAttribute(INCLUDE_PATH_INFO); request.removeAttribute(INCLUDE_QUERY_STRING); final String oldURI = requestImpl.getExchange().getRequestURI(); final String oldRequestPath = requestImpl.getExchange().getRequestPath(); final String oldPath = requestImpl.getExchange().getRelativePath(); final ServletPathMatch oldServletPathMatch = requestImpl.getExchange().getAttachment(ServletRequestContext.ATTACHMENT_KEY).getServletPathMatch(); if (!named) { //only update if this is the first forward if (request.getAttribute(FORWARD_REQUEST_URI) == null) { requestImpl.setAttribute(FORWARD_REQUEST_URI, requestImpl.getRequestURI()); requestImpl.setAttribute(FORWARD_CONTEXT_PATH, requestImpl.getContextPath()); requestImpl.setAttribute(FORWARD_SERVLET_PATH, requestImpl.getServletPath()); requestImpl.setAttribute(FORWARD_PATH_INFO, requestImpl.getPathInfo()); requestImpl.setAttribute(FORWARD_QUERY_STRING, requestImpl.getQueryString()); } int qsPos = path.indexOf("?"); String newServletPath = path; if (qsPos != -1) { String newQueryString = newServletPath.substring(qsPos + 1); newServletPath = newServletPath.substring(0, qsPos); String encoding = QueryParameterUtils.getQueryParamEncoding(servletRequestContext.getExchange()); Map> newQueryParameters = QueryParameterUtils.mergeQueryParametersWithNewQueryString(queryParameters, newQueryString, encoding); requestImpl.getExchange().setQueryString(newQueryString); requestImpl.setQueryParameters(newQueryParameters); } String newRequestUri = servletContext.getContextPath() + newServletPath; requestImpl.getExchange().setRelativePath(newServletPath); requestImpl.getExchange().setRequestPath(newRequestUri); requestImpl.getExchange().setRequestURI(newRequestUri); requestImpl.getExchange().getAttachment(ServletRequestContext.ATTACHMENT_KEY).setServletPathMatch(pathMatch); requestImpl.setServletContext(servletContext); responseImpl.setServletContext(servletContext); } try { try { servletRequestContext.setServletRequest(request); servletRequestContext.setServletResponse(response); if (named) { servletContext.getDeployment().getServletDispatcher().dispatchToServlet(requestImpl.getExchange(), chain, DispatcherType.FORWARD); } else { servletContext.getDeployment().getServletDispatcher().dispatchToPath(requestImpl.getExchange(), pathMatch, DispatcherType.FORWARD); } //if we are not in an async or error dispatch then we close the response if (!request.isAsyncStarted()) { if (response instanceof HttpServletResponseImpl) { responseImpl.closeStreamAndWriter(); } else { try { final PrintWriter writer = response.getWriter(); writer.flush(); writer.close(); } catch (IllegalStateException e) { final ServletOutputStream outputStream = response.getOutputStream(); outputStream.flush(); outputStream.close(); } } } } catch (ServletException e) { throw e; } catch (IOException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } } finally { servletRequestContext.setServletRequest(oldRequest); servletRequestContext.setServletResponse(oldResponse); final boolean preservePath = servletRequestContext.getDeployment().getDeploymentInfo().isPreservePathOnForward(); if (preservePath) { requestImpl.getExchange().setRelativePath(oldPath); requestImpl.getExchange().getAttachment(ServletRequestContext.ATTACHMENT_KEY).setServletPathMatch(oldServletPathMatch); requestImpl.getExchange().setRequestPath(oldRequestPath); requestImpl.getExchange().setRequestURI(oldURI); } } } @Override public void include(final ServletRequest request, final ServletResponse response) throws ServletException, IOException { if(System.getSecurityManager() != null) { try { AccessController.doPrivileged(new PrivilegedExceptionAction() { @Override public Object run() throws Exception { setupIncludeImpl(request, response); return null; } }); } catch (PrivilegedActionException e) { if(e.getCause() instanceof ServletException) { throw (ServletException)e.getCause(); } else if(e.getCause() instanceof IOException) { throw (IOException)e.getCause(); } else if(e.getCause() instanceof RuntimeException) { throw (RuntimeException)e.getCause(); } else { throw new RuntimeException(e.getCause()); } } } else { setupIncludeImpl(request, response); } } private void setupIncludeImpl(final ServletRequest request, final ServletResponse response) throws ServletException, IOException { final ServletRequestContext servletRequestContext = SecurityActions.currentServletRequestContext(); if(servletRequestContext == null) { UndertowLogger.REQUEST_LOGGER.debugf("No servlet request context for %s, dispatching mock request", request); mock(request, response); return; } final HttpServletRequestImpl requestImpl = servletRequestContext.getOriginalRequest(); final HttpServletResponseImpl responseImpl = servletRequestContext.getOriginalResponse(); ServletContextImpl oldServletContext = null; HttpSessionImpl oldSession = null; if (servletRequestContext.getCurrentServletContext() != this.servletContext) { //cross context request, we need to run the thread setup actions oldServletContext = servletRequestContext.getCurrentServletContext(); oldSession = servletRequestContext.getSession(); servletRequestContext.setSession(null); servletRequestContext.setCurrentServletContext(this.servletContext); try { servletRequestContext.getCurrentServletContext().invokeAction(servletRequestContext.getExchange(), new ThreadSetupHandler.Action() { @Override public Void call(HttpServerExchange exchange, Object context) throws Exception { includeImpl(request, response, servletRequestContext, requestImpl, responseImpl); return null; } }); } finally { // update time in new context and run the requestDone for the session servletRequestContext.getCurrentServletContext().updateSessionAccessTime(servletRequestContext.getExchange()); servletRequestContext.setSession(oldSession); servletRequestContext.setCurrentServletContext(oldServletContext); } } else { includeImpl(request, response, servletRequestContext, requestImpl, responseImpl); } } private void includeImpl(ServletRequest request, ServletResponse response, ServletRequestContext servletRequestContext, HttpServletRequestImpl requestImpl, HttpServletResponseImpl responseImpl) throws ServletException, IOException { if (!servletContext.getDeployment().getDeploymentInfo().isAllowNonStandardWrappers()) { if (servletRequestContext.getOriginalRequest() != request) { if (!(request instanceof ServletRequestWrapper)) { throw UndertowServletMessages.MESSAGES.requestWasNotOriginalOrWrapper(request); } } if (servletRequestContext.getOriginalResponse() != response) { if (!(response instanceof ServletResponseWrapper)) { throw UndertowServletMessages.MESSAGES.responseWasNotOriginalOrWrapper(response); } } } final ServletRequest oldRequest = servletRequestContext.getServletRequest(); final ServletResponse oldResponse = servletRequestContext.getServletResponse(); Object requestUri = null; Object contextPath = null; Object servletPath = null; Object pathInfo = null; Object queryString = null; Map> queryParameters = requestImpl.getQueryParameters(); if (!named) { requestUri = request.getAttribute(INCLUDE_REQUEST_URI); contextPath = request.getAttribute(INCLUDE_CONTEXT_PATH); servletPath = request.getAttribute(INCLUDE_SERVLET_PATH); pathInfo = request.getAttribute(INCLUDE_PATH_INFO); queryString = request.getAttribute(INCLUDE_QUERY_STRING); int qsPos = path.indexOf("?"); String newServletPath = path; if (qsPos != -1) { String newQueryString = newServletPath.substring(qsPos + 1); newServletPath = newServletPath.substring(0, qsPos); String encoding = QueryParameterUtils.getQueryParamEncoding(servletRequestContext.getExchange()); Map> newQueryParameters = QueryParameterUtils.mergeQueryParametersWithNewQueryString(queryParameters, newQueryString, encoding); requestImpl.setQueryParameters(newQueryParameters); requestImpl.setAttribute(INCLUDE_QUERY_STRING, newQueryString); } else { requestImpl.setAttribute(INCLUDE_QUERY_STRING, ""); } String newRequestUri = servletContext.getContextPath() + newServletPath; requestImpl.setAttribute(INCLUDE_REQUEST_URI, newRequestUri); requestImpl.setAttribute(INCLUDE_CONTEXT_PATH, servletContext.getContextPath()); requestImpl.setAttribute(INCLUDE_SERVLET_PATH, pathMatch.getMatched()); requestImpl.setAttribute(INCLUDE_PATH_INFO, pathMatch.getRemaining()); } boolean inInclude = responseImpl.isInsideInclude(); responseImpl.setInsideInclude(true); DispatcherType oldDispatcherType = servletRequestContext.getDispatcherType(); ServletContextImpl oldContext = requestImpl.getServletContext(); try { requestImpl.setServletContext(servletContext); responseImpl.setServletContext(servletContext); try { servletRequestContext.setServletRequest(request); servletRequestContext.setServletResponse(response); servletContext.getDeployment().getServletDispatcher().dispatchToServlet(requestImpl.getExchange(), chain, DispatcherType.INCLUDE); } catch (ServletException e) { throw e; } catch (IOException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } } finally { responseImpl.setInsideInclude(inInclude); requestImpl.setServletContext(oldContext); responseImpl.setServletContext(oldContext); servletRequestContext.setServletRequest(oldRequest); servletRequestContext.setServletResponse(oldResponse); servletRequestContext.setDispatcherType(oldDispatcherType); if (!named) { requestImpl.setAttribute(INCLUDE_REQUEST_URI, requestUri); requestImpl.setAttribute(INCLUDE_CONTEXT_PATH, contextPath); requestImpl.setAttribute(INCLUDE_SERVLET_PATH, servletPath); requestImpl.setAttribute(INCLUDE_PATH_INFO, pathInfo); requestImpl.setAttribute(INCLUDE_QUERY_STRING, queryString); requestImpl.setQueryParameters(queryParameters); } } } public void error(ServletRequestContext servletRequestContext, final ServletRequest request, final ServletResponse response, final String servletName, final String message) throws ServletException, IOException { error(servletRequestContext, request, response, servletName, null, message); } public void error(ServletRequestContext servletRequestContext, final ServletRequest request, final ServletResponse response, final String servletName) throws ServletException, IOException { error(servletRequestContext, request, response, servletName, null, null); } public void error(ServletRequestContext servletRequestContext, final ServletRequest request, final ServletResponse response, final String servletName, final Throwable exception) throws ServletException, IOException { error(servletRequestContext, request, response, servletName, exception, exception.getMessage()); } private void error(ServletRequestContext servletRequestContext, final ServletRequest request, final ServletResponse response, final String servletName, final Throwable exception, final String message) throws ServletException, IOException { if(request.getDispatcherType() == DispatcherType.ERROR) { //we have already dispatched once with an error //if we dispatch again we run the risk of a stack overflow //so we just kill it, the user will just get the basic error page UndertowServletLogger.REQUEST_LOGGER.errorGeneratingErrorPage(servletRequestContext.getExchange().getRequestPath(), request.getAttribute(ERROR_EXCEPTION), servletRequestContext.getExchange().getStatusCode(), exception); servletRequestContext.getExchange().endExchange(); return; } final HttpServletRequestImpl requestImpl = servletRequestContext.getOriginalRequest(); final HttpServletResponseImpl responseImpl = servletRequestContext.getOriginalResponse(); if (!servletContext.getDeployment().getDeploymentInfo().isAllowNonStandardWrappers()) { if (servletRequestContext.getOriginalRequest() != request) { if (!(request instanceof ServletRequestWrapper)) { throw UndertowServletMessages.MESSAGES.requestWasNotOriginalOrWrapper(request); } } if (servletRequestContext.getOriginalResponse() != response) { if (!(response instanceof ServletResponseWrapper)) { throw UndertowServletMessages.MESSAGES.responseWasNotOriginalOrWrapper(response); } } } final ServletRequest oldRequest = servletRequestContext.getServletRequest(); final ServletResponse oldResponse = servletRequestContext.getServletResponse(); servletRequestContext.setDispatcherType(DispatcherType.ERROR); //only update if this is the first forward, add forward attrs too if (request.getAttribute(FORWARD_REQUEST_URI) == null) { requestImpl.setAttribute(FORWARD_REQUEST_URI, requestImpl.getRequestURI()); requestImpl.setAttribute(FORWARD_CONTEXT_PATH, requestImpl.getContextPath()); requestImpl.setAttribute(FORWARD_SERVLET_PATH, requestImpl.getServletPath()); requestImpl.setAttribute(FORWARD_PATH_INFO, requestImpl.getPathInfo()); requestImpl.setAttribute(FORWARD_QUERY_STRING, requestImpl.getQueryString()); } requestImpl.setAttribute(ERROR_REQUEST_URI, requestImpl.getRequestURI()); requestImpl.setAttribute(ERROR_SERVLET_NAME, servletName); if (exception != null) { if (exception instanceof ServletException && ((ServletException)exception).getRootCause() != null) { requestImpl.setAttribute(ERROR_EXCEPTION, ((ServletException) exception).getRootCause()); requestImpl.setAttribute(ERROR_EXCEPTION_TYPE, ((ServletException) exception).getRootCause().getClass()); } else { requestImpl.setAttribute(ERROR_EXCEPTION, exception); requestImpl.setAttribute(ERROR_EXCEPTION_TYPE, exception.getClass()); } } requestImpl.setAttribute(ERROR_MESSAGE, message); requestImpl.setAttribute(ERROR_STATUS_CODE, responseImpl.getStatus()); int qsPos = path.indexOf("?"); String newServletPath = path; if (qsPos != -1) { Map> queryParameters = requestImpl.getQueryParameters(); String newQueryString = newServletPath.substring(qsPos + 1); newServletPath = newServletPath.substring(0, qsPos); String encoding = QueryParameterUtils.getQueryParamEncoding(servletRequestContext.getExchange()); Map> newQueryParameters = QueryParameterUtils.mergeQueryParametersWithNewQueryString(queryParameters, newQueryString, encoding); requestImpl.getExchange().setQueryString(newQueryString); requestImpl.setQueryParameters(newQueryParameters); } String newRequestUri = servletContext.getContextPath() + newServletPath; requestImpl.getExchange().setRelativePath(newServletPath); requestImpl.getExchange().setRequestPath(newRequestUri); requestImpl.getExchange().setRequestURI(newRequestUri); requestImpl.getExchange().getAttachment(ServletRequestContext.ATTACHMENT_KEY).setServletPathMatch(pathMatch); requestImpl.setServletContext(servletContext); responseImpl.setServletContext(servletContext); try { try { servletRequestContext.setServletRequest(request); servletRequestContext.setServletResponse(response); servletContext.getDeployment().getServletDispatcher().dispatchToPath(requestImpl.getExchange(), pathMatch, DispatcherType.ERROR); } catch (ServletException e) { throw e; } catch (IOException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } } finally { AsyncContextImpl ac = servletRequestContext.getOriginalRequest().getAsyncContextInternal(); if(ac != null) { ac.complete(); } servletRequestContext.setServletRequest(oldRequest); servletRequestContext.setServletResponse(oldResponse); } } public void mock(ServletRequest request, ServletResponse response) throws ServletException, IOException { if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; servletContext.getDeployment().getServletDispatcher().dispatchMockRequest(req, resp); } else { throw UndertowServletMessages.MESSAGES.invalidRequestResponseType(request, response); } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/SecurityActions.java000066400000000000000000000070061420065311100315040ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import java.security.AccessController; import java.security.PrivilegedAction; import javax.servlet.ServletContext; import io.undertow.server.session.Session; import io.undertow.servlet.handlers.ServletRequestContext; class SecurityActions { static ServletRequestContext currentServletRequestContext() { if (System.getSecurityManager() == null) { return ServletRequestContext.current(); } else { return AccessController.doPrivileged(new PrivilegedAction() { @Override public ServletRequestContext run() { return ServletRequestContext.current(); } }); } } static HttpSessionImpl forSession(final Session session, final ServletContext servletContext, final boolean newSession) { if (System.getSecurityManager() == null) { return HttpSessionImpl.forSession(session, servletContext, newSession); } else { return AccessController.doPrivileged(new PrivilegedAction() { @Override public HttpSessionImpl run() { return HttpSessionImpl.forSession(session, servletContext, newSession); } }); } } static void setCurrentRequestContext(final ServletRequestContext servletRequestContext) { if (System.getSecurityManager() == null) { ServletRequestContext.setCurrentRequestContext(servletRequestContext); } else { AccessController.doPrivileged(new PrivilegedAction() { @Override public Object run() { ServletRequestContext.setCurrentRequestContext(servletRequestContext); return null; } }); } } static void clearCurrentServletAttachments() { if (System.getSecurityManager() == null) { ServletRequestContext.clearCurrentServletAttachments(); } else { AccessController.doPrivileged(new PrivilegedAction() { @Override public Object run() { ServletRequestContext.clearCurrentServletAttachments(); return null; } }); } } static ServletRequestContext requireCurrentServletRequestContext() { if (System.getSecurityManager() == null) { return ServletRequestContext.requireCurrent(); } else { return AccessController.doPrivileged(new PrivilegedAction() { @Override public ServletRequestContext run() { return ServletRequestContext.requireCurrent(); } }); } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/ServletConfigImpl.java000066400000000000000000000036721420065311100317550ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import java.util.Enumeration; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import io.undertow.servlet.UndertowServletMessages; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.util.IteratorEnumeration; /** * @author Stuart Douglas */ public class ServletConfigImpl implements ServletConfig { private final ServletInfo servletInfo; private final ServletContext servletContext; public ServletConfigImpl(final ServletInfo servletInfo, final ServletContext servletContext) { this.servletInfo = servletInfo; this.servletContext = servletContext; } @Override public String getServletName() { return servletInfo.getName(); } @Override public ServletContext getServletContext() { return servletContext; } @Override public String getInitParameter(final String name) { if(name == null) { throw UndertowServletMessages.MESSAGES.nullName(); } return servletInfo.getInitParams().get(name); } @Override public Enumeration getInitParameterNames() { return new IteratorEnumeration<>(servletInfo.getInitParams().keySet().iterator()); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/ServletContextImpl.java000066400000000000000000001536641420065311100322030ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import io.undertow.Version; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.cache.LRUCache; import io.undertow.server.handlers.resource.Resource; import io.undertow.server.session.PathParameterSessionConfig; import io.undertow.server.session.Session; import io.undertow.server.session.SessionConfig; import io.undertow.server.session.SessionManager; import io.undertow.server.session.SslSessionConfig; import io.undertow.servlet.UndertowServletLogger; import io.undertow.servlet.UndertowServletMessages; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.HttpMethodSecurityInfo; import io.undertow.servlet.api.InstanceFactory; import io.undertow.servlet.api.ListenerInfo; import io.undertow.servlet.api.SecurityInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.ServletSecurityInfo; import io.undertow.servlet.api.SessionConfigWrapper; import io.undertow.servlet.api.ThreadSetupHandler; import io.undertow.servlet.api.TransportGuaranteeType; import io.undertow.servlet.core.ApplicationListeners; import io.undertow.servlet.core.ManagedListener; import io.undertow.servlet.core.ManagedServlet; import io.undertow.servlet.handlers.ServletChain; import io.undertow.servlet.handlers.ServletHandler; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.servlet.util.EmptyEnumeration; import io.undertow.servlet.util.ImmediateInstanceFactory; import io.undertow.servlet.util.IteratorEnumeration; import io.undertow.util.AttachmentKey; import io.undertow.util.CanonicalPathUtils; import javax.annotation.security.DeclareRoles; import javax.annotation.security.RunAs; import javax.servlet.DispatcherType; import javax.servlet.Filter; import javax.servlet.FilterRegistration; import javax.servlet.MultipartConfigElement; import javax.servlet.ReadListener; import javax.servlet.RequestDispatcher; import javax.servlet.Servlet; import javax.servlet.ServletContext; import javax.servlet.ServletContextListener; import javax.servlet.ServletException; import javax.servlet.ServletRegistration; import javax.servlet.ServletRequest; import javax.servlet.SessionTrackingMode; import javax.servlet.WriteListener; import javax.servlet.annotation.HttpMethodConstraint; import javax.servlet.annotation.MultipartConfig; import javax.servlet.annotation.ServletSecurity; import javax.servlet.descriptor.JspConfigDescriptor; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.Enumeration; import java.util.EventListener; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import static io.undertow.servlet.core.ApplicationListeners.ListenerState.NO_LISTENER; import static io.undertow.servlet.core.ApplicationListeners.ListenerState.PROGRAMATIC_LISTENER; /** * @author Stuart Douglas */ public class ServletContextImpl implements ServletContext { private final ServletContainer servletContainer; private final Deployment deployment; private volatile DeploymentInfo deploymentInfo; private final ConcurrentMap attributes; private final SessionCookieConfigImpl sessionCookieConfig; private final AttachmentKey sessionAttachmentKey = AttachmentKey.create(HttpSessionImpl.class); private volatile Set sessionTrackingModes = new HashSet<>(Arrays.asList(new SessionTrackingMode[]{SessionTrackingMode.COOKIE, SessionTrackingMode.URL})); private volatile Set defaultSessionTrackingModes = new HashSet<>(Arrays.asList(new SessionTrackingMode[]{SessionTrackingMode.COOKIE, SessionTrackingMode.URL})); private volatile SessionConfig sessionConfig; private volatile boolean initialized = false; private int filterMappingUrlPatternInsertPosition = 0; private int filterMappingServletNameInsertPosition = 0; private final LRUCache contentTypeCache; //I don't think these really belong here, but there is not really anywhere else for them //maybe we should move them into a separate class private volatile ThreadSetupHandler.Action onWritePossibleTask; private volatile ThreadSetupHandler.Action runnableTask; private volatile ThreadSetupHandler.Action onDataAvailableTask; private volatile ThreadSetupHandler.Action onAllDataReadTask; private volatile ThreadSetupHandler.Action> invokeActionTask; private volatile int defaultSessionTimeout; public ServletContextImpl(final ServletContainer servletContainer, final Deployment deployment) { this.servletContainer = servletContainer; this.deployment = deployment; this.deploymentInfo = deployment.getDeploymentInfo(); sessionCookieConfig = new SessionCookieConfigImpl(this); sessionCookieConfig.setPath(deploymentInfo.getContextPath()); if (deploymentInfo.getServletContextAttributeBackingMap() == null) { this.attributes = new ConcurrentHashMap<>(); } else { this.attributes = deploymentInfo.getServletContextAttributeBackingMap(); } attributes.putAll(deployment.getDeploymentInfo().getServletContextAttributes()); this.contentTypeCache = new LRUCache<>(deployment.getDeploymentInfo().getContentTypeCacheSize(), -1, true); this.defaultSessionTimeout = deploymentInfo.getDefaultSessionTimeout() / 60; } public void initDone() { initialized = true; Set trackingMethods = sessionTrackingModes; SessionConfig sessionConfig = sessionCookieConfig; if (trackingMethods != null && !trackingMethods.isEmpty()) { if (sessionTrackingModes.contains(SessionTrackingMode.SSL)) { sessionConfig = new SslSessionConfig(deployment.getSessionManager()); } else { if (sessionTrackingModes.contains(SessionTrackingMode.COOKIE) && sessionTrackingModes.contains(SessionTrackingMode.URL)) { sessionCookieConfig.setFallback(new PathParameterSessionConfig(sessionCookieConfig.getName().toLowerCase(Locale.ENGLISH))); } else if (sessionTrackingModes.contains(SessionTrackingMode.URL)) { sessionConfig = new PathParameterSessionConfig(sessionCookieConfig.getName().toLowerCase(Locale.ENGLISH)); } } } SessionConfigWrapper wrapper = deploymentInfo.getSessionConfigWrapper(); if (wrapper != null) { sessionConfig = wrapper.wrap(sessionConfig, deployment); } this.sessionConfig = new ServletContextSessionConfig(sessionConfig); this.onWritePossibleTask = deployment.createThreadSetupAction(new ThreadSetupHandler.Action() { @Override public Void call(HttpServerExchange exchange, WriteListener context) throws Exception { context.onWritePossible(); return null; } }); this.runnableTask = deployment.createThreadSetupAction(new ThreadSetupHandler.Action() { @Override public Void call(HttpServerExchange exchange, Runnable runnable) throws Exception { runnable.run(); return null; } }); this.onDataAvailableTask = deployment.createThreadSetupAction(new ThreadSetupHandler.Action() { @Override public Void call(HttpServerExchange exchange, ReadListener context) throws Exception { context.onDataAvailable(); return null; } }); this.onAllDataReadTask = deployment.createThreadSetupAction(new ThreadSetupHandler.Action() { @Override public Void call(HttpServerExchange exchange, ReadListener context) throws Exception { context.onAllDataRead(); return null; } }); this.invokeActionTask = deployment.createThreadSetupAction(new ThreadSetupHandler.Action>() { @Override public Void call(HttpServerExchange exchange, ThreadSetupHandler.Action context) throws Exception { context.call(exchange, null); return null; } }); } private DeploymentInfo getDeploymentInfo() { final DeploymentInfo deploymentInfo = this.deploymentInfo; if (deploymentInfo == null) throw UndertowServletLogger.ROOT_LOGGER.contextDestroyed(); return deploymentInfo; } @Override public String getContextPath() { String contextPath = getDeploymentInfo().getContextPath(); if (contextPath.equals("/")) { return ""; } return contextPath; } @Override public ServletContext getContext(final String uripath) { DeploymentManager deploymentByPath = servletContainer.getDeploymentByPath(uripath); if (deploymentByPath == null) { return null; } return deploymentByPath.getDeployment().getServletContext(); } @Override public int getMajorVersion() { return getDeploymentInfo().getContainerMajorVersion(); } @Override public int getMinorVersion() { return getDeploymentInfo().getContainerMinorVersion(); } @Override public int getEffectiveMajorVersion() { return getDeploymentInfo().getMajorVersion(); } @Override public int getEffectiveMinorVersion() { return getDeploymentInfo().getMinorVersion(); } @Override public String getMimeType(final String file) { if(file == null) { return null; } String lower = file.toLowerCase(Locale.ENGLISH); int pos = lower.lastIndexOf('.'); if (pos == -1) { return null; //no extension } return deployment.getMimeExtensionMappings().get(lower.substring(pos + 1)); } @Override public Set getResourcePaths(final String path) { final Resource resource; try { resource = getDeploymentInfo().getResourceManager().getResource(path); } catch (IOException e) { return null; } if (resource == null || !resource.isDirectory()) { return null; } final Set resources = new HashSet<>(); for (Resource res : resource.list()) { Path file = res.getFilePath(); if (file != null) { Path base = res.getResourceManagerRootPath(); if (base == null) { resources.add(file.toString()); //not much else we can do here } else { String filePath = file.toAbsolutePath().toString().substring(base.toAbsolutePath().toString().length()); filePath = filePath.replace('\\', '/'); //for windows systems if (Files.isDirectory(file)) { filePath = filePath + "/"; } resources.add(filePath); } } } return resources; } @Override public URL getResource(final String path) throws MalformedURLException { if (path == null || !path.startsWith("/")) { throw UndertowServletMessages.MESSAGES.pathMustStartWithSlash(path); } Resource resource = null; try { resource = getDeploymentInfo().getResourceManager().getResource(path); } catch (IOException e) { return null; } if (resource == null) { return null; } return resource.getUrl(); } @Override public InputStream getResourceAsStream(final String path) { Resource resource = null; try { resource = getDeploymentInfo().getResourceManager().getResource(path); } catch (IOException e) { return null; } if (resource == null) { return null; } try { if (resource.getFile() != null) { return new BufferedInputStream(new FileInputStream(resource.getFile())); } else { return new BufferedInputStream(resource.getUrl().openStream()); } } catch (FileNotFoundException e) { //should never happen, as the resource loader should return null in this case return null; } catch (IOException e) { return null; } } @Override public RequestDispatcher getRequestDispatcher(final String path) { if (path == null) { return null; } if (!path.startsWith("/")) { throw UndertowServletMessages.MESSAGES.pathMustStartWithSlashForRequestDispatcher(path); } final String realPath = CanonicalPathUtils.canonicalize(path, true); if (realPath == null) { // path is outside the servlet context, return null per spec return null; } return new RequestDispatcherImpl(realPath, this); } @Override public RequestDispatcher getNamedDispatcher(final String name) { ServletChain chain = deployment.getServletPaths().getServletHandlerByName(name); if (chain != null) { return new RequestDispatcherImpl(chain, this); } else { return null; } } @Override public Servlet getServlet(final String name) throws ServletException { return deployment.getServletPaths().getServletHandlerByName(name).getManagedServlet().getServlet().getInstance(); } @Override public Enumeration getServlets() { return EmptyEnumeration.instance(); } @Override public Enumeration getServletNames() { return EmptyEnumeration.instance(); } @Override public void log(final String msg) { UndertowServletLogger.ROOT_LOGGER.info(msg); } @Override public void log(final Exception exception, final String msg) { UndertowServletLogger.ROOT_LOGGER.error(msg, exception); } @Override public void log(final String message, final Throwable throwable) { UndertowServletLogger.ROOT_LOGGER.error(message, throwable); } @Override public String getRealPath(final String path) { if (path == null) { return null; } final DeploymentInfo deploymentInfo = getDeploymentInfo(); String canonicalPath = CanonicalPathUtils.canonicalize(path); Resource resource; try { resource = deploymentInfo.getResourceManager().getResource(canonicalPath); if (resource == null) { //UNDERTOW-373 even though the resource does not exist we still need to return a path Resource deploymentRoot = deploymentInfo.getResourceManager().getResource("/"); if(deploymentRoot == null) { return null; } Path root = deploymentRoot.getFilePath(); if(root == null) { return null; } if(!canonicalPath.startsWith("/")) { canonicalPath = "/" + canonicalPath; } if(File.separatorChar != '/') { canonicalPath = canonicalPath.replace('/', File.separatorChar); } return root.toAbsolutePath().toString() + canonicalPath; } } catch (IOException e) { return null; } Path file = resource.getFilePath(); if (file == null) { return null; } return file.toAbsolutePath().toString(); } @Override public String getServerInfo() { return getDeploymentInfo().getServerName() + " - " + Version.getVersionString(); } @Override public String getInitParameter(final String name) { if (name == null) { throw UndertowServletMessages.MESSAGES.nullName(); } return getDeploymentInfo().getInitParameters().get(name); } @Override public Enumeration getInitParameterNames() { return new IteratorEnumeration<>(getDeploymentInfo().getInitParameters().keySet().iterator()); } @Override public boolean setInitParameter(final String name, final String value) { if(name == null) { throw UndertowServletMessages.MESSAGES.paramCannotBeNullNPE("name"); } if (getDeploymentInfo().getInitParameters().containsKey(name)) { return false; } getDeploymentInfo().addInitParameter(name, value); return true; } @Override public Object getAttribute(final String name) { if (name == null) { throw UndertowServletMessages.MESSAGES.nullName(); } return attributes.get(name); } @Override public Enumeration getAttributeNames() { return new IteratorEnumeration<>(attributes.keySet().iterator()); } @Override public void setAttribute(final String name, final Object object) { if (name == null) { throw UndertowServletMessages.MESSAGES.nullName(); } if (object == null) { Object existing = attributes.remove(name); if (deployment.getApplicationListeners() != null) { if (existing != null) { deployment.getApplicationListeners().servletContextAttributeRemoved(name, existing); } } } else { Object existing = attributes.put(name, object); if (deployment.getApplicationListeners() != null) { if (existing != null) { deployment.getApplicationListeners().servletContextAttributeReplaced(name, existing); } else { deployment.getApplicationListeners().servletContextAttributeAdded(name, object); } } } } @Override public void removeAttribute(final String name) { Object exiting = attributes.remove(name); deployment.getApplicationListeners().servletContextAttributeRemoved(name, exiting); } @Override public String getServletContextName() { return getDeploymentInfo().getDisplayName(); } @Override public ServletRegistration.Dynamic addServlet(final String servletName, final String className) { return addServlet(servletName, className, Collections.emptyList()); } public ServletRegistration.Dynamic addServlet(final String servletName, final String className, List wrappers) { ensureNotProgramaticListener(); ensureNotInitialized(); ensureServletNameNotNull(servletName); try { final DeploymentInfo deploymentInfo = getDeploymentInfo(); if (deploymentInfo.getServlets().containsKey(servletName)) { return null; } Class servletClass=(Class) deploymentInfo.getClassLoader().loadClass(className); ServletInfo servlet = new ServletInfo(servletName, servletClass, deploymentInfo.getClassIntrospecter().createInstanceFactory(servletClass)); for(HandlerWrapper i : wrappers) { servlet.addHandlerChainWrapper(i); } readServletAnnotations(servlet, deploymentInfo); deploymentInfo.addServlet(servlet); ServletHandler handler = deployment.getServlets().addServlet(servlet); return new ServletRegistrationImpl(servlet, handler.getManagedServlet(), deployment); } catch (ClassNotFoundException e) { throw UndertowServletMessages.MESSAGES.cannotLoadClass(className, e); } catch (NoSuchMethodException e) { throw UndertowServletMessages.MESSAGES.couldNotCreateFactory(className,e); } } @Override public ServletRegistration.Dynamic addServlet(final String servletName, final Servlet servlet) { ensureNotProgramaticListener(); ensureNotInitialized(); ensureServletNameNotNull(servletName); final DeploymentInfo deploymentInfo = getDeploymentInfo(); if (deploymentInfo.getServlets().containsKey(servletName)) { return null; } ServletInfo s = new ServletInfo(servletName, servlet.getClass(), new ImmediateInstanceFactory<>(servlet)); readServletAnnotations(s, deploymentInfo); deploymentInfo.addServlet(s); ServletHandler handler = deployment.getServlets().addServlet(s); return new ServletRegistrationImpl(s, handler.getManagedServlet(), deployment); } @Override public ServletRegistration.Dynamic addServlet(final String servletName, final Class servletClass){ ensureNotProgramaticListener(); ensureNotInitialized(); ensureServletNameNotNull(servletName); final DeploymentInfo deploymentInfo = getDeploymentInfo(); if (deploymentInfo.getServlets().containsKey(servletName)) { return null; } try { ServletInfo servlet = new ServletInfo(servletName, servletClass, deploymentInfo.getClassIntrospecter().createInstanceFactory(servletClass)); readServletAnnotations(servlet, deploymentInfo); deploymentInfo.addServlet(servlet); ServletHandler handler = deployment.getServlets().addServlet(servlet); return new ServletRegistrationImpl(servlet, handler.getManagedServlet(), deployment); } catch (NoSuchMethodException e) { throw UndertowServletMessages.MESSAGES.couldNotCreateFactory(servletClass.getName(),e); } } private void ensureServletNameNotNull(String servletName) { if(servletName == null) { throw UndertowServletMessages.MESSAGES.servletNameNull(); } } @Override public T createServlet(final Class clazz) throws ServletException { ensureNotProgramaticListener(); try { return getDeploymentInfo().getClassIntrospecter().createInstanceFactory(clazz).createInstance().getInstance(); } catch (Exception e) { throw UndertowServletMessages.MESSAGES.couldNotInstantiateComponent(clazz.getName(), e); } } @Override public ServletRegistration getServletRegistration(final String servletName) { ensureNotProgramaticListener(); final ManagedServlet servlet = deployment.getServlets().getManagedServlet(servletName); if (servlet == null) { return null; } return new ServletRegistrationImpl(servlet.getServletInfo(), servlet, deployment); } @Override public Map getServletRegistrations() { ensureNotProgramaticListener(); final Map ret = new HashMap<>(); for (Map.Entry entry : deployment.getServlets().getServletHandlers().entrySet()) { ret.put(entry.getKey(), new ServletRegistrationImpl(entry.getValue().getManagedServlet().getServletInfo(), entry.getValue().getManagedServlet(), deployment)); } return ret; } @Override public FilterRegistration.Dynamic addFilter(final String filterName, final String className) { ensureNotProgramaticListener(); ensureNotInitialized(); final DeploymentInfo deploymentInfo = getDeploymentInfo(); if (deploymentInfo.getFilters().containsKey(filterName)) { return null; } try { Class filterClass=(Class) deploymentInfo.getClassLoader().loadClass(className); FilterInfo filter = new FilterInfo(filterName, filterClass, deploymentInfo.getClassIntrospecter().createInstanceFactory(filterClass)); deploymentInfo.addFilter(filter); deployment.getFilters().addFilter(filter); return new FilterRegistrationImpl(filter, deployment, this); } catch (ClassNotFoundException e) { throw UndertowServletMessages.MESSAGES.cannotLoadClass(className, e); }catch (NoSuchMethodException e) { throw UndertowServletMessages.MESSAGES.couldNotCreateFactory(className,e); } } @Override public FilterRegistration.Dynamic addFilter(final String filterName, final Filter filter) { ensureNotProgramaticListener(); ensureNotInitialized(); final DeploymentInfo deploymentInfo = getDeploymentInfo(); if (deploymentInfo.getFilters().containsKey(filterName)) { return null; } FilterInfo f = new FilterInfo(filterName, filter.getClass(), new ImmediateInstanceFactory<>(filter)); deploymentInfo.addFilter(f); deployment.getFilters().addFilter(f); return new FilterRegistrationImpl(f, deployment, this); } @Override public FilterRegistration.Dynamic addFilter(final String filterName, final Class filterClass) { ensureNotProgramaticListener(); ensureNotInitialized(); final DeploymentInfo deploymentInfo = getDeploymentInfo(); if (deploymentInfo.getFilters().containsKey(filterName)) { return null; } try { FilterInfo filter = new FilterInfo(filterName, filterClass,deploymentInfo.getClassIntrospecter().createInstanceFactory(filterClass)); deploymentInfo.addFilter(filter); deployment.getFilters().addFilter(filter); return new FilterRegistrationImpl(filter, deployment, this); } catch (NoSuchMethodException e) { throw UndertowServletMessages.MESSAGES.couldNotCreateFactory(filterClass.getName(),e); } } @Override public T createFilter(final Class clazz) throws ServletException { ensureNotProgramaticListener(); try { return getDeploymentInfo().getClassIntrospecter().createInstanceFactory(clazz).createInstance().getInstance(); } catch (Exception e) { throw UndertowServletMessages.MESSAGES.couldNotInstantiateComponent(clazz.getName(), e); } } @Override public FilterRegistration getFilterRegistration(final String filterName) { ensureNotProgramaticListener(); final FilterInfo filterInfo = getDeploymentInfo().getFilters().get(filterName); if (filterInfo == null) { return null; } return new FilterRegistrationImpl(filterInfo, deployment, this); } @Override public Map getFilterRegistrations() { ensureNotProgramaticListener(); final Map ret = new HashMap<>(); for (Map.Entry entry : getDeploymentInfo().getFilters().entrySet()) { ret.put(entry.getKey(), new FilterRegistrationImpl(entry.getValue(), deployment, this)); } return ret; } @Override public SessionCookieConfigImpl getSessionCookieConfig() { ensureNotProgramaticListener(); return sessionCookieConfig; } @Override public void setSessionTrackingModes(final Set sessionTrackingModes) { ensureNotProgramaticListener(); ensureNotInitialized(); if (sessionTrackingModes.size() > 1 && sessionTrackingModes.contains(SessionTrackingMode.SSL)) { throw UndertowServletMessages.MESSAGES.sslCannotBeCombinedWithAnyOtherMethod(); } this.sessionTrackingModes = new HashSet<>(sessionTrackingModes); //TODO: actually make this work } @Override public Set getDefaultSessionTrackingModes() { ensureNotProgramaticListener(); return defaultSessionTrackingModes; } @Override public Set getEffectiveSessionTrackingModes() { ensureNotProgramaticListener(); return Collections.unmodifiableSet(sessionTrackingModes); } @Override public void addListener(final String className) { try { Class clazz = (Class) getDeploymentInfo().getClassLoader().loadClass(className); addListener(clazz); } catch (ClassNotFoundException e) { throw new IllegalArgumentException(e); } } @Override public void addListener(final T t) { ensureNotInitialized(); ensureNotProgramaticListener(); if (ApplicationListeners.listenerState() != NO_LISTENER && ServletContextListener.class.isAssignableFrom(t.getClass())) { throw UndertowServletMessages.MESSAGES.cannotAddServletContextListener(); } ListenerInfo listener = new ListenerInfo(t.getClass(), new ImmediateInstanceFactory(t)); getDeploymentInfo().addListener(listener); deployment.getApplicationListeners().addListener(new ManagedListener(listener, true)); } @Override public void addListener(final Class listenerClass) { ensureNotInitialized(); ensureNotProgramaticListener(); if (ApplicationListeners.listenerState() != NO_LISTENER && ServletContextListener.class.isAssignableFrom(listenerClass)) { throw UndertowServletMessages.MESSAGES.cannotAddServletContextListener(); } final DeploymentInfo deploymentInfo = getDeploymentInfo(); InstanceFactory factory = null; try { factory = deploymentInfo.getClassIntrospecter().createInstanceFactory(listenerClass); } catch (Exception e) { throw new IllegalArgumentException(e); } final ListenerInfo listener = new ListenerInfo(listenerClass, factory); deploymentInfo.addListener(listener); deployment.getApplicationListeners().addListener(new ManagedListener(listener, true)); } @Override public T createListener(final Class clazz) throws ServletException { ensureNotProgramaticListener(); if (!ApplicationListeners.isListenerClass(clazz)) { throw UndertowServletMessages.MESSAGES.listenerMustImplementListenerClass(clazz); } try { return getDeploymentInfo().getClassIntrospecter().createInstanceFactory(clazz).createInstance().getInstance(); } catch (Exception e) { throw UndertowServletMessages.MESSAGES.couldNotInstantiateComponent(clazz.getName(), e); } } @Override public JspConfigDescriptor getJspConfigDescriptor() { return getDeploymentInfo().getJspConfigDescriptor(); } @Override public ClassLoader getClassLoader() { return getDeploymentInfo().getClassLoader(); } @Override public void declareRoles(final String... roleNames) { final DeploymentInfo di = this.getDeploymentInfo(); if (isInitialized()) { throw UndertowServletMessages.MESSAGES.servletAlreadyInitialize(di.getDeploymentName(), di.getContextPath()); } for (String role : roleNames) { if (role == null || role.isEmpty()) { throw UndertowServletMessages.MESSAGES.roleMustNotBeEmpty(di.getDeploymentName(), di.getContextPath()); } } if (ApplicationListeners.listenerState() == PROGRAMATIC_LISTENER) { //NOTE: its either null or false for non-programatic? - null in case its not ApplicationListener throw UndertowServletMessages.MESSAGES.cantCallFromDynamicListener(di.getDeploymentName(), di.getContextPath()); } deploymentInfo.addSecurityRoles(roleNames); } @Override public ServletRegistration.Dynamic addJspFile(String servletName, String jspFile) { if(servletName == null || servletName.isEmpty()) { throw UndertowServletMessages.MESSAGES.paramCannotBeNull("servletName"); } return addServlet(servletName, "org.apache.jasper.servlet.JspServlet", Collections.singletonList(handler -> exchange -> { ServletRequest request = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getServletRequest(); request.setAttribute(System.getProperty("org.apache.jasper.Constants.JSP_FILE", "org.apache.catalina.jsp_file"), jspFile); handler.handleRequest(exchange); })); } @Override public int getSessionTimeout() { return defaultSessionTimeout; } @Override public void setSessionTimeout(int sessionTimeout) { ensureNotInitialized(); ensureNotProgramaticListener(); this.defaultSessionTimeout = sessionTimeout; deployment.getSessionManager().setDefaultSessionTimeout(sessionTimeout * 60); } @Override public String getRequestCharacterEncoding() { final DeploymentInfo deploymentInfo = getDeploymentInfo(); String enconding = deploymentInfo.getDefaultRequestEncoding(); if(enconding != null) { return enconding; } return deploymentInfo.getDefaultEncoding(); } @Override public void setRequestCharacterEncoding(String encoding) { ensureNotInitialized(); ensureNotProgramaticListener(); getDeploymentInfo().setDefaultRequestEncoding(encoding); } @Override public String getResponseCharacterEncoding() { final DeploymentInfo deploymentInfo = getDeploymentInfo(); String enconding = deploymentInfo.getDefaultResponseEncoding(); if(enconding != null) { return enconding; } return deploymentInfo.getDefaultEncoding(); } @Override public void setResponseCharacterEncoding(String encoding) { ensureNotInitialized(); ensureNotProgramaticListener(); getDeploymentInfo().setDefaultResponseEncoding(encoding); } @Override public String getVirtualServerName() { return deployment.getDeploymentInfo().getHostName(); } /** * Gets the session with the specified ID if it exists * * @param sessionId The session ID * @return The session */ public HttpSessionImpl getSession(final String sessionId) { final SessionManager sessionManager = deployment.getSessionManager(); Session session = sessionManager.getSession(sessionId); if (session != null) { return SecurityActions.forSession(session, this, false); } return null; } public HttpSessionImpl getSession(final ServletContextImpl originalServletContext, final HttpServerExchange exchange, boolean create) { SessionConfig c = originalServletContext.getSessionConfig(); HttpSessionImpl httpSession = exchange.getAttachment(sessionAttachmentKey); if (httpSession != null && httpSession.isInvalid()) { exchange.removeAttachment(sessionAttachmentKey); httpSession = null; } if (httpSession == null) { final SessionManager sessionManager = deployment.getSessionManager(); Session session = sessionManager.getSession(exchange, c); if (session != null) { httpSession = SecurityActions.forSession(session, this, false); exchange.putAttachment(sessionAttachmentKey, httpSession); } else if (create) { String existing = c.findSessionId(exchange); Boolean isRequestedSessionIdSaved = exchange.getAttachment(HttpServletRequestImpl.REQUESTED_SESSION_ID_SET); if (isRequestedSessionIdSaved == null || !isRequestedSessionIdSaved) { exchange.putAttachment(HttpServletRequestImpl.REQUESTED_SESSION_ID_SET, Boolean.TRUE); exchange.putAttachment(HttpServletRequestImpl.REQUESTED_SESSION_ID, existing); } if (originalServletContext != this) { //this is a cross context request //we need to make sure there is a top level session final HttpSessionImpl topLevel = originalServletContext.getSession(originalServletContext, exchange, true); //override the session id to just return the same ID as the top level session c = new SessionConfig() { @Override public void setSessionId(HttpServerExchange exchange, String sessionId) { //noop } @Override public void clearSession(HttpServerExchange exchange, String sessionId) { //noop } @Override public String findSessionId(HttpServerExchange exchange) { return topLevel.getId(); } @Override public SessionCookieSource sessionCookieSource(HttpServerExchange exchange) { return SessionCookieSource.NONE; } @Override public String rewriteUrl(String originalUrl, String sessionId) { return null; } }; //first we check if there is a session with this id already //this can happen with a shared session manager session = sessionManager.getSession(exchange, c); if (session != null) { httpSession = SecurityActions.forSession(session, this, false); exchange.putAttachment(sessionAttachmentKey, httpSession); } } else if (existing != null) { if(getDeploymentInfo().isCheckOtherSessionManagers()) { boolean found = false; for (String deploymentName : deployment.getServletContainer().listDeployments()) { DeploymentManager deployment = this.deployment.getServletContainer().getDeployment(deploymentName); if (deployment != null) { if (deployment.getDeployment().getSessionManager().getSession(existing) != null) { found = true; break; } } } if (!found) { c.clearSession(exchange, existing); } } else { c.clearSession(exchange, existing); } } if (httpSession == null) { final Session newSession = sessionManager.createSession(exchange, c); httpSession = SecurityActions.forSession(newSession, this, true); exchange.putAttachment(sessionAttachmentKey, httpSession); } } } return httpSession; } /** * Gets the session * * @param create * @return */ public HttpSessionImpl getSession(final HttpServerExchange exchange, boolean create) { return getSession(this, exchange, create); } public void updateSessionAccessTime(final HttpServerExchange exchange) { HttpSessionImpl httpSession = getSession(exchange, false); if (httpSession != null) { Session underlyingSession; if (System.getSecurityManager() == null) { underlyingSession = httpSession.getSession(); } else { underlyingSession = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(httpSession)); } underlyingSession.requestDone(exchange); } } public Deployment getDeployment() { return deployment; } private void ensureNotInitialized() { if (initialized) { throw UndertowServletMessages.MESSAGES.servletContextAlreadyInitialized(); } } private void ensureNotProgramaticListener() { if (ApplicationListeners.listenerState() == PROGRAMATIC_LISTENER) { throw UndertowServletMessages.MESSAGES.cannotCallFromProgramaticListener(); } } boolean isInitialized() { return initialized; } public SessionConfig getSessionConfig() { return sessionConfig; } public void destroy() { attributes.clear(); deploymentInfo = null; } private void readServletAnnotations(ServletInfo servlet, DeploymentInfo deploymentInfo) { if (System.getSecurityManager() == null) { new ReadServletAnnotationsTask(servlet, deploymentInfo).run(); } else { AccessController.doPrivileged(new ReadServletAnnotationsTask(servlet, deploymentInfo)); } } public void setDefaultSessionTrackingModes(HashSet sessionTrackingModes) { this.defaultSessionTrackingModes = sessionTrackingModes; this.sessionTrackingModes = sessionTrackingModes; } void invokeOnWritePossible(HttpServerExchange exchange, WriteListener listener) { try { this.onWritePossibleTask.call(exchange, listener); } catch (Exception e) { throw new RuntimeException(e); } } void invokeOnAllDataRead(HttpServerExchange exchange, ReadListener listener) { try { this.onAllDataReadTask.call(exchange, listener); } catch (Exception e) { throw new RuntimeException(e); } } void invokeOnDataAvailable(HttpServerExchange exchange, ReadListener listener) { try { this.onDataAvailableTask.call(exchange, listener); } catch (Exception e) { throw new RuntimeException(e); } } void invokeAction(HttpServerExchange exchange, ThreadSetupHandler.Action listener) { try { this.invokeActionTask.call(exchange, listener); } catch (Exception e) { throw new RuntimeException(e); } } void invokeRunnable(HttpServerExchange exchange, Runnable runnable) { final boolean setupRequired = SecurityActions.currentServletRequestContext() == null; if(setupRequired) { try { this.runnableTask.call(exchange, runnable); } catch (Exception e) { throw new RuntimeException(e); } } else { runnable.run(); } } private static final class ReadServletAnnotationsTask implements PrivilegedAction { private final ServletInfo servletInfo; private final DeploymentInfo deploymentInfo; private ReadServletAnnotationsTask(ServletInfo servletInfo, DeploymentInfo deploymentInfo) { this.servletInfo = servletInfo; this.deploymentInfo = deploymentInfo; } @Override public Void run() { final ServletSecurity security = servletInfo.getServletClass().getAnnotation(ServletSecurity.class); if (security != null) { ServletSecurityInfo servletSecurityInfo = new ServletSecurityInfo() .setEmptyRoleSemantic(security.value().value() == ServletSecurity.EmptyRoleSemantic.DENY ? SecurityInfo.EmptyRoleSemantic.DENY : SecurityInfo.EmptyRoleSemantic.PERMIT) .setTransportGuaranteeType(security.value().transportGuarantee() == ServletSecurity.TransportGuarantee.CONFIDENTIAL ? TransportGuaranteeType.CONFIDENTIAL : TransportGuaranteeType.NONE) .addRolesAllowed(security.value().rolesAllowed()); for (HttpMethodConstraint constraint : security.httpMethodConstraints()) { servletSecurityInfo.addHttpMethodSecurityInfo(new HttpMethodSecurityInfo() .setMethod(constraint.value())) .setEmptyRoleSemantic(constraint.emptyRoleSemantic() == ServletSecurity.EmptyRoleSemantic.DENY ? SecurityInfo.EmptyRoleSemantic.DENY : SecurityInfo.EmptyRoleSemantic.PERMIT) .setTransportGuaranteeType(constraint.transportGuarantee() == ServletSecurity.TransportGuarantee.CONFIDENTIAL ? TransportGuaranteeType.CONFIDENTIAL : TransportGuaranteeType.NONE) .addRolesAllowed(constraint.rolesAllowed()); } servletInfo.setServletSecurityInfo(servletSecurityInfo); } final MultipartConfig multipartConfig = servletInfo.getServletClass().getAnnotation(MultipartConfig.class); if (multipartConfig != null) { servletInfo.setMultipartConfig(new MultipartConfigElement(multipartConfig.location(), multipartConfig.maxFileSize(), multipartConfig.maxRequestSize(), multipartConfig.fileSizeThreshold())); } final RunAs runAs = servletInfo.getServletClass().getAnnotation(RunAs.class); if (runAs != null) { servletInfo.setRunAs(runAs.value()); } final DeclareRoles declareRoles = servletInfo.getServletClass().getAnnotation(DeclareRoles.class); if (declareRoles != null) { deploymentInfo.addSecurityRoles(declareRoles.value()); } return null; } } void addMappingForServletNames(FilterInfo filterInfo, final EnumSet dispatcherTypes, final boolean isMatchAfter, final String... servletNames) { DeploymentInfo deploymentInfo = deployment.getDeploymentInfo(); for (final String servlet : servletNames) { if (isMatchAfter) { if (dispatcherTypes == null || dispatcherTypes.isEmpty()) { deploymentInfo.addFilterServletNameMapping(filterInfo.getName(), servlet, DispatcherType.REQUEST); } else { for (final DispatcherType dispatcher : dispatcherTypes) { deploymentInfo.addFilterServletNameMapping(filterInfo.getName(), servlet, dispatcher); } } } else { if (dispatcherTypes == null || dispatcherTypes.isEmpty()) { deploymentInfo.insertFilterServletNameMapping(filterMappingServletNameInsertPosition++, filterInfo.getName(), servlet, DispatcherType.REQUEST); } else { for (final DispatcherType dispatcher : dispatcherTypes) { deploymentInfo.insertFilterServletNameMapping(filterMappingServletNameInsertPosition++, filterInfo.getName(), servlet, dispatcher); } } } } deployment.getServletPaths().invalidate(); } void addMappingForUrlPatterns(FilterInfo filterInfo, final EnumSet dispatcherTypes, final boolean isMatchAfter, final String... urlPatterns) { DeploymentInfo deploymentInfo = deployment.getDeploymentInfo(); for (final String url : urlPatterns) { if (isMatchAfter) { if (dispatcherTypes == null || dispatcherTypes.isEmpty()) { deploymentInfo.addFilterUrlMapping(filterInfo.getName(), url, DispatcherType.REQUEST); } else { for (final DispatcherType dispatcher : dispatcherTypes) { deploymentInfo.addFilterUrlMapping(filterInfo.getName(), url, dispatcher); } } } else { if (dispatcherTypes == null || dispatcherTypes.isEmpty()) { deploymentInfo.insertFilterUrlMapping(filterMappingUrlPatternInsertPosition++, filterInfo.getName(), url, DispatcherType.REQUEST); } else { for (final DispatcherType dispatcher : dispatcherTypes) { deploymentInfo.insertFilterUrlMapping(filterMappingUrlPatternInsertPosition++, filterInfo.getName(), url, dispatcher); } } } } deployment.getServletPaths().invalidate(); } ContentTypeInfo parseContentType(String type) { ContentTypeInfo existing = contentTypeCache.get(type); if(existing != null) { return existing; } String contentType = type; String charset = null; int split = type.indexOf(";"); if (split != -1) { int pos = type.indexOf("charset="); if (pos != -1) { int i = pos + "charset=".length(); do { char c = type.charAt(i); if (c == ' ' || c == '\t' || c == ';') { break; } ++i; } while (i < type.length()); charset = type.substring(pos + "charset=".length(), i); //it is valid for the charset to be enclosed in quotes if (charset.startsWith("\"") && charset.endsWith("\"") && charset.length() > 1) { charset = charset.substring(1, charset.length() - 1); } int charsetStart = pos; while (type.charAt(--charsetStart) != ';' && charsetStart > 0) { } StringBuilder contentTypeBuilder = new StringBuilder(); contentTypeBuilder.append(type.substring(0, charsetStart)); if (i != type.length()) { contentTypeBuilder.append(type.substring(i)); } contentType = contentTypeBuilder.toString(); } //strip any trailing semicolon for (int i = contentType.length() - 1; i >= 0; --i) { char c = contentType.charAt(i); if (c == ' ' || c == '\t') { continue; } if (c == ';') { contentType = contentType.substring(0, i); } break; } } if(charset == null) { existing = new ContentTypeInfo(contentType, null, contentType); } else { existing = new ContentTypeInfo(contentType + ";charset=" + charset, charset, contentType); } contentTypeCache.add(type, existing); return existing; } /** * This is a bit of a hack to make sure than an invalidated session ID is not re-used. It also allows {@link io.undertow.servlet.handlers.ServletRequestContext#getOverridenSessionId()} to be used. */ static final class ServletContextSessionConfig implements SessionConfig { private final AttachmentKey INVALIDATED = AttachmentKey.create(String.class); private final SessionConfig delegate; private ServletContextSessionConfig(SessionConfig delegate) { this.delegate = delegate; } @Override public void setSessionId(HttpServerExchange exchange, String sessionId) { delegate.setSessionId(exchange, sessionId); } @Override public void clearSession(HttpServerExchange exchange, String sessionId) { exchange.putAttachment(INVALIDATED, sessionId); Boolean isRequestedSessionIdSaved = exchange.getAttachment(HttpServletRequestImpl.REQUESTED_SESSION_ID_SET); if (isRequestedSessionIdSaved == null || !isRequestedSessionIdSaved) { exchange.putAttachment(HttpServletRequestImpl.REQUESTED_SESSION_ID_SET, Boolean.TRUE); exchange.putAttachment(HttpServletRequestImpl.REQUESTED_SESSION_ID, sessionId); } delegate.clearSession(exchange, sessionId); } @Override public String findSessionId(HttpServerExchange exchange) { String invalidated = exchange.getAttachment(INVALIDATED); ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); final String current; if(src.getOverridenSessionId() == null) { current = delegate.findSessionId(exchange); } else { current = src.getOverridenSessionId(); } if(invalidated == null) { return current; } if(invalidated.equals(current)) { return null; } return current; } @Override public SessionCookieSource sessionCookieSource(HttpServerExchange exchange) { return delegate.sessionCookieSource(exchange); } @Override public String rewriteUrl(String originalUrl, String sessionId) { return delegate.rewriteUrl(originalUrl, sessionId); } public SessionConfig getDelegate() { return delegate; } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/ServletCookieAdaptor.java000066400000000000000000000137051420065311100324500ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import java.util.Arrays; import java.util.Date; import io.undertow.UndertowMessages; import io.undertow.server.handlers.Cookie; import io.undertow.server.handlers.CookieSameSiteMode; import io.undertow.servlet.UndertowServletLogger; import io.undertow.servlet.UndertowServletMessages; /** * Adaptor between and undertow and a servlet cookie * * @author Stuart Douglas * @author Richard Opalka */ public class ServletCookieAdaptor implements Cookie { private final javax.servlet.http.Cookie cookie; private boolean sameSite; private String sameSiteMode; public ServletCookieAdaptor(final javax.servlet.http.Cookie cookie) { this.cookie = cookie; } @Override public String getName() { return cookie.getName(); } @Override public String getValue() { return cookie.getValue(); } @Override public Cookie setValue(final String value) { cookie.setValue(value); return this; } @Override public String getPath() { return cookie.getPath(); } @Override public Cookie setPath(final String path) { cookie.setPath(path); return this; } @Override public String getDomain() { return cookie.getDomain(); } @Override public Cookie setDomain(final String domain) { cookie.setDomain(domain); return this; } @Override public Integer getMaxAge() { return cookie.getMaxAge(); } @Override public Cookie setMaxAge(final Integer maxAge) { cookie.setMaxAge(maxAge); return this; } @Override public boolean isDiscard() { return cookie.getMaxAge() < 0; } @Override public Cookie setDiscard(final boolean discard) { return this; } @Override public boolean isSecure() { return cookie.getSecure(); } @Override public Cookie setSecure(final boolean secure) { cookie.setSecure(secure); return this; } @Override public int getVersion() { return cookie.getVersion(); } @Override public Cookie setVersion(final int version) { cookie.setVersion(version); return this; } @Override public boolean isHttpOnly() { return cookie.isHttpOnly(); } @Override public Cookie setHttpOnly(final boolean httpOnly) { cookie.setHttpOnly(httpOnly); return this; } @Override public Date getExpires() { return null; } @Override public Cookie setExpires(final Date expires) { throw UndertowServletMessages.MESSAGES.notImplemented(); } @Override public String getComment() { return cookie.getComment(); } @Override public Cookie setComment(final String comment) { cookie.setComment(comment); return this; } @Override public boolean isSameSite() { return sameSite; } @Override public Cookie setSameSite(final boolean sameSite) { this.sameSite = sameSite; return this; } @Override public String getSameSiteMode() { return sameSiteMode; } @Override public Cookie setSameSiteMode(final String mode) { final String m = CookieSameSiteMode.lookupModeString(mode); if (m != null) { UndertowServletLogger.REQUEST_LOGGER.tracef("Setting SameSite mode to [%s] for cookie [%s]", m, this.getName()); this.sameSiteMode = m; this.setSameSite(true); } else { UndertowServletLogger.REQUEST_LOGGER.warnf(UndertowMessages.MESSAGES.invalidSameSiteMode(mode, Arrays.toString(CookieSameSiteMode.values())), "Ignoring specified SameSite mode [%s] for cookie [%s]", mode, this.getName()); } return this; } @Override public final int hashCode() { int result = 17; result = 37 * result + (getName() == null ? 0 : getName().hashCode()); result = 37 * result + (getPath() == null ? 0 : getPath().hashCode()); result = 37 * result + (getDomain() == null ? 0 : getDomain().hashCode()); return result; } @Override public final boolean equals(final Object other) { if (other == this) return true; if (!(other instanceof Cookie)) return false; final Cookie o = (Cookie) other; // compare names if (getName() == null && o.getName() != null) return false; if (getName() != null && !getName().equals(o.getName())) return false; // compare paths if (getPath() == null && o.getPath() != null) return false; if (getPath() != null && !getPath().equals(o.getPath())) return false; // compare domains if (getDomain() == null && o.getDomain() != null) return false; if (getDomain() != null && !getDomain().equals(o.getDomain())) return false; // same cookie return true; } @Override public final int compareTo(final Object other) { return Cookie.super.compareTo(other); } @Override public final String toString() { return "{ServletCookieAdaptor@" + System.identityHashCode(this) + " name=" + getName() + " path=" + getPath() + " domain=" + getDomain() + "}"; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/ServletInputStreamImpl.java000066400000000000000000000300771420065311100330220ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.anyAreClear; import static org.xnio.Bits.anyAreSet; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import org.xnio.Buffers; import org.xnio.ChannelListener; import org.xnio.IoUtils; import org.xnio.channels.Channels; import org.xnio.channels.EmptyStreamSourceChannel; import org.xnio.channels.StreamSourceChannel; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import io.undertow.servlet.UndertowServletMessages; /** * Servlet input stream implementation. This stream is non-buffered, and is used for both * HTTP requests and for upgraded streams. * * @author Stuart Douglas */ public class ServletInputStreamImpl extends ServletInputStream { private final HttpServletRequestImpl request; private final StreamSourceChannel channel; private final ByteBufferPool bufferPool; private volatile ReadListener listener; private volatile ServletInputStreamChannelListener internalListener; /** * If this stream is ready for a read */ private static final int FLAG_READY = 1; private static final int FLAG_CLOSED = 1 << 1; private static final int FLAG_FINISHED = 1 << 2; private static final int FLAG_ON_DATA_READ_CALLED = 1 << 3; private static final int FLAG_CALL_ON_ALL_DATA_READ = 1 << 4; private static final int FLAG_BEING_INVOKED_IN_IO_THREAD = 1 << 5; private static final int FLAG_IS_READY_CALLED = 1 << 6; private volatile int state; private volatile AsyncContextImpl asyncContext; private volatile PooledByteBuffer pooled; private volatile boolean asyncIoStarted; private static final AtomicIntegerFieldUpdater stateUpdater = AtomicIntegerFieldUpdater.newUpdater(ServletInputStreamImpl.class, "state"); public ServletInputStreamImpl(final HttpServletRequestImpl request) { this.request = request; if (request.getExchange().isRequestChannelAvailable()) { this.channel = request.getExchange().getRequestChannel(); } else { this.channel = new EmptyStreamSourceChannel(request.getExchange().getIoThread()); } this.bufferPool = request.getExchange().getConnection().getByteBufferPool(); } @Override public boolean isFinished() { return anyAreSet(state, FLAG_FINISHED); } @Override public boolean isReady() { if (!asyncContext.isInitialRequestDone()) { return false; } boolean finished = anyAreSet(state, FLAG_FINISHED); if(finished) { if (anyAreClear(state, FLAG_ON_DATA_READ_CALLED)) { if(allAreClear(state, FLAG_BEING_INVOKED_IN_IO_THREAD)) { setFlags(FLAG_ON_DATA_READ_CALLED); request.getServletContext().invokeOnAllDataRead(request.getExchange(), listener); } else { setFlags(FLAG_CALL_ON_ALL_DATA_READ); } } } if (!asyncIoStarted) { //make sure we don't call resumeReads unless we have started async IO return false; } boolean ready = anyAreSet(state, FLAG_READY) && !finished; if(!ready && listener != null && !finished) { channel.resumeReads(); } if(ready) { setFlags(FLAG_IS_READY_CALLED); } return ready; } @Override public void setReadListener(final ReadListener readListener) { if (readListener == null) { throw UndertowServletMessages.MESSAGES.listenerCannotBeNull(); } if (listener != null) { throw UndertowServletMessages.MESSAGES.listenerAlreadySet(); } if (!request.isAsyncStarted()) { throw UndertowServletMessages.MESSAGES.asyncNotStarted(); } asyncContext = request.getAsyncContext(); listener = readListener; channel.getReadSetter().set(internalListener = new ServletInputStreamChannelListener()); //we resume from an async task, after the request has been dispatched asyncContext.addAsyncTask(new Runnable() { @Override public void run() { channel.getIoThread().execute(new Runnable() { @Override public void run() { asyncIoStarted = true; internalListener.handleEvent(channel); } }); } }); } @Override public int read() throws IOException { byte[] b = new byte[1]; int read = read(b); if (read == -1) { return -1; } return b[0] & 0xff; } @Override public int read(final byte[] b) throws IOException { return read(b, 0, b.length); } @Override public int read(final byte[] b, final int off, final int len) throws IOException { if (anyAreSet(state, FLAG_CLOSED)) { throw UndertowServletMessages.MESSAGES.streamIsClosed(); } if (listener != null) { if (anyAreClear(state, FLAG_READY | FLAG_IS_READY_CALLED) ) { throw UndertowServletMessages.MESSAGES.streamNotReady(); } clearFlags(FLAG_IS_READY_CALLED); } else { readIntoBuffer(); } if (anyAreSet(state, FLAG_FINISHED)) { return -1; } if (len == 0) { return 0; } ByteBuffer buffer = pooled.getBuffer(); int copied = Buffers.copy(ByteBuffer.wrap(b, off, len), buffer); if (!buffer.hasRemaining()) { pooled.close(); pooled = null; if (listener != null) { readIntoBufferNonBlocking(); } } return copied; } private void readIntoBuffer() throws IOException { if (pooled == null && !anyAreSet(state, FLAG_FINISHED)) { pooled = bufferPool.allocate(); int res = Channels.readBlocking(channel, pooled.getBuffer()); pooled.getBuffer().flip(); if (res == -1) { setFlags(FLAG_FINISHED); pooled.close(); pooled = null; } } } private void readIntoBufferNonBlocking() throws IOException { if (pooled == null && !anyAreSet(state, FLAG_FINISHED)) { pooled = bufferPool.allocate(); if (listener == null) { int res = channel.read(pooled.getBuffer()); if (res == 0) { pooled.close(); pooled = null; return; } pooled.getBuffer().flip(); if (res == -1) { setFlags(FLAG_FINISHED); pooled.close(); pooled = null; } } else { int res = channel.read(pooled.getBuffer()); pooled.getBuffer().flip(); if (res == -1) { setFlags(FLAG_FINISHED); pooled.close(); pooled = null; } else if (res == 0) { clearFlags(FLAG_READY); pooled.close(); pooled = null; } } } } @Override public int available() throws IOException { if (anyAreSet(state, FLAG_CLOSED)) { throw UndertowServletMessages.MESSAGES.streamIsClosed(); } readIntoBufferNonBlocking(); if (anyAreSet(state, FLAG_FINISHED)) { return 0; } if (pooled == null) { return 0; } return pooled.getBuffer().remaining(); } @Override public void close() throws IOException { if (anyAreSet(state, FLAG_CLOSED)) { return; } setFlags(FLAG_CLOSED); try { while (allAreClear(state, FLAG_FINISHED)) { readIntoBuffer(); if (pooled != null) { pooled.close(); pooled = null; } } } finally { setFlags(FLAG_FINISHED); if (pooled != null) { pooled.close(); pooled = null; } channel.shutdownReads(); } } private class ServletInputStreamChannelListener implements ChannelListener { @Override public void handleEvent(final StreamSourceChannel channel) { try { if (asyncContext.isDispatched()) { //this is no longer an async request //we just return //TODO: what do we do here? Revert back to blocking mode? channel.suspendReads(); return; } if (anyAreSet(state, FLAG_FINISHED)) { channel.suspendReads(); return; } readIntoBufferNonBlocking(); if (pooled != null) { channel.suspendReads(); setFlags(FLAG_READY); if (!anyAreSet(state, FLAG_FINISHED)) { setFlags(FLAG_BEING_INVOKED_IN_IO_THREAD); try { request.getServletContext().invokeOnDataAvailable(request.getExchange(), listener); } finally { clearFlags(FLAG_BEING_INVOKED_IN_IO_THREAD); } if(anyAreSet(state, FLAG_CALL_ON_ALL_DATA_READ) && allAreClear(state, FLAG_ON_DATA_READ_CALLED)) { setFlags(FLAG_ON_DATA_READ_CALLED); request.getServletContext().invokeOnAllDataRead(request.getExchange(), listener); } } } else if(anyAreSet(state, FLAG_FINISHED)) { if (allAreClear(state, FLAG_ON_DATA_READ_CALLED)) { setFlags(FLAG_ON_DATA_READ_CALLED); request.getServletContext().invokeOnAllDataRead(request.getExchange(), listener); } } else { channel.resumeReads(); } } catch (final Throwable e) { try { request.getServletContext().invokeRunnable(request.getExchange(), new Runnable() { @Override public void run() { listener.onError(e); } }); } finally { if (pooled != null) { pooled.close(); pooled = null; } IoUtils.safeClose(channel); } } } } private void setFlags(int flags) { int old; do { old = state; } while (!stateUpdater.compareAndSet(this, old, old | flags)); } private void clearFlags(int flags) { int old; do { old = state; } while (!stateUpdater.compareAndSet(this, old, old & ~flags)); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/ServletOutputStreamImpl.java000066400000000000000000001072561420065311100332270ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.anyAreClear; import static org.xnio.Bits.anyAreSet; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import javax.servlet.DispatcherType; import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; import javax.servlet.WriteListener; import io.undertow.UndertowLogger; import org.xnio.Buffers; import org.xnio.ChannelListener; import org.xnio.IoUtils; import org.xnio.channels.Channels; import org.xnio.channels.StreamSinkChannel; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import io.undertow.io.BufferWritableOutputStream; import io.undertow.server.protocol.http.HttpAttachments; import io.undertow.servlet.UndertowServletMessages; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.util.Headers; /** * This stream essentially has two modes. When it is being used in standard blocking mode then * it will buffer in the pooled buffer. If the stream is closed before the buffer is full it will * set a content-length header if one has not been explicitly set. *

    * If a content-length header was present when the stream was created then it will automatically * close and flush itself once the appropriate amount of data has been written. *

    * Once the listener has been set it goes into async mode, and writes become non blocking. Most methods * have two different code paths, based on if the listener has been set or not *

    * Once the write listener has been set operations must only be invoked on this stream from the write * listener callback. Attempting to invoke from a different thread will result in an IllegalStateException. *

    * Async listener tasks are queued in the {@link AsyncContextImpl}. At most one listener can be active at * one time, which simplifies the thread safety requirements. * * @author Stuart Douglas */ public class ServletOutputStreamImpl extends ServletOutputStream implements BufferWritableOutputStream { private final ServletRequestContext servletRequestContext; private PooledByteBuffer pooledBuffer; private ByteBuffer buffer; private Integer bufferSize; private StreamSinkChannel channel; private long written; private volatile int state; private volatile boolean asyncIoStarted; private AsyncContextImpl asyncContext; private WriteListener listener; private WriteChannelListener internalListener; /** * buffers that are queued up to be written via async writes. This will include * {@link #buffer} as the first element, and maybe a user supplied buffer that * did not fit */ private ByteBuffer[] buffersToWrite; private FileChannel pendingFile; private static final int FLAG_CLOSED = 1; private static final int FLAG_WRITE_STARTED = 1 << 1; private static final int FLAG_READY = 1 << 2; private static final int FLAG_DELEGATE_SHUTDOWN = 1 << 3; private static final int FLAG_IN_CALLBACK = 1 << 4; //TODO: should this be configurable? private static final int MAX_BUFFERS_TO_ALLOCATE = 6; private static final AtomicIntegerFieldUpdater stateUpdater = AtomicIntegerFieldUpdater.newUpdater(ServletOutputStreamImpl.class, "state"); /** * Construct a new instance. No write timeout is configured. */ public ServletOutputStreamImpl(final ServletRequestContext servletRequestContext) { this.servletRequestContext = servletRequestContext; } /** * Construct a new instance. No write timeout is configured. */ public ServletOutputStreamImpl(final ServletRequestContext servletRequestContext, int bufferSize) { this.bufferSize = bufferSize; this.servletRequestContext = servletRequestContext; } /** * {@inheritDoc} */ public void write(final int b) throws IOException { write(new byte[]{(byte) b}, 0, 1); } /** * {@inheritDoc} */ public void write(final byte[] b) throws IOException { write(b, 0, b.length); } /** * {@inheritDoc} */ public void write(final byte[] b, final int off, final int len) throws IOException { if (anyAreSet(state, FLAG_CLOSED) || servletRequestContext.getOriginalResponse().isTreatAsCommitted()) { throw UndertowServletMessages.MESSAGES.streamIsClosed(); } if (len < 1) { return; } if (listener == null) { ByteBuffer buffer = buffer(); if (buffer.remaining() < len) { writeTooLargeForBuffer(b, off, len, buffer); } else { buffer.put(b, off, len); if (buffer.remaining() == 0) { writeBufferBlocking(false); } } updateWritten(len); } else { writeAsync(b, off, len); } } private void writeTooLargeForBuffer(byte[] b, int off, int len, ByteBuffer buffer) throws IOException { //so what we have will not fit. //We allocate multiple buffers up to MAX_BUFFERS_TO_ALLOCATE //and put it in them //if it still dopes not fit we loop, re-using these buffers StreamSinkChannel channel = this.channel; if (channel == null) { this.channel = channel = servletRequestContext.getExchange().getResponseChannel(); } final ByteBufferPool bufferPool = servletRequestContext.getExchange().getConnection().getByteBufferPool(); ByteBuffer[] buffers = new ByteBuffer[MAX_BUFFERS_TO_ALLOCATE + 1]; PooledByteBuffer[] pooledBuffers = new PooledByteBuffer[MAX_BUFFERS_TO_ALLOCATE]; try { buffers[0] = buffer; int bytesWritten = 0; int rem = buffer.remaining(); buffer.put(b, bytesWritten + off, rem); buffer.flip(); bytesWritten += rem; int bufferCount = 1; for (int i = 0; i < MAX_BUFFERS_TO_ALLOCATE; ++i) { PooledByteBuffer pooled = bufferPool.allocate(); pooledBuffers[bufferCount - 1] = pooled; buffers[bufferCount++] = pooled.getBuffer(); ByteBuffer cb = pooled.getBuffer(); int toWrite = len - bytesWritten; if (toWrite > cb.remaining()) { rem = cb.remaining(); cb.put(b, bytesWritten + off, rem); cb.flip(); bytesWritten += rem; } else { cb.put(b, bytesWritten + off, toWrite); bytesWritten = len; cb.flip(); break; } } Channels.writeBlocking(channel, buffers, 0, bufferCount); while (bytesWritten < len) { //ok, it did not fit, loop and loop and loop until it is done bufferCount = 0; for (int i = 0; i < MAX_BUFFERS_TO_ALLOCATE + 1; ++i) { ByteBuffer cb = buffers[i]; cb.clear(); bufferCount++; int toWrite = len - bytesWritten; if (toWrite > cb.remaining()) { rem = cb.remaining(); cb.put(b, bytesWritten + off, rem); cb.flip(); bytesWritten += rem; } else { cb.put(b, bytesWritten + off, toWrite); bytesWritten = len; cb.flip(); break; } } Channels.writeBlocking(channel, buffers, 0, bufferCount); } buffer.clear(); } finally { for (int i = 0; i < pooledBuffers.length; ++i) { PooledByteBuffer p = pooledBuffers[i]; if (p == null) { break; } p.close(); } } } private void writeAsync(byte[] b, int off, int len) throws IOException { if (anyAreClear(state, FLAG_READY)) { throw UndertowServletMessages.MESSAGES.streamNotReady(); } //even though we are in async mode we are still buffering try { ByteBuffer buffer = buffer(); if (buffer.remaining() > len) { buffer.put(b, off, len); } else { buffer.flip(); final ByteBuffer userBuffer = ByteBuffer.wrap(b, off, len); final ByteBuffer[] bufs = new ByteBuffer[]{buffer, userBuffer}; long toWrite = Buffers.remaining(bufs); long res; long written = 0; createChannel(); setFlags(FLAG_WRITE_STARTED); do { res = channel.write(bufs); written += res; if (res == 0) { //write it out with a listener //but we need to copy any extra data final ByteBuffer copy = ByteBuffer.allocate(userBuffer.remaining()); copy.put(userBuffer); copy.flip(); this.buffersToWrite = new ByteBuffer[]{buffer, copy}; clearFlags(FLAG_READY); return; } } while (written < toWrite); buffer.clear(); } } finally { updateWrittenAsync(len); } } @Override public void write(ByteBuffer[] buffers) throws IOException { if (anyAreSet(state, FLAG_CLOSED) || servletRequestContext.getOriginalResponse().isTreatAsCommitted()) { throw UndertowServletMessages.MESSAGES.streamIsClosed(); } int len = 0; for (ByteBuffer buf : buffers) { len += buf.remaining(); } if (len < 1) { return; } if (listener == null) { //if we have received the exact amount of content write it out in one go //this is a common case when writing directly from a buffer cache. if (this.written == 0 && len == servletRequestContext.getOriginalResponse().getContentLength()) { if (channel == null) { channel = servletRequestContext.getExchange().getResponseChannel(); } Channels.writeBlocking(channel, buffers, 0, buffers.length); setFlags(FLAG_WRITE_STARTED); } else { ByteBuffer buffer = buffer(); if (len < buffer.remaining()) { Buffers.copy(buffer, buffers, 0, buffers.length); } else { if (channel == null) { channel = servletRequestContext.getExchange().getResponseChannel(); } if (buffer.position() == 0) { Channels.writeBlocking(channel, buffers, 0, buffers.length); } else { final ByteBuffer[] newBuffers = new ByteBuffer[buffers.length + 1]; buffer.flip(); newBuffers[0] = buffer; System.arraycopy(buffers, 0, newBuffers, 1, buffers.length); Channels.writeBlocking(channel, newBuffers, 0, newBuffers.length); buffer.clear(); } setFlags(FLAG_WRITE_STARTED); } } updateWritten(len); } else { if (anyAreClear(state, FLAG_READY)) { throw UndertowServletMessages.MESSAGES.streamNotReady(); } //even though we are in async mode we are still buffering try { ByteBuffer buffer = buffer(); if (buffer.remaining() > len) { Buffers.copy(buffer, buffers, 0, buffers.length); } else { final ByteBuffer[] bufs = new ByteBuffer[buffers.length + 1]; buffer.flip(); bufs[0] = buffer; System.arraycopy(buffers, 0, bufs, 1, buffers.length); long toWrite = Buffers.remaining(bufs); long res; long written = 0; createChannel(); setFlags(FLAG_WRITE_STARTED); do { res = channel.write(bufs); written += res; if (res == 0) { //write it out with a listener //but we need to copy any extra data //TODO: should really allocate from the pool here final ByteBuffer copy = ByteBuffer.allocate((int) Buffers.remaining(buffers)); Buffers.copy(copy, buffers, 0, buffers.length); copy.flip(); this.buffersToWrite = new ByteBuffer[]{buffer, copy}; clearFlags(FLAG_READY); channel.resumeWrites(); return; } } while (written < toWrite); buffer.clear(); } } finally { updateWrittenAsync(len); } } } @Override public void write(ByteBuffer byteBuffer) throws IOException { write(new ByteBuffer[]{byteBuffer}); } void updateWritten(final long len) throws IOException { this.written += len; long contentLength = servletRequestContext.getOriginalResponse().getContentLength(); if (contentLength != -1 && this.written >= contentLength) { close(); } } void updateWrittenAsync(final long len) throws IOException { this.written += len; long contentLength = servletRequestContext.getOriginalResponse().getContentLength(); if (contentLength != -1 && this.written >= contentLength) { setFlags(FLAG_CLOSED); //if buffersToWrite is set we are already flushing //so we don't have to do anything if (buffersToWrite == null && pendingFile == null) { if (flushBufferAsync(true)) { channel.shutdownWrites(); setFlags(FLAG_DELEGATE_SHUTDOWN); channel.flush(); if (pooledBuffer != null) { pooledBuffer.close(); buffer = null; pooledBuffer = null; } } } } } private boolean flushBufferAsync(final boolean writeFinal) throws IOException { ByteBuffer[] bufs = buffersToWrite; if (bufs == null) { ByteBuffer buffer = this.buffer; if (buffer == null || buffer.position() == 0) { return true; } buffer.flip(); bufs = new ByteBuffer[]{buffer}; } long toWrite = Buffers.remaining(bufs); if (toWrite == 0) { //we clear the buffer, so it can be written to again buffer.clear(); return true; } setFlags(FLAG_WRITE_STARTED); createChannel(); long res; long written = 0; do { if (writeFinal) { res = channel.writeFinal(bufs); } else { res = channel.write(bufs); } written += res; if (res == 0) { //write it out with a listener clearFlags(FLAG_READY); buffersToWrite = bufs; channel.resumeWrites(); return false; } } while (written < toWrite); buffer.clear(); return true; } /** * Returns the underlying buffer. If this has not been created yet then * it is created. *

    * Callers that use this method must call {@link #updateWritten(long)} to update the written * amount. *

    * This allows the buffer to be filled directly, which can be more efficient. *

    * This method is basically a hack that should only be used by the print writer * * @return The underlying buffer */ ByteBuffer underlyingBuffer() { if (anyAreSet(state, FLAG_CLOSED)) { return null; } return buffer(); } /** * {@inheritDoc} */ public void flush() throws IOException { //according to the servlet spec we ignore a flush from within an include if (servletRequestContext.getOriginalRequest().getDispatcherType() == DispatcherType.INCLUDE || servletRequestContext.getOriginalResponse().isTreatAsCommitted()) { return; } if (servletRequestContext.getDeployment().getDeploymentInfo().isIgnoreFlush() && servletRequestContext.getExchange().isRequestComplete() && servletRequestContext.getOriginalResponse().getHeader(Headers.TRANSFER_ENCODING_STRING) == null) { //we mark the stream as flushed, but don't actually flush //because in most cases flush just kills performance //we only do this if the request is fully read, so that http tunneling scenarios still work servletRequestContext.getOriginalResponse().setIgnoredFlushPerformed(true); return; } try { flushInternal(); } catch (IOException ioe) { final HttpServletRequestImpl request = this.servletRequestContext.getOriginalRequest(); if (request.isAsyncStarted() || request.getDispatcherType() == DispatcherType.ASYNC) { servletRequestContext.getExchange().unDispatch(); servletRequestContext.getOriginalRequest().getAsyncContextInternal().handleError(ioe); throw ioe; } } } /** * {@inheritDoc} */ public void flushInternal() throws IOException { if (listener == null) { if (anyAreSet(state, FLAG_CLOSED)) { //just return return; } if (buffer != null && buffer.position() != 0) { writeBufferBlocking(false); } if (channel == null) { channel = servletRequestContext.getExchange().getResponseChannel(); } Channels.flushBlocking(channel); } else { if (anyAreClear(state, FLAG_READY)) { return; } createChannel(); if (buffer == null || buffer.position() == 0) { //nothing to flush, we just flush the underlying stream //it does not matter if this succeeds or not channel.flush(); return; } //we have some data in the buffer, we can just write it out //if the write fails we just compact, rather than changing the ready state setFlags(FLAG_WRITE_STARTED); buffer.flip(); long res; do { res = channel.write(buffer); } while (buffer.hasRemaining() && res != 0); if (!buffer.hasRemaining()) { channel.flush(); } buffer.compact(); } } @Override public void transferFrom(FileChannel source) throws IOException { if (anyAreSet(state, FLAG_CLOSED) || servletRequestContext.getOriginalResponse().isTreatAsCommitted()) { throw UndertowServletMessages.MESSAGES.streamIsClosed(); } if (listener == null) { if (buffer != null && buffer.position() != 0) { writeBufferBlocking(false); } if (channel == null) { channel = servletRequestContext.getExchange().getResponseChannel(); } long position = source.position(); long count = source.size() - position; Channels.transferBlocking(channel, source, position, count); updateWritten(count); } else { setFlags(FLAG_WRITE_STARTED); createChannel(); long pos = 0; try { long size = source.size(); pos = source.position(); while (size - pos > 0) { long ret = channel.transferFrom(pendingFile, pos, size - pos); if (ret <= 0) { clearFlags(FLAG_READY); pendingFile = source; source.position(pos); channel.resumeWrites(); return; } pos += ret; } } finally { updateWrittenAsync(pos - source.position()); } } } private void writeBufferBlocking(final boolean writeFinal) throws IOException { if (channel == null) { channel = servletRequestContext.getExchange().getResponseChannel(); } buffer.flip(); while (buffer.hasRemaining()) { if (writeFinal) { channel.writeFinal(buffer); } else { channel.write(buffer); } if (buffer.hasRemaining()) { channel.awaitWritable(); } } buffer.clear(); setFlags(FLAG_WRITE_STARTED); } /** * {@inheritDoc} */ public void close() throws IOException { if (servletRequestContext.getOriginalRequest().getDispatcherType() == DispatcherType.INCLUDE || servletRequestContext.getOriginalResponse().isTreatAsCommitted()) { return; } if (listener == null) { if (anyAreSet(state, FLAG_CLOSED)) return; setFlags(FLAG_CLOSED); clearFlags(FLAG_READY); if (allAreClear(state, FLAG_WRITE_STARTED) && channel == null && servletRequestContext.getOriginalResponse().getHeader(Headers.CONTENT_LENGTH_STRING) == null) { if (servletRequestContext.getOriginalResponse().getHeader(Headers.TRANSFER_ENCODING_STRING) == null && servletRequestContext.getExchange().getAttachment(HttpAttachments.RESPONSE_TRAILER_SUPPLIER) == null && servletRequestContext.getExchange().getAttachment(HttpAttachments.RESPONSE_TRAILERS) == null) { if (buffer == null) { servletRequestContext.getExchange().getResponseHeaders().put(Headers.CONTENT_LENGTH, "0"); } else { servletRequestContext.getExchange().getResponseHeaders().put(Headers.CONTENT_LENGTH, Integer.toString(buffer.position())); } } } try { if (buffer != null) { writeBufferBlocking(true); } if (channel == null) { channel = servletRequestContext.getExchange().getResponseChannel(); } setFlags(FLAG_DELEGATE_SHUTDOWN); StreamSinkChannel channel = this.channel; if (channel != null) { //mock requests channel.shutdownWrites(); Channels.flushBlocking(channel); } } catch (IOException | RuntimeException | Error e) { IoUtils.safeClose(this.channel); throw e; } finally { if (pooledBuffer != null) { pooledBuffer.close(); buffer = null; } else { buffer = null; } } } else { closeAsync(); } } /** * Closes the channel, and flushes any data out using async IO *

    * This is used in two situations, if an output stream is not closed when a * request is done, and when performing a close on a stream that is in async * mode * * @throws IOException */ public void closeAsync() throws IOException { if (anyAreSet(state, FLAG_CLOSED) || servletRequestContext.getOriginalResponse().isTreatAsCommitted()) { return; } if (!servletRequestContext.getExchange().isInIoThread()) { servletRequestContext.getExchange().getIoThread().execute(new Runnable() { @Override public void run() { try { closeAsync(); } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.closeAsyncFailed(e); } } }); return; } try { setFlags(FLAG_CLOSED); clearFlags(FLAG_READY); if (allAreClear(state, FLAG_WRITE_STARTED) && channel == null) { if (servletRequestContext.getOriginalResponse().getHeader(Headers.TRANSFER_ENCODING_STRING) == null) { if (buffer == null) { servletRequestContext.getOriginalResponse().setHeader(Headers.CONTENT_LENGTH, "0"); } else { servletRequestContext.getOriginalResponse().setHeader(Headers.CONTENT_LENGTH, Integer.toString(buffer.position())); } } } createChannel(); if (buffer != null) { if (!flushBufferAsync(true)) { return; } if (pooledBuffer != null) { pooledBuffer.close(); buffer = null; } else { buffer = null; } } channel.shutdownWrites(); setFlags(FLAG_DELEGATE_SHUTDOWN); if (!channel.flush()) { channel.resumeWrites(); } } catch (IOException | RuntimeException | Error e) { if (pooledBuffer != null) { pooledBuffer.close(); pooledBuffer = null; buffer = null; } throw e; } } private void createChannel() { if (channel == null) { channel = servletRequestContext.getExchange().getResponseChannel(); if (internalListener != null) { channel.getWriteSetter().set(internalListener); } } } private ByteBuffer buffer() { ByteBuffer buffer = this.buffer; if (buffer != null) { return buffer; } if (bufferSize != null) { this.buffer = ByteBuffer.allocateDirect(bufferSize); return this.buffer; } else { this.pooledBuffer = servletRequestContext.getExchange().getConnection().getByteBufferPool().allocate(); this.buffer = pooledBuffer.getBuffer(); return this.buffer; } } public void resetBuffer() { if (allAreClear(state, FLAG_WRITE_STARTED)) { if (pooledBuffer != null) { pooledBuffer.close(); pooledBuffer = null; } buffer = null; this.written = 0; } else { throw UndertowServletMessages.MESSAGES.responseAlreadyCommited(); } } public void setBufferSize(final int size) { if (buffer != null || servletRequestContext.getOriginalResponse().isTreatAsCommitted()) { throw UndertowServletMessages.MESSAGES.contentHasBeenWritten(); } this.bufferSize = size; } public boolean isClosed() { return anyAreSet(state, FLAG_CLOSED); } @Override public boolean isReady() { if (listener == null) { //TODO: is this the correct behaviour? throw UndertowServletMessages.MESSAGES.streamNotInAsyncMode(); } if (!asyncIoStarted) { //if we don't add this guard here calls to isReady could start async IO too soon //resulting in a 'resuming + dispatched' message return false; } if (!anyAreSet(state, FLAG_READY)) { if (channel != null) { channel.resumeWrites(); } return false; } return true; } @Override public void setWriteListener(final WriteListener writeListener) { if (writeListener == null) { throw UndertowServletMessages.MESSAGES.listenerCannotBeNull(); } if (listener != null) { throw UndertowServletMessages.MESSAGES.listenerAlreadySet(); } final ServletRequest servletRequest = servletRequestContext.getOriginalRequest(); if (!servletRequest.isAsyncStarted()) { throw UndertowServletMessages.MESSAGES.asyncNotStarted(); } asyncContext = (AsyncContextImpl) servletRequest.getAsyncContext(); listener = writeListener; //we register the write listener on the underlying connection //so we don't have to force the creation of the response channel //under normal circumstances this will break write listener delegation this.internalListener = new WriteChannelListener(); if (this.channel != null) { this.channel.getWriteSetter().set(internalListener); } //we resume from an async task, after the request has been dispatched asyncContext.addAsyncTask(new Runnable() { @Override public void run() { asyncIoStarted = true; if (channel == null) { servletRequestContext.getExchange().getIoThread().execute(new Runnable() { @Override public void run() { internalListener.handleEvent(null); } }); } else { channel.resumeWrites(); } } }); } ServletRequestContext getServletRequestContext() { return servletRequestContext; } private class WriteChannelListener implements ChannelListener { @Override public void handleEvent(final StreamSinkChannel aChannel) { //flush the channel if it is closed if (anyAreSet(state, FLAG_DELEGATE_SHUTDOWN)) { try { //either it will work, and the channel is closed //or it won't, and we continue with writes resumed channel.flush(); return; } catch (Throwable t) { handleError(t); return; } } //if there is data still to write if (buffersToWrite != null) { long toWrite = Buffers.remaining(buffersToWrite); long written = 0; long res; if (toWrite > 0) { //should always be true, but just to be defensive do { try { res = channel.write(buffersToWrite); written += res; if (res == 0) { return; } } catch (Throwable t) { handleError(t); return; } } while (written < toWrite); } buffersToWrite = null; buffer.clear(); } if (pendingFile != null) { try { long size = pendingFile.size(); long pos = pendingFile.position(); while (size - pos > 0) { long ret = channel.transferFrom(pendingFile, pos, size - pos); if (ret <= 0) { pendingFile.position(pos); return; } pos += ret; } pendingFile = null; } catch (Throwable t) { handleError(t); return; } } if (anyAreSet(state, FLAG_CLOSED)) { try { if (pooledBuffer != null) { pooledBuffer.close(); buffer = null; } else { buffer = null; } channel.shutdownWrites(); setFlags(FLAG_DELEGATE_SHUTDOWN); channel.flush(); } catch (Throwable t) { handleError(t); return; } } else { if (asyncContext.isDispatched()) { //this is no longer an async request //we just return for now //TODO: what do we do here? Revert back to blocking mode? channel.suspendWrites(); return; } setFlags(FLAG_READY); try { setFlags(FLAG_IN_CALLBACK); //if the stream is still ready then we do not resume writes //this is per spec, we only call the listener once for each time //isReady returns true if (channel != null) { channel.suspendWrites(); } servletRequestContext.getCurrentServletContext().invokeOnWritePossible(servletRequestContext.getExchange(), listener); } catch (Throwable e) { IoUtils.safeClose(channel); } finally { clearFlags(FLAG_IN_CALLBACK); } } } private void handleError(final Throwable t) { try { servletRequestContext.getCurrentServletContext().invokeRunnable(servletRequestContext.getExchange(), new Runnable() { @Override public void run() { listener.onError(t); } }); } finally { IoUtils.safeClose(channel, servletRequestContext.getExchange().getConnection()); if (pooledBuffer != null) { pooledBuffer.close(); pooledBuffer = null; buffer = null; } } } } private void setFlags(int flags) { int old; do { old = state; } while (!stateUpdater.compareAndSet(this, old, old | flags)); } private void clearFlags(int flags) { int old; do { old = state; } while (!stateUpdater.compareAndSet(this, old, old & ~flags)); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/ServletPrintWriter.java000066400000000000000000000311111420065311100322040ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import javax.servlet.DispatcherType; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; import java.util.Locale; /** * Real servlet print writer functionality, that is not limited by extending * {@link java.io.PrintWriter} *

    * * @author Stuart Douglas */ public class ServletPrintWriter { private static final char[] EMPTY_CHAR = {}; private final ServletOutputStreamImpl outputStream; private final String charset; private CharsetEncoder charsetEncoder; private boolean error = false; private boolean closed = false; private char[] underflow; public ServletPrintWriter(final ServletOutputStreamImpl outputStream, final String charset) throws UnsupportedEncodingException { this.charset = charset; this.outputStream = outputStream; //for some known charset we get optimistic and hope that //only ascii will be output //in this case we can avoid creating the encoder altogether if (!charset.equalsIgnoreCase("utf-8") && !charset.equalsIgnoreCase("iso-8859-1")) { createEncoder(); } } private void createEncoder() { this.charsetEncoder = Charset.forName(this.charset).newEncoder(); //replace malformed and unmappable with question marks this.charsetEncoder.onUnmappableCharacter(CodingErrorAction.REPLACE); this.charsetEncoder.onMalformedInput(CodingErrorAction.REPLACE); } public void flush() { try { outputStream.flush(); } catch (IOException e) { error = true; } } public void close() { if (outputStream.getServletRequestContext().getOriginalRequest().getDispatcherType() == DispatcherType.INCLUDE) { return; } if (closed) { return; } closed = true; try { boolean done = false; CharBuffer buffer; if (underflow == null) { buffer = CharBuffer.wrap(EMPTY_CHAR); } else { buffer = CharBuffer.wrap(underflow); underflow = null; } if (charsetEncoder != null) { do { ByteBuffer out = outputStream.underlyingBuffer(); if (out == null) { //servlet output stream has already been closed error = true; return; } CoderResult result = charsetEncoder.encode(buffer, out, true); if (result.isOverflow()) { outputStream.flushInternal(); if (out.remaining() == 0) { outputStream.close(); error = true; return; } } else { done = true; } } while (!done); } outputStream.close(); } catch (IOException e) { error = true; } } public boolean checkError() { flush(); return error; } public void write(final CharBuffer input) { ByteBuffer buffer = outputStream.underlyingBuffer(); if (buffer == null) { //stream has been closed error = true; return; } try { if (!buffer.hasRemaining()) { outputStream.flushInternal(); if (!buffer.hasRemaining()) { error = true; return; } } if (charsetEncoder == null) { createEncoder(); } final CharBuffer cb; if (underflow == null) { cb = input; } else { char[] newArray = new char[underflow.length + input.remaining()]; System.arraycopy(underflow, 0, newArray, 0, underflow.length); input.get(newArray, underflow.length, input.remaining()); cb = CharBuffer.wrap(newArray); underflow = null; } int last = -1; while (cb.hasRemaining()) { int remaining = buffer.remaining(); CoderResult result = charsetEncoder.encode(cb, buffer, false); outputStream.updateWritten(remaining - buffer.remaining()); if (result.isOverflow() || !buffer.hasRemaining()) { outputStream.flushInternal(); if (!buffer.hasRemaining()) { error = true; return; } } if (result.isUnderflow()) { underflow = new char[cb.remaining()]; cb.get(underflow); return; } if (result.isError()) { error = true; return; } if (result.isUnmappable()) { //this should not happen error = true; return; } if (last == cb.remaining()) { underflow = new char[cb.remaining()]; cb.get(underflow); return; } last = cb.remaining(); } } catch (IOException e) { error = true; } } public void write(final int c) { write(Character.toString((char)c)); } public void write(final char[] buf, final int off, final int len) { if(charsetEncoder == null) { try { ByteBuffer buffer = outputStream.underlyingBuffer(); if(buffer == null) { //already closed error = true; return; } //fast path, basically we are hoping this is ascii only int remaining = buffer.remaining(); boolean ok = true; //so we have a pure ascii buffer, just write it out and skip all the encoder cost int end = off + len; int i = off; int flushPos = i + remaining; while (ok && i < end) { int realEnd = Math.min(end, flushPos); for (; i < realEnd; ++i) { char c = buf[i]; if (c > 127) { ok = false; break; } else { buffer.put((byte) c); } } if (i == flushPos) { outputStream.flushInternal(); flushPos = i + buffer.remaining(); } } outputStream.updateWritten(remaining - buffer.remaining()); if (ok) { return; } final CharBuffer cb = CharBuffer.wrap(buf, i, len - (i - off)); write(cb); return; } catch (IOException e) { error = false; return; } } final CharBuffer cb = CharBuffer.wrap(buf, off, len); write(cb); } public void write(final char[] buf) { write(buf,0, buf.length); } public void write(final String s, final int off, final int len) { if(charsetEncoder == null) { try { ByteBuffer buffer = outputStream.underlyingBuffer(); if(buffer == null) { //already closed error = true; return; } //fast path, basically we are hoping this is ascii only int remaining = buffer.remaining(); boolean ok = true; //so we have a pure ascii buffer, just write it out and skip all the encoder cost int end = off + len; int i = off; int fpos = i + remaining; for (; i < end; ++i) { if (i == fpos) { outputStream.flushInternal(); fpos = i + buffer.remaining(); } char c = s.charAt(i); if (c > 127) { ok = false; break; } buffer.put((byte) c); } outputStream.updateWritten(remaining - buffer.remaining()); if (ok) { return; } //wrap(String, off, len) acts wrong in the presence of multi byte characters final CharBuffer cb = CharBuffer.wrap(s.toCharArray(), i, len - (i - off)); write(cb); return; } catch (IOException e) { error = false; return; } } final CharBuffer cb = CharBuffer.wrap(s, off, off + len); write(cb); } public void write(final String s) { write(s, 0, s.length()); } public void print(final boolean b) { write(Boolean.toString(b)); } public void print(final char c) { write(Character.toString(c)); } public void print(final int i) { write(Integer.toString(i)); } public void print(final long l) { write(Long.toString(l)); } public void print(final float f) { write(Float.toString(f)); } public void print(final double d) { write(Double.toString(d)); } public void print(final char[] s) { write(CharBuffer.wrap(s)); } public void print(final String s) { write(s == null ? "null" : s); } public void print(final Object obj) { write(obj == null ? "null" : obj.toString()); } public void println() { print("\r\n"); } public void println(final boolean b) { print(b); println(); } public void println(final char c) { print(c); println(); } public void println(final int i) { print(i); println(); } public void println(final long l) { print(l); println(); } public void println(final float f) { print(f); println(); } public void println(final double d) { print(d); println(); } public void println(final char[] s) { print(s); println(); } public void println(final String s) { print(s); println(); } public void println(final Object obj) { print(obj); println(); } public void printf(final String format, final Object... args) { print(String.format(format, args)); } public void printf(final Locale l, final String format, final Object... args) { print(String.format(l, format, args)); } public void format(final String format, final Object... args) { printf(format, args); } public void format(final Locale l, final String format, final Object... args) { printf(l, format, args); } public void append(final CharSequence csq) { if (csq == null) { write("null"); } else { write(csq.toString()); } } public void append(final CharSequence csq, final int start, final int end) { CharSequence cs = (csq == null ? "null" : csq); write(cs.subSequence(start, end).toString()); } public void append(final char c) { write(c); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/ServletPrintWriterDelegate.java000066400000000000000000000156731420065311100336560ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import java.io.OutputStream; import java.io.PrintWriter; import java.lang.reflect.Field; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Locale; import sun.misc.Unsafe; /** * @author Stuart Douglas */ public final class ServletPrintWriterDelegate extends PrintWriter { private ServletPrintWriterDelegate() { super((OutputStream) null); } private static final sun.misc.Unsafe UNSAFE; static { UNSAFE = getUnsafe(); } public static ServletPrintWriterDelegate newInstance(final ServletPrintWriter servletPrintWriter) { final ServletPrintWriterDelegate delegate; if (System.getSecurityManager() == null) { try { delegate = (ServletPrintWriterDelegate) UNSAFE.allocateInstance(ServletPrintWriterDelegate.class); } catch (InstantiationException e) { throw new RuntimeException(e); } } else { delegate = AccessController.doPrivileged(new PrivilegedAction() { @Override public ServletPrintWriterDelegate run() { try { return (ServletPrintWriterDelegate) UNSAFE.allocateInstance(ServletPrintWriterDelegate.class); } catch (InstantiationException e) { throw new RuntimeException(e); } } }); } delegate.setServletPrintWriter(servletPrintWriter); return delegate; } private ServletPrintWriter servletPrintWriter; public void setServletPrintWriter(final ServletPrintWriter servletPrintWriter) { this.servletPrintWriter = servletPrintWriter; } @Override public void flush() { servletPrintWriter.flush(); } @Override public void close() { servletPrintWriter.close(); } @Override public boolean checkError() { return servletPrintWriter.checkError(); } @Override public void write(final int c) { servletPrintWriter.write(c); } @Override public void write(final char[] buf, final int off, final int len) { servletPrintWriter.write(buf, off, len); } @Override public void write(final char[] buf) { servletPrintWriter.write(buf); } @Override public void write(final String s, final int off, final int len) { servletPrintWriter.write(s, off, len); } @Override public void write(final String s) { servletPrintWriter.write(s == null ? "null" : s); } @Override public void print(final boolean b) { servletPrintWriter.print(b); } @Override public void print(final char c) { servletPrintWriter.print(c); } @Override public void print(final int i) { servletPrintWriter.print(i); } @Override public void print(final long l) { servletPrintWriter.print(l); } @Override public void print(final float f) { servletPrintWriter.print(f); } @Override public void print(final double d) { servletPrintWriter.print(d); } @Override public void print(final char[] s) { servletPrintWriter.print(s); } @Override public void print(final String s) { servletPrintWriter.print(s); } @Override public void print(final Object obj) { servletPrintWriter.print(obj); } @Override public void println() { servletPrintWriter.println(); } @Override public void println(final boolean x) { servletPrintWriter.println(x); } @Override public void println(final char x) { servletPrintWriter.println(x); } @Override public void println(final int x) { servletPrintWriter.println(x); } @Override public void println(final long x) { servletPrintWriter.println(x); } @Override public void println(final float x) { servletPrintWriter.println(x); } @Override public void println(final double x) { servletPrintWriter.println(x); } @Override public void println(final char[] x) { servletPrintWriter.println(x); } @Override public void println(final String x) { servletPrintWriter.println(x); } @Override public void println(final Object x) { servletPrintWriter.println(x); } @Override public PrintWriter printf(final String format, final Object... args) { servletPrintWriter.printf(format, args); return this; } @Override public PrintWriter printf(final Locale l, final String format, final Object... args) { servletPrintWriter.printf(l, format, args); return this; } @Override public PrintWriter format(final String format, final Object... args) { servletPrintWriter.format(format, args); return this; } @Override public PrintWriter format(final Locale l, final String format, final Object... args) { servletPrintWriter.format(l, format, args); return this; } @Override public PrintWriter append(final CharSequence csq) { servletPrintWriter.append(csq); return this; } @Override public PrintWriter append(final CharSequence csq, final int start, final int end) { servletPrintWriter.append(csq, start, end); return this; } @Override public PrintWriter append(final char c) { servletPrintWriter.append(c); return this; } private static Unsafe getUnsafe() { if (System.getSecurityManager() != null) { return AccessController.doPrivileged(new PrivilegedAction() { public Unsafe run() { return getUnsafe0(); } }); } return getUnsafe0(); } private static Unsafe getUnsafe0() { try { Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true); return (Unsafe) theUnsafe.get(null); } catch (Throwable t) { throw new RuntimeException("JDK did not allow accessing unsafe", t); } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/ServletRegistrationImpl.java000066400000000000000000000150521420065311100332150ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.servlet.HttpMethodConstraintElement; import javax.servlet.MultipartConfigElement; import javax.servlet.ServletRegistration; import javax.servlet.ServletSecurityElement; import javax.servlet.annotation.ServletSecurity; import io.undertow.UndertowMessages; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.HttpMethodSecurityInfo; import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.SecurityInfo; import io.undertow.servlet.api.SecurityInfo.EmptyRoleSemantic; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.ServletSecurityInfo; import io.undertow.servlet.api.TransportGuaranteeType; import io.undertow.servlet.api.WebResourceCollection; import io.undertow.servlet.core.ManagedServlet; import static javax.servlet.annotation.ServletSecurity.TransportGuarantee.CONFIDENTIAL; /** * @author Stuart Douglas */ public class ServletRegistrationImpl implements ServletRegistration, ServletRegistration.Dynamic { private final ServletInfo servletInfo; private final ManagedServlet managedServlet; private final Deployment deployment; public ServletRegistrationImpl(final ServletInfo servletInfo, ManagedServlet managedServlet, final Deployment deployment) { this.servletInfo = servletInfo; this.managedServlet = managedServlet; this.deployment = deployment; } @Override public void setLoadOnStartup(final int loadOnStartup) { servletInfo.setLoadOnStartup(loadOnStartup); } @Override public Set setServletSecurity(final ServletSecurityElement constraint) { if (constraint == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("constraint"); } DeploymentInfo deploymentInfo = deployment.getDeploymentInfo(); //this is not super efficient, but it does not really matter final Set urlPatterns = new HashSet<>(); for (SecurityConstraint sc : deploymentInfo.getSecurityConstraints()) { for (WebResourceCollection webResources : sc.getWebResourceCollections()) { urlPatterns.addAll(webResources.getUrlPatterns()); } } final Set ret = new HashSet<>(); for (String url : servletInfo.getMappings()) { if (urlPatterns.contains(url)) { ret.add(url); } } ServletSecurityInfo info = new ServletSecurityInfo(); servletInfo.setServletSecurityInfo(info); info.setTransportGuaranteeType(constraint.getTransportGuarantee() == CONFIDENTIAL ? TransportGuaranteeType.CONFIDENTIAL : TransportGuaranteeType.NONE) .setEmptyRoleSemantic(emptyRoleSemantic(constraint.getEmptyRoleSemantic())) .addRolesAllowed(constraint.getRolesAllowed()); for (final HttpMethodConstraintElement methodConstraint : constraint.getHttpMethodConstraints()) { info.addHttpMethodSecurityInfo(new HttpMethodSecurityInfo() .setTransportGuaranteeType(methodConstraint.getTransportGuarantee() == CONFIDENTIAL ? TransportGuaranteeType.CONFIDENTIAL : TransportGuaranteeType.NONE) .setMethod(methodConstraint.getMethodName()) .setEmptyRoleSemantic(emptyRoleSemantic(methodConstraint.getEmptyRoleSemantic())) .addRolesAllowed(methodConstraint.getRolesAllowed())); } return ret; } private SecurityInfo.EmptyRoleSemantic emptyRoleSemantic(final ServletSecurity.EmptyRoleSemantic emptyRoleSemantic) { switch (emptyRoleSemantic) { case PERMIT: return EmptyRoleSemantic.PERMIT; case DENY: return EmptyRoleSemantic.DENY; default: return null; } } @Override public void setMultipartConfig(final MultipartConfigElement multipartConfig) { servletInfo.setMultipartConfig(multipartConfig); managedServlet.setupMultipart(deployment.getServletContext()); } @Override public void setRunAsRole(final String roleName) { servletInfo.setRunAs(roleName); } @Override public void setAsyncSupported(final boolean isAsyncSupported) { servletInfo.setAsyncSupported(isAsyncSupported); } @Override public Set addMapping(final String... urlPatterns) { return deployment.tryAddServletMappings(servletInfo, urlPatterns); } @Override public Collection getMappings() { return servletInfo.getMappings(); } @Override public String getRunAsRole() { return servletInfo.getRunAs(); } @Override public String getName() { return servletInfo.getName(); } @Override public String getClassName() { return servletInfo.getServletClass().getName(); } @Override public boolean setInitParameter(final String name, final String value) { if (servletInfo.getInitParams().containsKey(name)) { return false; } servletInfo.addInitParam(name, value); return true; } @Override public String getInitParameter(final String name) { return servletInfo.getInitParams().get(name); } @Override public Set setInitParameters(final Map initParameters) { final Set ret = new HashSet<>(); for (Map.Entry entry : initParameters.entrySet()) { if (!setInitParameter(entry.getKey(), entry.getValue())) { ret.add(entry.getKey()); } } return ret; } @Override public Map getInitParameters() { return servletInfo.getInitParams(); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/SessionCookieConfigImpl.java000066400000000000000000000120631420065311100331000ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import io.undertow.server.HttpServerExchange; import io.undertow.server.session.SessionConfig; import io.undertow.servlet.UndertowServletMessages; import javax.servlet.SessionCookieConfig; /** * @author Stuart Douglas */ public class SessionCookieConfigImpl implements SessionCookieConfig, SessionConfig { private final ServletContextImpl servletContext; private final io.undertow.server.session.SessionCookieConfig delegate; private SessionConfig fallback; public SessionCookieConfigImpl(final ServletContextImpl servletContext) { this.servletContext = servletContext; this.delegate = new io.undertow.server.session.SessionCookieConfig(); } @Override public String rewriteUrl(final String originalUrl, final String sessionid) { if(fallback != null) { return fallback.rewriteUrl(originalUrl, sessionid); } return originalUrl; } @Override public void setSessionId(final HttpServerExchange exchange, final String sessionId) { delegate.setSessionId(exchange, sessionId); } @Override public void clearSession(final HttpServerExchange exchange, final String sessionId) { delegate.clearSession(exchange, sessionId); } @Override public String findSessionId(final HttpServerExchange exchange) { String existing = delegate.findSessionId(exchange); if(existing != null) { return existing; } if(fallback != null) { return fallback.findSessionId(exchange); } return null; } @Override public SessionCookieSource sessionCookieSource(HttpServerExchange exchange) { String existing = delegate.findSessionId(exchange); if (existing != null) { return SessionCookieSource.COOKIE; } if(fallback != null) { String id = fallback.findSessionId(exchange); return id != null ? fallback.sessionCookieSource(exchange) : SessionCookieSource.NONE; } return SessionCookieSource.NONE; } public String getName() { return delegate.getCookieName(); } public void setName(final String name) { if(servletContext.isInitialized()) { throw UndertowServletMessages.MESSAGES.servletContextAlreadyInitialized(); } delegate.setCookieName(name); } public String getDomain() { return delegate.getDomain(); } public void setDomain(final String domain) { if(servletContext.isInitialized()) { throw UndertowServletMessages.MESSAGES.servletContextAlreadyInitialized(); } delegate.setDomain(domain); } public String getPath() { return delegate.getPath(); } public void setPath(final String path) { if(servletContext.isInitialized()) { throw UndertowServletMessages.MESSAGES.servletContextAlreadyInitialized(); } delegate.setPath(path); } public String getComment() { return delegate.getComment(); } public void setComment(final String comment) { if(servletContext.isInitialized()) { throw UndertowServletMessages.MESSAGES.servletContextAlreadyInitialized(); } delegate.setComment(comment); } public boolean isHttpOnly() { return delegate.isHttpOnly(); } public void setHttpOnly(final boolean httpOnly) { if(servletContext.isInitialized()) { throw UndertowServletMessages.MESSAGES.servletContextAlreadyInitialized(); } delegate.setHttpOnly(httpOnly); } public boolean isSecure() { return delegate.isSecure(); } public void setSecure(final boolean secure) { if(servletContext.isInitialized()) { throw UndertowServletMessages.MESSAGES.servletContextAlreadyInitialized(); } delegate.setSecure(secure); } public int getMaxAge() { return delegate.getMaxAge(); } public void setMaxAge(final int maxAge) { if(servletContext.isInitialized()) { throw UndertowServletMessages.MESSAGES.servletContextAlreadyInitialized(); } this.delegate.setMaxAge(maxAge); } public SessionConfig getFallback() { return fallback; } public void setFallback(final SessionConfig fallback) { this.fallback = fallback; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/UpgradeServletInputStream.java000066400000000000000000000213731420065311100335070ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import io.undertow.servlet.UndertowServletMessages; import org.xnio.Buffers; import org.xnio.ChannelListener; import org.xnio.IoUtils; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import org.xnio.channels.Channels; import org.xnio.channels.StreamSourceChannel; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.Executor; import static org.xnio.Bits.anyAreClear; import static org.xnio.Bits.anyAreSet; /** * Servlet input stream implementation. This stream is non-buffered, and is only used for * upgraded requests * * @author Stuart Douglas */ public class UpgradeServletInputStream extends ServletInputStream { private final StreamSourceChannel channel; private final ByteBufferPool bufferPool; private final Executor ioExecutor; private volatile ReadListener listener; /** * If this stream is ready for a read */ private static final int FLAG_READY = 1; private static final int FLAG_CLOSED = 1 << 1; private static final int FLAG_FINISHED = 1 << 2; private static final int FLAG_ON_DATA_READ_CALLED = 1 << 3; private int state; private PooledByteBuffer pooled; public UpgradeServletInputStream(final StreamSourceChannel channel, final ByteBufferPool bufferPool, Executor ioExecutor) { this.channel = channel; this.bufferPool = bufferPool; this.ioExecutor = ioExecutor; } @Override public boolean isFinished() { return anyAreSet(state, FLAG_FINISHED); } @Override public boolean isReady() { return anyAreSet(state, FLAG_READY) && !isFinished(); } @Override public void setReadListener(final ReadListener readListener) { if (readListener == null) { throw UndertowServletMessages.MESSAGES.listenerCannotBeNull(); } if (listener != null) { throw UndertowServletMessages.MESSAGES.listenerAlreadySet(); } listener = readListener; channel.getReadSetter().set(new ServletInputStreamChannelListener()); //we resume from an async task, after the request has been dispatched ioExecutor.execute(new Runnable() { @Override public void run() { channel.wakeupReads(); } }); } @Override public int read() throws IOException { byte[] b = new byte[1]; int read = read(b); if (read == -1) { return -1; } return b[0] & 0xff; } @Override public int read(final byte[] b) throws IOException { return read(b, 0, b.length); } @Override public int read(final byte[] b, final int off, final int len) throws IOException { if (anyAreSet(state, FLAG_CLOSED)) { throw UndertowServletMessages.MESSAGES.streamIsClosed(); } if (listener != null) { if (anyAreClear(state, FLAG_READY)) { throw UndertowServletMessages.MESSAGES.streamNotReady(); } } else { readIntoBuffer(); } if (anyAreSet(state, FLAG_FINISHED)) { return -1; } if (len == 0) { return 0; } ByteBuffer buffer = pooled.getBuffer(); int copied = Buffers.copy(ByteBuffer.wrap(b, off, len), buffer); if (!buffer.hasRemaining()) { pooled.close(); pooled = null; if (listener != null) { readIntoBufferNonBlocking(); } } return copied; } private void readIntoBuffer() throws IOException { if (pooled == null && !anyAreSet(state, FLAG_FINISHED)) { pooled = bufferPool.allocate(); int res = Channels.readBlocking(channel, pooled.getBuffer()); pooled.getBuffer().flip(); if (res == -1) { state |= FLAG_FINISHED; pooled.close(); pooled = null; } } } private void readIntoBufferNonBlocking() throws IOException { if (pooled == null && !anyAreSet(state, FLAG_FINISHED | FLAG_CLOSED)) { pooled = bufferPool.allocate(); if (listener == null) { int res = channel.read(pooled.getBuffer()); if (res == 0) { pooled.close(); pooled = null; return; } pooled.getBuffer().flip(); if (res == -1) { state |= FLAG_FINISHED; pooled.close(); pooled = null; } } else { if (anyAreClear(state, FLAG_READY)) { throw UndertowServletMessages.MESSAGES.streamNotReady(); } int res = channel.read(pooled.getBuffer()); pooled.getBuffer().flip(); if (res == -1) { state |= FLAG_FINISHED; pooled.close(); pooled = null; } else if (res == 0) { state &= ~FLAG_READY; pooled.close(); pooled = null; if(Thread.currentThread() == channel.getIoThread()) { channel.resumeReads(); } else { ioExecutor.execute(new Runnable() { @Override public void run() { channel.resumeReads(); } }); } } } } } @Override public int available() throws IOException { if (anyAreSet(state, FLAG_CLOSED)) { throw UndertowServletMessages.MESSAGES.streamIsClosed(); } readIntoBufferNonBlocking(); if (anyAreSet(state, FLAG_FINISHED)) { return 0; } if (pooled == null) { return 0; } return pooled.getBuffer().remaining(); } @Override public void close() throws IOException { if (anyAreSet(state, FLAG_CLOSED)) { return; } state |= FLAG_FINISHED | FLAG_CLOSED; if (pooled != null) { pooled.close(); pooled = null; } channel.suspendReads(); channel.shutdownReads(); } private class ServletInputStreamChannelListener implements ChannelListener { @Override public void handleEvent(final StreamSourceChannel channel) { if (anyAreSet(state, FLAG_FINISHED)) { return; } state |= FLAG_READY; try { readIntoBufferNonBlocking(); if (pooled != null) { state |= FLAG_READY; if (!anyAreSet(state, FLAG_FINISHED)) { listener.onDataAvailable(); } } } catch (Exception e) { if(pooled != null) { pooled.close(); pooled = null; } listener.onError(e); IoUtils.safeClose(channel); } if (anyAreSet(state, FLAG_FINISHED)) { if (anyAreClear(state, FLAG_ON_DATA_READ_CALLED)) { try { state |= FLAG_ON_DATA_READ_CALLED; channel.shutdownReads(); listener.onAllDataRead(); } catch (IOException e) { if(pooled != null) { pooled.close(); pooled = null; } listener.onError(e); IoUtils.safeClose(channel); } } } else if(isReady()) { channel.suspendReads(); } } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/UpgradeServletOutputStream.java000066400000000000000000000202061420065311100337020ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import io.undertow.servlet.UndertowServletMessages; import org.xnio.ChannelListener; import org.xnio.IoUtils; import org.xnio.channels.Channels; import org.xnio.channels.StreamSinkChannel; import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.Executor; import static org.xnio.Bits.anyAreClear; import static org.xnio.Bits.anyAreSet; /** * Output stream used for upgraded requests. This is different to {@link ServletOutputStreamImpl} * as it does no buffering, and it not tied to an exchange. * * @author Stuart Douglas */ public class UpgradeServletOutputStream extends ServletOutputStream { private final StreamSinkChannel channel; private WriteListener listener; private final Executor ioExecutor; /** * If this stream is ready for a write */ private static final int FLAG_READY = 1; private static final int FLAG_CLOSED = 1 << 1; private static final int FLAG_DELEGATE_SHUTDOWN = 1 << 2; private int state; /** * The buffer that is in the process of being written out */ private ByteBuffer buffer; protected UpgradeServletOutputStream(final StreamSinkChannel channel, Executor ioExecutor) { this.channel = channel; this.ioExecutor = ioExecutor; } @Override public void write(final byte[] b) throws IOException { write(b, 0, b.length); } @Override public void write(final byte[] b, final int off, final int len) throws IOException { if (anyAreSet(state, FLAG_CLOSED)) { throw UndertowServletMessages.MESSAGES.streamIsClosed(); } if (listener == null) { Channels.writeBlocking(channel, ByteBuffer.wrap(b, off, len)); } else { if (anyAreClear(state, FLAG_READY)) { throw UndertowServletMessages.MESSAGES.streamNotReady(); } int res; ByteBuffer buffer = ByteBuffer.wrap(b); do { res = channel.write(buffer); if (res == 0) { ByteBuffer copy = ByteBuffer.allocate(buffer.remaining()); copy.put(buffer); copy.flip(); this.buffer = copy; state = state & ~FLAG_READY; if (Thread.currentThread() == channel.getIoThread()) { channel.resumeWrites(); } else { ioExecutor.execute(new Runnable() { @Override public void run() { channel.resumeWrites(); } }); } return; } } while (buffer.hasRemaining()); } } @Override public void write(final int b) throws IOException { write(new byte[]{(byte) b}, 0, 1); } @Override public void flush() throws IOException { if (anyAreSet(state, FLAG_CLOSED)) { throw UndertowServletMessages.MESSAGES.streamIsClosed(); } if (listener == null) { Channels.flushBlocking(channel); } } @Override public void close() throws IOException { state |= FLAG_CLOSED; state &= ~FLAG_READY; if (listener == null) { channel.shutdownWrites(); state |= FLAG_DELEGATE_SHUTDOWN; Channels.flushBlocking(channel); } else { if (buffer == null) { channel.shutdownWrites(); state |= FLAG_DELEGATE_SHUTDOWN; if (!channel.flush()) { if (Thread.currentThread() == channel.getIoThread()) { channel.resumeWrites(); } else { ioExecutor.execute(new Runnable() { @Override public void run() { channel.resumeWrites(); } }); } } } } } void closeBlocking() throws IOException { state |= FLAG_CLOSED; try { if (buffer != null) { Channels.writeBlocking(channel, buffer); } channel.shutdownWrites(); Channels.flushBlocking(channel); } catch (IOException e){ channel.close(); throw e; } } @Override public boolean isReady() { if (listener == null) { //TODO: is this the correct behaviour? throw UndertowServletMessages.MESSAGES.streamNotInAsyncMode(); } return anyAreSet(state, FLAG_READY); } @Override public void setWriteListener(final WriteListener writeListener) { if (writeListener == null) { throw UndertowServletMessages.MESSAGES.paramCannotBeNull("writeListener"); } if (listener != null) { throw UndertowServletMessages.MESSAGES.listenerAlreadySet(); } listener = writeListener; channel.getWriteSetter().set(new WriteChannelListener()); state |= FLAG_READY; ioExecutor.execute(new Runnable() { @Override public void run() { channel.resumeWrites(); } }); } private class WriteChannelListener implements ChannelListener { @Override public void handleEvent(final StreamSinkChannel channel) { //flush the channel if it is closed if (anyAreSet(state, FLAG_DELEGATE_SHUTDOWN)) { try { //either it will work, and the channel is closed //or it won't, and we continue with writes resumed channel.flush(); return; } catch (IOException e) { handleError(channel, e); } } //if there is data still to write if (buffer != null) { int res; do { try { res = channel.write(buffer); if (res == 0) { return; } } catch (IOException e) { handleError(channel, e); } } while (buffer.hasRemaining()); buffer = null; } if (anyAreSet(state, FLAG_CLOSED)) { try { channel.shutdownWrites(); state |= FLAG_DELEGATE_SHUTDOWN; channel.flush(); //if this does not succeed we are already resumed anyway } catch (IOException e) { handleError(channel, e); } } else { state |= FLAG_READY; channel.suspendWrites(); try { listener.onWritePossible(); } catch (IOException e) { listener.onError(e); } } } private void handleError(final StreamSinkChannel channel, final IOException e) { try { listener.onError(e); } finally { IoUtils.safeClose(channel); } } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/spec/WebConnectionImpl.java000066400000000000000000000047771420065311100317470ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.spec; import java.io.IOException; import java.util.concurrent.Executor; import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; import javax.servlet.http.WebConnection; import org.xnio.ChannelListener; import org.xnio.IoUtils; import io.undertow.connector.ByteBufferPool; import org.xnio.StreamConnection; /** * @author Stuart Douglas */ public class WebConnectionImpl implements WebConnection { private final StreamConnection channel; private final UpgradeServletOutputStream outputStream; private final UpgradeServletInputStream inputStream; private final Executor ioExecutor; public WebConnectionImpl(final StreamConnection channel, ByteBufferPool bufferPool, Executor ioExecutor) { this.channel = channel; this.ioExecutor = ioExecutor; this.outputStream = new UpgradeServletOutputStream(channel.getSinkChannel(), ioExecutor); this.inputStream = new UpgradeServletInputStream(channel.getSourceChannel(), bufferPool, ioExecutor); channel.getCloseSetter().set(new ChannelListener() { @Override public void handleEvent(StreamConnection channel) { try { close(); } catch (Exception e) { throw new RuntimeException(e); } } }); } @Override public ServletInputStream getInputStream() throws IOException { return inputStream; } @Override public ServletOutputStream getOutputStream() throws IOException { return outputStream; } @Override public void close() throws Exception { try { outputStream.closeBlocking(); } finally { IoUtils.safeClose(inputStream, channel); } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/sse/000077500000000000000000000000001420065311100253465ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/sse/ServerSentEvent.java000066400000000000000000000031331420065311100313130ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.sse; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * * Annotation that can be applied to classes that implement {@link io.undertow.server.handlers.sse.ServerSentEventConnectionCallback} * * These classes will then have handlers registered under the given path. This path is a path template, any * path parameter values can be retrieved from {@link io.undertow.server.handlers.sse.ServerSentEventConnection#getParameter(String)} * * Only a single instance of the callback will be created at deployment time. * * @author Stuart Douglas */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface ServerSentEvent { /** * The path to register this SSE handler. This path can be a path template. */ String value(); } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/sse/ServerSentEventSCI.java000066400000000000000000000074641420065311100316650ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.sse; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.handlers.PathTemplateHandler; import io.undertow.server.handlers.sse.ServerSentEventConnectionCallback; import io.undertow.server.handlers.sse.ServerSentEventHandler; import io.undertow.servlet.api.InstanceHandle; import io.undertow.servlet.spec.ServletContextImpl; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.ServletException; import javax.servlet.annotation.HandlesTypes; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * @author Stuart Douglas */ @HandlesTypes(ServerSentEvent.class) public class ServerSentEventSCI implements ServletContainerInitializer { @Override public void onStartup(Set> c, ServletContext ctx) throws ServletException { if(c == null || c.isEmpty()) { return; } try { final Map callbacks = new HashMap<>(); ServletContextImpl servletContext = (ServletContextImpl) ctx; final List> handles = new ArrayList<>(); for (Class clazz : c) { final ServerSentEvent annotation = clazz.getAnnotation(ServerSentEvent.class); if(annotation == null) { continue; } String path = annotation.value(); final InstanceHandle instance = servletContext.getDeployment().getDeploymentInfo().getClassIntrospecter().createInstanceFactory(clazz).createInstance(); handles.add(instance); callbacks.put(path, (ServerSentEventConnectionCallback) instance.getInstance()); } if(callbacks.isEmpty()) { return; } servletContext.getDeployment().getDeploymentInfo().addInnerHandlerChainWrapper(new HandlerWrapper() { @Override public HttpHandler wrap(HttpHandler handler) { PathTemplateHandler pathTemplateHandler = new PathTemplateHandler(handler, false); for(Map.Entry e : callbacks.entrySet()) { pathTemplateHandler.add(e.getKey(), new ServerSentEventHandler(e.getValue())); } return pathTemplateHandler; } }); servletContext.addListener(new ServletContextListener() { @Override public void contextInitialized(ServletContextEvent sce) { } @Override public void contextDestroyed(ServletContextEvent sce) { for(InstanceHandle h: handles) { h.release(); } } }); } catch (Exception e) { throw new ServletException(e); } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/util/000077500000000000000000000000001420065311100255315ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/util/ConstructorInstanceFactory.java000066400000000000000000000034551420065311100337450ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.util; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import io.undertow.servlet.api.InstanceFactory; import io.undertow.servlet.api.InstanceHandle; /** * @author Stuart Douglas */ public class ConstructorInstanceFactory implements InstanceFactory { private final Constructor constructor; public ConstructorInstanceFactory(final Constructor constructor) { constructor.setAccessible(true); this.constructor = constructor; } @Override public InstanceHandle createInstance() throws InstantiationException { try { final T instance = constructor.newInstance(); return new ImmediateInstanceHandle<>(instance); } catch (IllegalAccessException e) { InstantiationException ite = new InstantiationException(); ite.initCause(e); throw ite; } catch (InvocationTargetException e) { InstantiationException ite = new InstantiationException(); ite.initCause(e); throw ite; } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/util/DefaultClassIntrospector.java000066400000000000000000000024301420065311100333610ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.util; import io.undertow.servlet.api.ClassIntrospecter; import io.undertow.servlet.api.InstanceFactory; /** * @author Stuart Douglas */ public class DefaultClassIntrospector implements ClassIntrospecter { public static final DefaultClassIntrospector INSTANCE = new DefaultClassIntrospector(); private DefaultClassIntrospector() { } @Override public InstanceFactory createInstanceFactory(final Class clazz) throws NoSuchMethodException { return new ConstructorInstanceFactory<>(clazz.getDeclaredConstructor()); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/util/EmptyEnumeration.java000066400000000000000000000024071420065311100317040ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.util; import java.util.Enumeration; /** * @author Stuart Douglas */ public class EmptyEnumeration implements Enumeration { private static final Enumeration INSTANCE = new EmptyEnumeration(); @SuppressWarnings("unchecked") public static Enumeration instance() { return (Enumeration) INSTANCE; } private EmptyEnumeration() { } @Override public boolean hasMoreElements() { return false; } @Override public Object nextElement() { return null; } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/util/ImmediateInstanceFactory.java000066400000000000000000000023241420065311100333100ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.util; import io.undertow.servlet.api.InstanceFactory; import io.undertow.servlet.api.InstanceHandle; /** * @author Stuart Douglas */ public class ImmediateInstanceFactory implements InstanceFactory { private final T instance; public ImmediateInstanceFactory(final T instance) { this.instance = instance; } @Override public InstanceHandle createInstance() throws InstantiationException { return new ImmediateInstanceHandle<>(instance); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/util/ImmediateInstanceHandle.java000066400000000000000000000022031420065311100330700ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.util; import io.undertow.servlet.api.InstanceHandle; /** * @author Stuart Douglas */ public class ImmediateInstanceHandle implements InstanceHandle { private final T instance; public ImmediateInstanceHandle(final T instance) { this.instance = instance; } @Override public T getInstance() { return instance; } @Override public void release() { } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/util/InMemorySessionPersistence.java000066400000000000000000000107461420065311100337140ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.util; import io.undertow.servlet.UndertowServletLogger; import io.undertow.servlet.api.SessionPersistenceManager; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * Session persistence implementation that simply stores session information in memory. * * @author Stuart Douglas */ public class InMemorySessionPersistence implements SessionPersistenceManager { private static final Map> data = new ConcurrentHashMap<>(); @Override public void persistSessions(String deploymentName, Map sessionData) { try { final Map serializedData = new HashMap<>(); for (Map.Entry sessionEntry : sessionData.entrySet()) { Map data = new HashMap<>(); for (Map.Entry sessionAttribute : sessionEntry.getValue().getSessionData().entrySet()) { try { final ByteArrayOutputStream out = new ByteArrayOutputStream(); final ObjectOutputStream objectOutputStream = new ObjectOutputStream(out); objectOutputStream.writeObject(sessionAttribute.getValue()); objectOutputStream.close(); data.put(sessionAttribute.getKey(), out.toByteArray()); } catch (Exception e) { UndertowServletLogger.ROOT_LOGGER.failedToPersistSessionAttribute(sessionAttribute.getKey(), sessionAttribute.getValue(), sessionEntry.getKey(), e); } } serializedData.put(sessionEntry.getKey(), new SessionEntry(sessionEntry.getValue().getExpiration(), data)); } data.put(deploymentName, serializedData); } catch (Exception e) { UndertowServletLogger.ROOT_LOGGER.failedToPersistSessions(e); } } @Override public Map loadSessionAttributes(String deploymentName, final ClassLoader classLoader) { try { long time = System.currentTimeMillis(); Map data = this.data.remove(deploymentName); if (data != null) { Map ret = new HashMap<>(); for (Map.Entry sessionEntry : data.entrySet()) { if (sessionEntry.getValue().expiry.getTime() > time) { Map session = new HashMap<>(); for (Map.Entry sessionAttribute : sessionEntry.getValue().data.entrySet()) { final ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(sessionAttribute.getValue())); session.put(sessionAttribute.getKey(), in.readObject()); } ret.put(sessionEntry.getKey(), new PersistentSession(sessionEntry.getValue().expiry, session)); } } return ret; } } catch (Exception e) { UndertowServletLogger.ROOT_LOGGER.failedtoLoadPersistentSessions(e); } return null; } @Override public void clear(String deploymentName) { } static final class SessionEntry { private final Date expiry; private final Map data; private SessionEntry(Date expiry, Map data) { this.expiry = expiry; this.data = data; } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/util/IteratorEnumeration.java000066400000000000000000000024011420065311100323710ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.util; import java.util.Enumeration; import java.util.Iterator; /** * Wrapper to convert an iterator to an enumeration * * @author Stuart Douglas */ public class IteratorEnumeration implements Enumeration { private final Iterator iterator; public IteratorEnumeration(final Iterator iterator) { this.iterator = iterator; } @Override public boolean hasMoreElements() { return iterator.hasNext(); } @Override public T nextElement() { return iterator.next(); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/util/SavedRequest.java000066400000000000000000000203241420065311100310100ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.util; import io.undertow.UndertowLogger; import io.undertow.UndertowOptions; import io.undertow.server.Connectors; import io.undertow.server.HttpServerExchange; import io.undertow.server.session.Session; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.servlet.spec.HttpSessionImpl; import io.undertow.util.HeaderMap; import io.undertow.util.HeaderValues; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.ImmediatePooledByteBuffer; import javax.servlet.http.HttpSession; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.nio.ByteBuffer; import java.security.AccessController; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; /** * Saved servlet request. * * @author Stuart Douglas */ public class SavedRequest implements Serializable { private static final String SESSION_KEY = SavedRequest.class.getName(); private final byte[] data; private final int dataLength; private final HttpString method; private final String requestPath; private final HashMap> headerMap = new HashMap<>(); public SavedRequest(byte[] data, int dataLength, HttpString method, String requestPath, HeaderMap headerMap) { this.data = data; this.dataLength = dataLength; this.method = method; this.requestPath = requestPath; for(HeaderValues val : headerMap) { this.headerMap.put(val.getHeaderName(), new ArrayList<>(val)); } } /** * With added possibility to save data from buffer instead f from request body, there has to be method which returns max allowed buffer size to save. * * @param exchange * @return */ public static int getMaxBufferSizeToSave(final HttpServerExchange exchange) { int maxSize = exchange.getConnection().getUndertowOptions().get(UndertowOptions.MAX_BUFFERED_REQUEST_SIZE, UndertowOptions.DEFAULT_MAX_BUFFERED_REQUEST_SIZE); return maxSize; } public static void trySaveRequest(final HttpServerExchange exchange) { int maxSize = getMaxBufferSizeToSave(exchange); if (maxSize > 0) { //if this request has a body try and cache the response if (!exchange.isRequestComplete()) { final long requestContentLength = exchange.getRequestContentLength(); if (requestContentLength > maxSize) { UndertowLogger.REQUEST_LOGGER.debugf("Request to %s was to large to save", exchange.getRequestURI()); return;//failed to save the request, we just return } //TODO: we should really be used pooled buffers //TODO: we should probably limit the number of saved requests at any given time byte[] buffer = new byte[maxSize]; int read = 0; int res = 0; InputStream in = exchange.getInputStream(); try { while ((res = in.read(buffer, read, buffer.length - read)) > 0) { read += res; if (read == maxSize) { UndertowLogger.REQUEST_LOGGER.debugf("Request to %s was to large to save", exchange.getRequestURI()); return;//failed to save the request, we just return } } //save request from buffer trySaveRequest(exchange, buffer, read); } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); } } } } public static void trySaveRequest(final HttpServerExchange exchange, final byte[] buffer, int length) { int maxSize = exchange.getConnection().getUndertowOptions().get(UndertowOptions.MAX_BUFFERED_REQUEST_SIZE, UndertowOptions.DEFAULT_MAX_BUFFERED_REQUEST_SIZE); if (maxSize > 0) { if (length > maxSize) { UndertowLogger.REQUEST_LOGGER.debugf("Request to %s was to large to save", exchange.getRequestURI()); return;//failed to save the request, we just return } //TODO: we should really be used pooled buffers //TODO: we should probably limit the number of saved requests at any given time HeaderMap headers = new HeaderMap(); for (HeaderValues entry : exchange.getRequestHeaders()) { if (entry.getHeaderName().equals(Headers.CONTENT_LENGTH) || entry.getHeaderName().equals(Headers.TRANSFER_ENCODING) || entry.getHeaderName().equals(Headers.CONNECTION)) { continue; } headers.putAll(entry.getHeaderName(), entry); } SavedRequest request = new SavedRequest(buffer, length, exchange.getRequestMethod(), exchange.getRelativePath(), exchange.getRequestHeaders()); final ServletRequestContext sc = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); HttpSessionImpl session = sc.getCurrentServletContext().getSession(exchange, true); Session underlyingSession; if (System.getSecurityManager() == null) { underlyingSession = session.getSession(); } else { underlyingSession = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(session)); } underlyingSession.setAttribute(SESSION_KEY, request); } } public static void tryRestoreRequest(final HttpServerExchange exchange, HttpSession session) { if(session instanceof HttpSessionImpl) { Session underlyingSession; if(System.getSecurityManager() == null) { underlyingSession = ((HttpSessionImpl) session).getSession(); } else { underlyingSession = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(session)); } SavedRequest request = (SavedRequest) underlyingSession.getAttribute(SESSION_KEY); if(request != null) { if(request.requestPath.equals(exchange.getRelativePath()) && exchange.isRequestComplete()) { UndertowLogger.REQUEST_LOGGER.debugf("restoring request body for request to %s", request.requestPath); exchange.setRequestMethod(request.method); Connectors.ungetRequestBytes(exchange, new ImmediatePooledByteBuffer(ByteBuffer.wrap(request.data, 0, request.dataLength))); underlyingSession.removeAttribute(SESSION_KEY); //clear the existing header map of everything except the connection header //TODO: are there other headers we should preserve? Iterator headerIterator = exchange.getRequestHeaders().iterator(); while (headerIterator.hasNext()) { HeaderValues header = headerIterator.next(); if(!header.getHeaderName().equals(Headers.CONNECTION)) { headerIterator.remove(); } } for(Map.Entry> header : request.headerMap.entrySet()) { exchange.getRequestHeaders().putAll(header.getKey(), header.getValue()); } } } } } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/websockets/000077500000000000000000000000001420065311100267255ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/websockets/SecurityActions.java000066400000000000000000000026031420065311100327210ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.websockets; import java.security.AccessController; import java.security.PrivilegedAction; import io.undertow.servlet.handlers.ServletRequestContext; class SecurityActions { static ServletRequestContext requireCurrentServletRequestContext() { if (System.getSecurityManager() == null) { return ServletRequestContext.requireCurrent(); } else { return AccessController.doPrivileged(new PrivilegedAction() { @Override public ServletRequestContext run() { return ServletRequestContext.requireCurrent(); } }); } } } ServletWebSocketHttpExchange.java000066400000000000000000000165541420065311100352620ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/websockets/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.websockets; import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpUpgradeListener; import io.undertow.util.AttachmentKey; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.spi.WebSocketHttpExchange; import org.xnio.FinishedIoFuture; import org.xnio.FutureResult; import org.xnio.IoFuture; import org.xnio.IoUtils; import org.xnio.OptionMap; import io.undertow.connector.ByteBufferPool; import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.security.Principal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; /** * @author Stuart Douglas */ public class ServletWebSocketHttpExchange implements WebSocketHttpExchange { private final HttpServletRequest request; private final HttpServletResponse response; private final HttpServerExchange exchange; private final Set peerConnections; public ServletWebSocketHttpExchange(final HttpServletRequest request, final HttpServletResponse response, Set peerConnections) { this.request = request; this.response = response; this.peerConnections = peerConnections; this.exchange = SecurityActions.requireCurrentServletRequestContext().getOriginalRequest().getExchange(); } @Override public void putAttachment(final AttachmentKey key, final T value) { exchange.putAttachment(key, value); } @Override public T getAttachment(final AttachmentKey key) { return exchange.getAttachment(key); } @Override public String getRequestHeader(final String headerName) { return request.getHeader(headerName); } @Override public Map> getRequestHeaders() { Map> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); final Enumeration headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String header = headerNames.nextElement(); final Enumeration theHeaders = request.getHeaders(header); final List vals = new ArrayList<>(); headers.put(header, vals); while (theHeaders.hasMoreElements()) { vals.add(theHeaders.nextElement()); } } return Collections.unmodifiableMap(headers); } @Override public String getResponseHeader(final String headerName) { return response.getHeader(headerName); } @Override public Map> getResponseHeaders() { Map> headers = new HashMap<>(); final Collection headerNames = response.getHeaderNames(); for (String header : headerNames) { headers.put(header, new ArrayList<>(response.getHeaders(header))); } return Collections.unmodifiableMap(headers); } @Override public void setResponseHeaders(final Map> headers) { for (String header : response.getHeaderNames()) { response.setHeader(header, null); } for (Map.Entry> entry : headers.entrySet()) { for (String val : entry.getValue()) { response.addHeader(entry.getKey(), val); } } } @Override public void setResponseHeader(final String headerName, final String headerValue) { response.setHeader(headerName, headerValue); } @Override public void upgradeChannel(final HttpUpgradeListener upgradeCallback) { exchange.upgradeChannel(upgradeCallback); } @Override public IoFuture sendData(final ByteBuffer data) { try { final ServletOutputStream outputStream = response.getOutputStream(); while (data.hasRemaining()) { outputStream.write(data.get()); } return new FinishedIoFuture<>(null); } catch (IOException e) { final FutureResult ioFuture = new FutureResult<>(); ioFuture.setException(e); return ioFuture.getIoFuture(); } } @Override public IoFuture readRequestData() { final ByteArrayOutputStream data = new ByteArrayOutputStream(); try { final ServletInputStream in = request.getInputStream(); byte[] buf = new byte[1024]; int r; while ((r = in.read(buf)) != -1) { data.write(buf, 0, r); } return new FinishedIoFuture<>(data.toByteArray()); } catch (IOException e) { final FutureResult ioFuture = new FutureResult<>(); ioFuture.setException(e); return ioFuture.getIoFuture(); } } @Override public void endExchange() { //noop } @Override public void close() { IoUtils.safeClose(exchange.getConnection()); } @Override public String getRequestScheme() { return request.getScheme(); } @Override public String getRequestURI() { return request.getRequestURI() + (request.getQueryString() == null ? "" : "?" + request.getQueryString()); } @Override public ByteBufferPool getBufferPool() { return exchange.getConnection().getByteBufferPool(); } @Override public String getQueryString() { return request.getQueryString(); } @Override public Object getSession() { return request.getSession(false); } @Override public Map> getRequestParameters() { Map> params = new HashMap<>(); for(Map.Entry param : request.getParameterMap().entrySet()) { params.put(param.getKey(), new ArrayList<>(Arrays.asList(param.getValue()))); } return params; } @Override public Principal getUserPrincipal() { return request.getUserPrincipal(); } @Override public boolean isUserInRole(String role) { return request.isUserInRole(role); } @Override public Set getPeerConnections() { return peerConnections; } @Override public OptionMap getOptions() { return exchange.getConnection().getUndertowOptions(); } } undertow-2.2.16.Final/servlet/src/main/java/io/undertow/servlet/websockets/WebSocketServlet.java000066400000000000000000000114131420065311100330230ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.websockets; import io.undertow.UndertowLogger; import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpUpgradeListener; import io.undertow.servlet.UndertowServletMessages; import io.undertow.util.StatusCodes; import io.undertow.websockets.WebSocketConnectionCallback; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.protocol.Handshake; import io.undertow.websockets.core.protocol.version07.Hybi07Handshake; import io.undertow.websockets.core.protocol.version08.Hybi08Handshake; import io.undertow.websockets.core.protocol.version13.Hybi13Handshake; import org.xnio.StreamConnection; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * @author Stuart Douglas */ public class WebSocketServlet extends HttpServlet { public static final String SESSION_HANDLER = "io.undertow.handler"; private final List handshakes; private WebSocketConnectionCallback callback; private Set peerConnections; public WebSocketServlet() { this.handshakes = handshakes(); } public WebSocketServlet(WebSocketConnectionCallback callback) { this.callback = callback; this.handshakes = handshakes(); } @Override public void init(final ServletConfig config) throws ServletException { super.init(config); peerConnections = Collections.newSetFromMap(new ConcurrentHashMap()); try { final String sessionHandler = config.getInitParameter(SESSION_HANDLER); if (sessionHandler != null) { final Class clazz = Class.forName(sessionHandler, true, Thread.currentThread().getContextClassLoader()); final Object handler = clazz.newInstance(); this.callback = (WebSocketConnectionCallback) handler; } //TODO: set properties based on init params } catch (ClassNotFoundException e) { throw new ServletException(e); } catch (InstantiationException e) { throw new ServletException(e); } catch (IllegalAccessException e) { throw new ServletException(e); } if (callback == null) { throw UndertowServletMessages.MESSAGES.noWebSocketHandler(); } } @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { final ServletWebSocketHttpExchange facade = new ServletWebSocketHttpExchange(req, resp, peerConnections); Handshake handshaker = null; for (Handshake method : handshakes) { if (method.matches(facade)) { handshaker = method; break; } } if (handshaker == null) { UndertowLogger.REQUEST_LOGGER.debug("Could not find hand shaker for web socket request"); resp.sendError(StatusCodes.BAD_REQUEST); return; } final Handshake selected = handshaker; facade.upgradeChannel(new HttpUpgradeListener() { @Override public void handleUpgrade(StreamConnection streamConnection, HttpServerExchange exchange) { WebSocketChannel channel = selected.createChannel(facade, streamConnection, facade.getBufferPool()); peerConnections.add(channel); callback.onConnect(facade, channel); } }); handshaker.handshake(facade); } protected List handshakes() { List handshakes = new ArrayList<>(); handshakes.add(new Hybi13Handshake()); handshakes.add(new Hybi08Handshake()); handshakes.add(new Hybi07Handshake()); return handshakes; } } undertow-2.2.16.Final/servlet/src/main/resources/000077500000000000000000000000001420065311100217035ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/resources/META-INF/000077500000000000000000000000001420065311100230435ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/resources/META-INF/services/000077500000000000000000000000001420065311100246665ustar00rootroot00000000000000io.undertow.attribute.ExchangeAttributeBuilder000066400000000000000000000016771420065311100357400ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/resources/META-INF/servicesio.undertow.servlet.attribute.ServletRequestAttribute$Builder io.undertow.servlet.attribute.ServletSessionAttribute$Builder io.undertow.servlet.attribute.ServletSessionIdAttribute$Builder io.undertow.servlet.attribute.ServletRequestURLAttribute$Builder io.undertow.servlet.attribute.ServletRequestLineAttribute$Builder io.undertow.servlet.attribute.ServletRelativePathAttribute$Builder io.undertow.servlet.attribute.ServletRequestedSessionIdAttribute$Builder io.undertow.servlet.attribute.ServletRequestedSessionIdFromCookieAttribute$Builder io.undertow.servlet.attribute.ServletRequestedSessionIdValidAttribute$Builder io.undertow.servlet.attribute.ServletRequestLocaleAttribute$Builder io.undertow.servlet.attribute.ServletRequestCharacterEncodingAttribute$Builder io.undertow.servlet.attribute.ServletContextAttribute$Builder io.undertow.servlet.attribute.ServletRequestParameterAttribute$Builder io.undertow.servlet.attribute.ServletNameAttribute$Builder io.undertow.predicate.PredicateBuilder000066400000000000000000000002521420065311100341530ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/resources/META-INF/servicesio.undertow.servlet.predicate.DispatcherTypePredicate$Builder io.undertow.servlet.predicate.DirectoryPredicate$Builder io.undertow.servlet.predicate.FilePredicate$Builderio.undertow.server.handlers.builder.HandlerBuilder000066400000000000000000000000671420065311100364260ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/resources/META-INF/servicesio.undertow.servlet.handlers.MarkSecureHandler$Builder javax.servlet.ServletContainerInitializer000066400000000000000000000000521420065311100350150ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/main/resources/META-INF/servicesio.undertow.servlet.sse.ServerSentEventSCIundertow-2.2.16.Final/servlet/src/test/000077500000000000000000000000001420065311100177245ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/000077500000000000000000000000001420065311100206455ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/000077500000000000000000000000001420065311100212545ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/000077500000000000000000000000001420065311100231235ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/000077500000000000000000000000001420065311100246075ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/000077500000000000000000000000001420065311100255665ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/HandlerListingTestCase.java000066400000000000000000000134451420065311100330030ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test; import io.undertow.attribute.ExchangeAttribute; import io.undertow.predicate.PredicateBuilder; import io.undertow.server.handlers.builder.HandlerBuilder; import org.junit.Test; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.ServiceLoader; import static java.lang.System.out; /** * not a real test, but used to generate documentation * * @author Stuart Douglas */ public class HandlerListingTestCase { @Test public void listHandlers() { out.println(); out.println(); out.println(); out.println("handlers"); ArrayList builds = new ArrayList<>(); for (HandlerBuilder i : ServiceLoader.load(HandlerBuilder.class, getClass().getClassLoader())) { builds.add(i); } Collections.sort(builds, new Comparator() { @Override public int compare(HandlerBuilder o1, HandlerBuilder o2) { return o1.name().compareTo(o2.name()); } }); for (HandlerBuilder handler : builds) { out.print("|" + handler.name()); out.print("\t|"); List parms = new ArrayList<>(handler.parameters().keySet()); Collections.sort(parms); Iterator it = parms.iterator(); while (it.hasNext()) { String paramName = it.next(); out.print(paramName + ": "); Class obj = handler.parameters().get(paramName); if (obj == ExchangeAttribute.class) { out.print("attribute"); } else if (obj.equals(ExchangeAttribute[].class)) { out.print("attribute[]"); } else if (obj.equals(String.class)) { out.print("String"); } else if (obj.equals(String[].class)) { out.print("String[]"); } else if (obj.equals(Long.class)) { out.print("Long"); } else if (obj.equals(Long[].class)) { out.print("Long[]"); } else if (obj.equals(Boolean.class)) { out.print("Boolean"); } else { out.print(obj); } if(handler.requiredParameters() != null && handler.requiredParameters().contains(paramName)) { out.print(" (required)"); } if (it.hasNext()) { out.print(", "); } } out.print("\t|"); if(handler.defaultParameter() != null) { out.print(handler.defaultParameter()); } out.print("\t|\n"); } } @Test public void listPredicates() { out.println(); out.println(); out.println(); out.println("predicates"); ArrayList builds = new ArrayList(); for (PredicateBuilder i : ServiceLoader.load(PredicateBuilder.class, getClass().getClassLoader())) { builds.add(i); } Collections.sort(builds, new Comparator() { @Override public int compare(PredicateBuilder o1, PredicateBuilder o2) { return o1.name().compareTo(o2.name()); } }); for (PredicateBuilder handler : builds) { out.print("|" + handler.name()); out.print("\t|"); List parms = new ArrayList<>(handler.parameters().keySet()); Collections.sort(parms); Iterator it = parms.iterator(); while (it.hasNext()) { String paramName = it.next(); out.print(paramName + ": "); Class obj = handler.parameters().get(paramName); if (obj == ExchangeAttribute.class) { out.print("attribute"); } else if (obj.equals(ExchangeAttribute[].class)) { out.print("attribute[]"); } else if (obj.equals(String.class)) { out.print("String"); } else if (obj.equals(String[].class)) { out.print("String[]"); } else if (obj.equals(Long.class)) { out.print("Long"); } else if (obj.equals(Long[].class)) { out.print("Long[]"); } else if (obj.equals(Boolean.class)) { out.print("Boolean"); } else { out.print(obj); } if(handler.requiredParameters().contains(paramName)) { out.print(" (required)"); } if (it.hasNext()) { out.print(", "); } } out.print("\t|"); if(handler.defaultParameter() != null) { out.print(handler.defaultParameter()); } out.print("\t|\n"); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/ProxyForwardedTestCase.java000066400000000000000000000127311420065311100330500ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test; import io.undertow.server.HttpHandler; import io.undertow.server.handlers.ForwardedHandler; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.constant.GenericServletConstants; import io.undertow.servlet.test.util.ProxyPeerXForwardedHandlerServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.ProxyIgnore; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.util.EntityUtils; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; /** * @author Moulali Shikalwadi */ @RunWith(DefaultServer.class) @ProxyIgnore public class ProxyForwardedTestCase { protected static int PORT; @BeforeClass public static void setup() throws ServletException { PORT = DefaultServer.getHostPort("default"); final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("servlet", ProxyPeerXForwardedHandlerServlet.class) .addMapping("/forwardedHandler"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlet(s); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); HttpHandler startHandler = manager.start(); startHandler = new ForwardedHandler(startHandler, false); root.addPrefixPath(builder.getContextPath(), startHandler); DefaultServer.setRootHandler(root); } @Test public void testForwardedHandler() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet getForwardedHandler = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/forwardedHandler"); getForwardedHandler.addHeader(Headers.FORWARDED_STRING, "for=192.0.2.43"); getForwardedHandler.addHeader(Headers.FORWARDED_STRING, "by=203.0.113.60"); getForwardedHandler.addHeader(Headers.FORWARDED_STRING, "proto=http"); getForwardedHandler.addHeader(Headers.FORWARDED_STRING, "host=192.0.2.10:8888"); HttpResponse result = client.execute(getForwardedHandler); HttpEntity entity = result.getEntity(); String results = EntityUtils.toString(entity); Map map = convertWithStream(results); Socket socket = new Socket(); socket.connect(new InetSocketAddress(DefaultServer.getHostAddress(), PORT)); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(socket.getLocalAddress().getHostAddress(), map.get(GenericServletConstants.LOCAL_ADDR)); Assert.assertEquals(socket.getLocalAddress().getHostName(), map.get(GenericServletConstants.LOCAL_NAME)); Assert.assertEquals(PORT, Integer.parseInt(map.get(GenericServletConstants.LOCAL_PORT))); Assert.assertEquals("192.0.2.10", map.get(GenericServletConstants.SERVER_NAME)); Assert.assertEquals("8888", map.get(GenericServletConstants.SERVER_PORT)); Assert.assertEquals("192.0.2.43", map.get(GenericServletConstants.REMOTE_ADDR)); Assert.assertEquals("0", map.get(GenericServletConstants.REMOTE_PORT)); } finally { client.getConnectionManager().shutdown(); } } private Map convertWithStream(String mapAsString) { Map map = new HashMap(); if (mapAsString != null) { mapAsString = mapAsString.substring(1, mapAsString.length() - 1); map = Arrays.stream(mapAsString.split(",")) .map(entry -> entry.split("=")) .collect(Collectors.toMap(entry -> entry[0].trim(), entry -> entry[1].trim())); } return map; } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/ProxyXForwardedTestCase.java000066400000000000000000000127641420065311100332060ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test; import io.undertow.server.HttpHandler; import io.undertow.server.handlers.PathHandler; import io.undertow.server.handlers.ProxyPeerAddressHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.constant.GenericServletConstants; import io.undertow.servlet.test.util.ProxyPeerXForwardedHandlerServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.ProxyIgnore; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.util.EntityUtils; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; /** * @author Moulali Shikalwadi */ @RunWith(DefaultServer.class) @ProxyIgnore public class ProxyXForwardedTestCase { protected static int PORT; @BeforeClass public static void setup() throws ServletException { PORT = DefaultServer.getHostPort("default"); final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("servlet", ProxyPeerXForwardedHandlerServlet.class) .addMapping("/proxyPeerHandler"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlet(s); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); HttpHandler startHandler = manager.start(); startHandler = new ProxyPeerAddressHandler(startHandler, false); root.addPrefixPath(builder.getContextPath(), startHandler); DefaultServer.setRootHandler(root); } @Test public void testProxyPeerHandler() throws IOException, ServletException { TestHttpClient client = new TestHttpClient(); try { HttpGet getProxyPeerHandler = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/proxyPeerHandler"); getProxyPeerHandler.addHeader(Headers.X_FORWARDED_FOR_STRING, "192.0.2.43"); getProxyPeerHandler.addHeader(Headers.X_FORWARDED_PROTO_STRING, "http"); getProxyPeerHandler.addHeader(Headers.X_FORWARDED_HOST_STRING, "192.0.2.10"); getProxyPeerHandler.addHeader(Headers.X_FORWARDED_PORT_STRING, "8888"); HttpResponse result = client.execute(getProxyPeerHandler); HttpEntity entity = result.getEntity(); String results = EntityUtils.toString(entity); Map map = convertWithStream(results); Socket socket = new Socket(); socket.connect(new InetSocketAddress(DefaultServer.getHostAddress(), PORT)); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(socket.getLocalAddress().getHostAddress(), map.get(GenericServletConstants.LOCAL_ADDR)); Assert.assertEquals(socket.getLocalAddress().getHostName(), map.get(GenericServletConstants.LOCAL_NAME)); Assert.assertEquals(PORT, Integer.parseInt(map.get(GenericServletConstants.LOCAL_PORT))); Assert.assertEquals("192.0.2.10", map.get(GenericServletConstants.SERVER_NAME)); Assert.assertEquals("8888", map.get(GenericServletConstants.SERVER_PORT)); Assert.assertEquals("192.0.2.43", map.get(GenericServletConstants.REMOTE_ADDR)); Assert.assertEquals("0", map.get(GenericServletConstants.REMOTE_PORT)); } finally { client.getConnectionManager().shutdown(); } } private Map convertWithStream(String mapAsString) { Map map = new HashMap(); if(mapAsString != null){ mapAsString = mapAsString.substring(1, mapAsString.length() - 1); map = Arrays.stream(mapAsString.split(",")) .map(entry -> entry.split("=")) .collect(Collectors.toMap(entry -> entry[0].trim(), entry -> entry[1].trim())); } return map; } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/SimpleServletTestCase.java000066400000000000000000000062671420065311100326760ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test; import java.io.IOException; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.MessageServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import io.undertow.testutils.TestHttpClient; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class SimpleServletTestCase { public static final String HELLO_WORLD = "Hello World"; @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("servlet", MessageServlet.class) .addInitParam(MessageServlet.MESSAGE, HELLO_WORLD) .addMapping("/aa"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlet(s); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testSimpleHttpServlet() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/aa"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(HELLO_WORLD, response); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/async/000077500000000000000000000000001420065311100267035ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/async/AnotherAsyncServlet.java000066400000000000000000000033561420065311100335200ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.async; import java.io.IOException; import javax.servlet.AsyncContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Jozef Hartinger */ public class AnotherAsyncServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { final AsyncContext ctx = req.startAsync(); Thread t = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(100); resp.setContentType("text/plain"); resp.getWriter().write(AnotherAsyncServlet.class.getSimpleName()); ctx.complete(); } catch (Exception e) { throw new RuntimeException(e); } } }); t.start(); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/async/AsyncDispatchServlet.java000066400000000000000000000027031420065311100336520ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.async; import java.io.IOException; import javax.servlet.AsyncContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class AsyncDispatchServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { final AsyncContext ac = req.startAsync(req, new TestAsyncRespWrapper(resp)); ac.start(new Runnable() { @Override public void run() { ac.dispatch("/message"); } }); } }AsyncDoubleCompleteServlet.java000066400000000000000000000027001420065311100347340ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/async/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.async; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author Carter Kozak */ public class AsyncDoubleCompleteServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { req.startAsync(); resp.getWriter().write(SimpleAsyncTestCase.HELLO_WORLD); if (req.isAsyncStarted()) { req.getAsyncContext().complete(); } if (req.isAsyncStarted()) { req.getAsyncContext().complete(); } } } AsyncErrorListenerServlet.java000066400000000000000000000043151420065311100346340ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/async/* * JBoss, Home of Professional Open Source. * Copyright 2018 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * 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. */ package io.undertow.servlet.test.async; import java.io.IOException; import java.util.concurrent.LinkedBlockingDeque; import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class AsyncErrorListenerServlet extends HttpServlet { static final LinkedBlockingDeque EVENTS = new LinkedBlockingDeque<>(); @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { AsyncContext ac = req.startAsync(); ac.addListener(new AsyncListener() { @Override public void onComplete(AsyncEvent event) throws IOException { EVENTS.add("COMPLETED"); } @Override public void onTimeout(AsyncEvent event) throws IOException { } @Override public void onError(AsyncEvent event) throws IOException { EVENTS.add("ERROR"); } @Override public void onStartAsync(AsyncEvent event) throws IOException { } }); throw new RuntimeException("FAILED"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/async/AsyncErrorServlet.java000066400000000000000000000021021420065311100331750ustar00rootroot00000000000000package io.undertow.servlet.test.async; import io.undertow.util.StatusCodes; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author Stuart Douglas */ public class AsyncErrorServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { req.startAsync(); Thread t = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(100); resp.sendError(StatusCodes.INTERNAL_SERVER_ERROR); } catch (Exception e) { throw new RuntimeException(e); } } }); t.start(); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/async/AsyncServlet.java000066400000000000000000000030771420065311100321770ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.async; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class AsyncServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { req.startAsync(); Thread t = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } req.getAsyncContext().dispatch("/message"); } }); t.start(); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/async/SimpleAsyncTestCase.java000066400000000000000000000252501420065311100334350ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.async; import io.undertow.servlet.ServletExtension; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.ErrorPage; import io.undertow.servlet.api.ServletStackTraces; import io.undertow.servlet.api.ThreadSetupHandler; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.servlet.test.util.MessageServlet; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletContext; import javax.servlet.ServletException; import java.io.IOException; import java.text.ParseException; import java.util.Date; import java.util.concurrent.TimeUnit; import static io.undertow.servlet.Servlets.servlet; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class SimpleAsyncTestCase { public static final String HELLO_WORLD = "Hello World"; private static class SimpleDateThreadSetupHandler implements ThreadSetupHandler { @Override public Action create(Action action) { return (exchange, context) -> { SimpleDateThreadLocalAsyncServlet.initThreadLocalSimpleDate(); try { return action.call(exchange, context); } finally { SimpleDateThreadLocalAsyncServlet.removeThreadLocalSimpleDate(); } }; } }; @BeforeClass public static void setup() throws ServletException { DeploymentUtils.setupServlet(new ServletExtension() { @Override public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servletContext) { deploymentInfo.setServletStackTraces(ServletStackTraces.NONE); deploymentInfo.addErrorPages(new ErrorPage("/500", StatusCodes.INTERNAL_SERVER_ERROR)); deploymentInfo.addThreadSetupAction(new SimpleDateThreadSetupHandler()); } }, servlet("messageServlet", MessageServlet.class) .addInitParam(MessageServlet.MESSAGE, HELLO_WORLD) .setAsyncSupported(true) .addMapping("/message"), servlet("500", MessageServlet.class) .addInitParam(MessageServlet.MESSAGE, "500") .setAsyncSupported(true) .addMapping("/500"), servlet("asyncServlet", AsyncServlet.class) .addInitParam(MessageServlet.MESSAGE, HELLO_WORLD) .setAsyncSupported(true) .addMapping("/async"), servlet("asyncServlet2", AnotherAsyncServlet.class) .setAsyncSupported(true) .addMapping("/async2"), servlet("error", AsyncErrorServlet.class) .setAsyncSupported(true) .addMapping("/error"), servlet("errorlistener", AsyncErrorListenerServlet.class) .setAsyncSupported(true) .addMapping("/errorlistener"), servlet("dispatch", AsyncDispatchServlet.class) .setAsyncSupported(true) .addMapping("/dispatch"), servlet("doubleCompleteServlet", AsyncDoubleCompleteServlet.class) .setAsyncSupported(true) .addMapping("/double-complete"), servlet("simpleDateThreadLocal", SimpleDateThreadLocalAsyncServlet.class) .setAsyncSupported(true) .addMapping("/simple-date-thread-local")); } @Test public void testSimpleHttpServlet() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/async"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(HELLO_WORLD, response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testSimpleHttpAsyncServletWithoutDispatch() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/async2"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(AnotherAsyncServlet.class.getSimpleName(), response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testErrorServlet() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/error"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.INTERNAL_SERVER_ERROR, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("500", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testErrorListenerServlet() throws Exception { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/errorlistener"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.INTERNAL_SERVER_ERROR, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("500", response); Assert.assertEquals("ERROR", AsyncErrorListenerServlet.EVENTS.poll(10, TimeUnit.SECONDS)); Assert.assertEquals("COMPLETED", AsyncErrorListenerServlet.EVENTS.poll(10, TimeUnit.SECONDS)); } finally { client.getConnectionManager().shutdown(); } } @Test public void testWrappedDispatch() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/dispatch"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("wrapped: " + HELLO_WORLD, response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testErrorServletWithPostData() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext/error"); post.setEntity(new StringEntity("Post body stuff")); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.INTERNAL_SERVER_ERROR, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("500", response); post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext/error"); post.setEntity(new StringEntity("Post body stuff")); result = client.execute(post); Assert.assertEquals(StatusCodes.INTERNAL_SERVER_ERROR, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("500", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testServletCompletesTwiceOnInitialThread() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/double-complete"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(HELLO_WORLD, response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testSimpleDateThreahLocalAsyncServlet() throws IOException, ParseException { TestHttpClient client = new TestHttpClient(); try { final Date start = new Date(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/simple-date-thread-local"); HttpResponse result = client.execute(get); Assert.assertEquals("Response status is not OK", StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertNotEquals("Date thread-local was not found", SimpleDateThreadLocalAsyncServlet.NULL_THREAD_LOCAL, response); final Date date = SimpleDateThreadLocalAsyncServlet.parseDate(response); Assert.assertTrue("Date thread-local is not in range", date.compareTo(start) >= 0 && date.compareTo(new Date()) <= 0); } finally { client.getConnectionManager().shutdown(); } } } SimpleDateThreadLocalAsyncServlet.java000066400000000000000000000057151420065311100361740ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/async/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.async; import java.io.IOException; import java.io.PrintWriter; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import javax.servlet.AsyncContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** *

    A servlet that executes asynchronous start method and searches * for a thread-local Date variable in it. The date is returned in the response * formatted as a String. The thread-local should be set by a setup action * configured via the deployment info. This way the servlet ensures that * setup actions were executed OK for the runnable.

    * * @author rmartinc */ public class SimpleDateThreadLocalAsyncServlet extends HttpServlet { public static final String NULL_THREAD_LOCAL = "NULL_THREAD_LOCAL"; private static final String DATE_FORMAT = "yyyyMMddHHmmssSSSZ"; private static final ThreadLocal simpleDateThreadLocal = new ThreadLocal<>(); public static Date getThreadLocalSimpleDate() { return simpleDateThreadLocal.get(); } public static void initThreadLocalSimpleDate() { simpleDateThreadLocal.set(new Date()); } public static void removeThreadLocalSimpleDate() { simpleDateThreadLocal.remove(); } public static Date parseDate(String date) throws ParseException { return new SimpleDateFormat(DATE_FORMAT).parse(date); } public static String formatDate(Date date) { return new SimpleDateFormat(DATE_FORMAT).format(date); } @Override protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { final AsyncContext ac = request.startAsync(request, response); ac.start(() -> { try { response.setStatus(HttpServletResponse.SC_OK); Date date = getThreadLocalSimpleDate(); try (PrintWriter pw = response.getWriter()) { pw.write(date == null? NULL_THREAD_LOCAL : formatDate(date)); } } catch (IOException e) { e.printStackTrace(); } }); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/async/TestAsyncRespWrapper.java000066400000000000000000000023311420065311100336550ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.async; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; class TestAsyncRespWrapper extends HttpServletResponseWrapper { TestAsyncRespWrapper(HttpServletResponse resp) { super(resp); } @Override public PrintWriter getWriter() throws IOException { PrintWriter writer = super.getWriter(); writer.write("wrapped: "); return writer; } }undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/charset/000077500000000000000000000000001420065311100272175ustar00rootroot00000000000000CharacterEncodingTestCase.java000066400000000000000000000060601420065311100350040ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/charset/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.charset; import java.io.IOException; import javax.servlet.ServletException; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class CharacterEncodingTestCase { @BeforeClass public static void setup() throws ServletException { DeploymentUtils.setupServlet( new ServletInfo("servlet", CharsetServlet.class) .addMapping("/")); } public static byte[] toByteArray(int[] source) { byte[] ret = new byte[source.length]; for (int i = 0; i < source.length; ++i) { ret[i] = (byte) (0xff & source[i]); } return ret; } private static final byte[] UTF16 = toByteArray(new int[]{0x00, 0x41, 0x00, 0xA9, 0x00, 0xE9, 0x03, 0x01, 0x09, 0x41, 0xD8, 0x35, 0xDD, 0x0A}); private static final byte[] UTF8 = toByteArray(new int[]{0x41, 0xC2, 0xA9, 0xC3, 0xA9, 0xCC, 0x81, 0xE0, 0xA5, 0x81, 0xF0, 0x9D, 0x94, 0x8A}); @Test public void testCharacterEncoding() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext?charset=UTF-16BE"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); byte[] response = HttpClientUtils.readRawResponse(result); Assert.assertArrayEquals(UTF16, response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext?charset=UTF-8"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readRawResponse(result); Assert.assertArrayEquals(UTF8, response); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/charset/CharsetServlet.java000066400000000000000000000026601420065311100330240ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.charset; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class CharsetServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { String charset = req.getParameter("charset"); resp.setCharacterEncoding(charset); PrintWriter writer = resp.getWriter(); writer.write("\u0041\u00A9\u00E9\u0301\u0941\uD835\uDD0A"); writer.close(); } } DefaultCharacterEncodingServlet.java000066400000000000000000000047271420065311100362320ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/charset/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.charset; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; /** * @author Artemy Osipov */ public class DefaultCharacterEncodingServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { String requestCharacterEncoding = req.getCharacterEncoding(); String responseCharacterEncoding = resp.getCharacterEncoding(); PrintWriter writer = resp.getWriter(); writer.write(String.format("requestCharacterEncoding=%s;responseCharacterEncoding=%s;", requestCharacterEncoding, responseCharacterEncoding)); writer.close(); } @Override protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { final Reader reader = req.getReader(); final char[] buf = new char[1024]; final StringBuilder contentBuilder = new StringBuilder(); int numRead = -1; while ((numRead = reader.read(buf)) != -1) { contentBuilder.append(buf, 0, numRead); } final String requestCharacterEncoding = req.getCharacterEncoding(); final String responseCharacterEncoding = resp.getCharacterEncoding(); final PrintWriter writer = resp.getWriter(); writer.write(String.format("requestCharacterEncoding=%s;responseCharacterEncoding=%s;content=%s;", requestCharacterEncoding, responseCharacterEncoding, contentBuilder.toString())); writer.close(); } } DefaultCharacterEncodingTestCase.java000066400000000000000000000200551420065311100363110ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/charset/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.charset; import io.undertow.servlet.ServletExtension; import io.undertow.servlet.Servlets; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletContext; import javax.servlet.ServletException; import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author Artemy Osipov */ @RunWith(DefaultServer.class) public class DefaultCharacterEncodingTestCase { private void setup(final String defaultEncoding) throws ServletException { DeploymentUtils.setupServlet(new ServletExtension() { @Override public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servletContext) { if (defaultEncoding != null) { deploymentInfo.setDefaultEncoding(defaultEncoding); } } }, Servlets.servlet("servlet", DefaultCharacterEncodingServlet.class) .addMapping("/")); } private void testDefaultEncoding(String defaultCharacterEncoding, String expectedRequestCharacterEncoding, String expectedResponseCharacterEncoding) throws IOException, ServletException { setup(defaultCharacterEncoding); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("Unexpected request character encoding", expectedRequestCharacterEncoding, readParameter(response, "requestCharacterEncoding")); Assert.assertEquals("Unexpected response character encoding", expectedResponseCharacterEncoding, readParameter(response, "responseCharacterEncoding")); } finally { client.getConnectionManager().shutdown(); } } private void testServletContextCharacterEncoding(final String requestCharacterEncoding, final String responseCharacterEncoding, final String defaultContainerLevelEncoding, final String body) throws IOException, ServletException { DeploymentUtils.setupServlet(new ServletExtension() { @Override public void handleDeployment(final DeploymentInfo deploymentInfo, final ServletContext servletContext) { servletContext.setRequestCharacterEncoding(requestCharacterEncoding); servletContext.setResponseCharacterEncoding(responseCharacterEncoding); } }, Servlets.servlet("servlet", DefaultCharacterEncodingServlet.class).addMapping("/")); TestHttpClient client = new TestHttpClient(); try { final HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext"); if (body != null) { post.setEntity(new StringEntity(body, requestCharacterEncoding)); } // spec mandates "ISO-8859-1" as the default (see javadoc of ServletResponse#getCharacterEncoding()) final String expectedResponseCharEncoding = responseCharacterEncoding == null ? "ISO-8859-1" : responseCharacterEncoding; final HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result, Charset.forName(expectedResponseCharEncoding)); final String expectedRequestCharEncoding = requestCharacterEncoding == null ? "null" : requestCharacterEncoding; Assert.assertEquals("Unexpected request character encoding", expectedRequestCharEncoding, readParameter(response, "requestCharacterEncoding")); Assert.assertEquals("Unexpected response character encoding", expectedResponseCharEncoding, readParameter(response, "responseCharacterEncoding")); if (body != null) { Assert.assertEquals("Unexpected response body", body, readParameter(response, "content")); } } finally { client.getConnectionManager().shutdown(); } } private String readParameter(String response, String parameter) { Pattern pattern = Pattern.compile(parameter + "=(.*?);"); Matcher matcher = pattern.matcher(response); if (matcher.find()) { return matcher.group(1); } else { return null; } } @Test public void testDefaultEncodingNotSet() throws IOException, ServletException { testDefaultEncoding(null, "null", "ISO-8859-1"); } @Test public void testDefaultEncodingSetEqualDefault() throws IOException, ServletException { testDefaultEncoding("ISO-8859-1", "ISO-8859-1", "ISO-8859-1"); } @Test public void testDefaultEncodingSetNotEqualDefault() throws IOException, ServletException { testDefaultEncoding("UTF-8", "UTF-8", "UTF-8"); } /** * Tests that the character encoding set on the servlet context using {@link ServletContext#setRequestCharacterEncoding(String)} * and {@link ServletContext#setResponseCharacterEncoding(String)} is honoured at runtime during request/response processing * * @throws Exception */ @Test public void testServletContextCharEncoding() throws Exception { final String[] defaultContainerLevelEncodings = new String[]{null, StandardCharsets.ISO_8859_1.name(), StandardCharsets.UTF_8.name(), StandardCharsets.UTF_16BE.name()}; for (final String defaultContainerLevelEncoding : defaultContainerLevelEncodings) { testServletContextCharacterEncoding(null, null, defaultContainerLevelEncoding, null); testServletContextCharacterEncoding("UTF-8", null, defaultContainerLevelEncoding, null); testServletContextCharacterEncoding(null, "UTF-8", defaultContainerLevelEncoding, null); testServletContextCharacterEncoding(StandardCharsets.UTF_16BE.name(), "UTF-8", defaultContainerLevelEncoding, null); // send a unicode string in body testServletContextCharacterEncoding("UTF-8", "UTF-8", defaultContainerLevelEncoding, "\u3042"); } } } DefaultCharsetFormParserServlet.java000066400000000000000000000025261420065311100362540ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/charset/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.charset; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author Stuart Douglas */ public class DefaultCharsetFormParserServlet extends HttpServlet { @Override protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { String utf8Bytes = req.getParameter("\u0041\u00A9\u00E9\u0301\u0941\uD835\uDD0A"); resp.getOutputStream().write(utf8Bytes.getBytes("UTF-8")); } } DefaultCharsetServlet.java000066400000000000000000000030131420065311100342430ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/charset/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.charset; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * @author Stuart Douglas */ public class DefaultCharsetServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { PrintWriter writer = resp.getWriter(); if(req.getParameter("array") != null) { writer.write("abc\u0041\u00A9\u00E9\u0301\u0941\uD835\uDD0A".toCharArray(), 3, 7); } else { writer.write("abc\u0041\u00A9\u00E9\u0301\u0941\uD835\uDD0A", 3, 7); } writer.close(); } } DefaultCharsetTestCase.java000066400000000000000000000110141420065311100343320ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/charset/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.charset; import static io.undertow.servlet.Servlets.servlet; import java.io.IOException; import java.util.Collections; import javax.servlet.ServletContext; import javax.servlet.ServletException; import org.apache.http.HttpResponse; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.message.BasicNameValuePair; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.util.StatusCodes; import io.undertow.servlet.ServletExtension; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class DefaultCharsetTestCase { @BeforeClass public static void setup() throws ServletException { DeploymentUtils.setupServlet(new ServletExtension() { @Override public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servletContext) { deploymentInfo.setDefaultEncoding("UTF-8"); } }, servlet("servlet", DefaultCharsetServlet.class) .addMapping("/writer"), servlet("form", DefaultCharsetFormParserServlet.class) .addMapping("/form")); } public static byte[] toByteArray(int[] source) { byte[] ret = new byte[source.length]; for (int i = 0; i < source.length; ++i) { ret[i] = (byte) (0xff & source[i]); } return ret; } private static final byte[] UTF8 = toByteArray(new int[]{0x41, 0xC2, 0xA9, 0xC3, 0xA9, 0xCC, 0x81, 0xE0, 0xA5, 0x81, 0xF0, 0x9D, 0x94, 0x8A}); @Test public void testCharacterEncodingWriter() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/writer"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); byte[] response = HttpClientUtils.readRawResponse(result); Assert.assertArrayEquals(UTF8, response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/writer?array=true"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readRawResponse(result); Assert.assertArrayEquals(UTF8, response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testCharacterEncodingFormParser() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext/form"); post.setEntity(new UrlEncodedFormEntity(Collections.singletonList(new BasicNameValuePair("\u0041\u00A9\u00E9\u0301\u0941\uD835\uDD0A", "\u0041\u00A9\u00E9\u0301\u0941\uD835\uDD0A")), "UTF-8")); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); byte[] response = HttpClientUtils.readRawResponse(result); Assert.assertArrayEquals(UTF8, response); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/charset/EchoServlet.java000066400000000000000000000032661420065311100323140ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.charset; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Matej Lazar */ public class EchoServlet extends HttpServlet { @Override protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { String charset = req.getParameter("charset"); if (charset != null) { req.setCharacterEncoding(charset); resp.setCharacterEncoding(charset); } PrintWriter writer = resp.getWriter(); String message = req.getParameter("message"); if(message == null) { message = req.getServletPath().substring(1); } System.out.println("Received message: " + message); writer.write(message); writer.close(); } } ParameterCharacterEncodingTestCase.java000066400000000000000000000127441420065311100366530ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/charset/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.charset; import io.undertow.servlet.Servlets; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.mime.MultipartEntity; import org.apache.http.entity.mime.content.StringBody; import org.apache.http.message.BasicNameValuePair; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import static io.undertow.servlet.Servlets.multipartConfig; /** * @author Matej Lazar */ @RunWith(DefaultServer.class) public class ParameterCharacterEncodingTestCase { @BeforeClass public static void setup() throws ServletException { DeploymentUtils.setupServlet(Servlets.servlet("servlet", EchoServlet.class) .addMapping("/") .setMultipartConfig(multipartConfig(null, 0, 0, 0))); } @Test public void testUrlCharacterEncoding() throws IOException { TestHttpClient client = new TestHttpClient(); try { String message = "abc (\"čšž\")"; String charset = "UTF-8"; HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext?charset=" + charset + "&message=" + URLEncoder.encode(message, "UTF-8")); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals(message, response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testUrlPathEncodings() throws IOException { TestHttpClient client = new TestHttpClient(); try { String message = "abc(\"čšž\")"; String charset = "UTF-8"; HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/" + URLEncoder.encode(message, "UTF-8") + "?charset=" + charset); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals(message, response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testMultipartCharacterEncoding() throws IOException { TestHttpClient client = new TestHttpClient(); try { String message = "abcčšž"; String charset = "UTF-8"; HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext"); MultipartEntity multipart = new MultipartEntity(); multipart.addPart("charset", new StringBody(charset, Charset.forName(charset))); multipart.addPart("message", new StringBody(message, Charset.forName(charset))); post.setEntity(multipart); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals(message, response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testFormDataCharacterEncoding() throws IOException { TestHttpClient client = new TestHttpClient(); try { String message = "abcčšž"; String charset = "UTF-8"; HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext"); final List values = new ArrayList<>(); values.add(new BasicNameValuePair("charset", charset)); values.add(new BasicNameValuePair("message", message)); UrlEncodedFormEntity data = new UrlEncodedFormEntity(values, "UTF-8"); post.setEntity(data); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals(message, response); } finally { client.getConnectionManager().shutdown(); } } } UnmappableCharacterTestCase.java000066400000000000000000000043141420065311100353420ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/charset/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.charset; import io.undertow.servlet.Servlets; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import java.io.IOException; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class UnmappableCharacterTestCase { @BeforeClass public static void setup() throws ServletException { DeploymentUtils.setupServlet(Servlets.servlet("servlet", EchoServlet.class) .addMapping("/")); } @Test public void testUnmappableCharacters() throws IOException { TestHttpClient client = new TestHttpClient(); try { String message = "abcčšžgg"; HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext?message=" + message); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("abc???gg", response); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/compat/000077500000000000000000000000001420065311100270515ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/compat/rewrite/000077500000000000000000000000001420065311100305325ustar00rootroot00000000000000RewriteTestCase.java000066400000000000000000000073131420065311100343770ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/compat/rewrite/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.compat.rewrite; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.servlet.ServletExtension; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.compat.rewrite.RewriteConfig; import io.undertow.servlet.compat.rewrite.RewriteConfigFactory; import io.undertow.servlet.compat.rewrite.RewriteHandler; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.servlet.test.util.PathTestServlet; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletContext; import javax.servlet.ServletException; import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class RewriteTestCase { @BeforeClass public static void setup() throws ServletException { DeploymentUtils.setupServlet(new ServletExtension() { @Override public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servletContext) { deploymentInfo.addOuterHandlerChainWrapper(new HandlerWrapper() { @Override public HttpHandler wrap(HttpHandler handler) { byte[] data = "RewriteRule /foo1 /bar1".getBytes(StandardCharsets.UTF_8); RewriteConfig config = RewriteConfigFactory.build(new ByteArrayInputStream(data)); return new RewriteHandler(config, handler); } }); } }, new ServletInfo("fooServlet", PathTestServlet.class).addMapping("/bar1") ); } @Test public void testRewrite() throws Exception { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/foo1"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("pathInfo:null queryString:null servletPath:/bar1 requestUri:/servletContext/bar1", response); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/constant/000077500000000000000000000000001420065311100274175ustar00rootroot00000000000000GenericServletConstants.java000066400000000000000000000023331420065311100350220ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/constant/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.constant; /** * @author Moulali Shikalwadi */ public class GenericServletConstants { public static final String LOCAL_ADDR = "localAddr"; public static final String LOCAL_NAME = "localName"; public static final String LOCAL_PORT = "localPort"; public static final String SERVER_NAME = "serverName"; public static final String SERVER_PORT = "serverPort"; public static final String REMOTE_ADDR = "remoteAddr"; public static final String REMOTE_PORT = "remotePort"; } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/crosscontext/000077500000000000000000000000001420065311100303245ustar00rootroot00000000000000CrossContextClassLoaderTestCase.java000066400000000000000000000127241420065311100373250ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/crosscontext/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.crosscontext; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class CrossContextClassLoaderTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("includer", IncludeServlet.class) .addMapping("/a"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(new TempClassLoader("IncluderClassLoader")) .setContextPath("/includer") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("includer.war") .addServlet(s); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); s = new ServletInfo("included", IncludedServlet.class) .addMapping("/a"); builder = new DeploymentInfo() .setClassLoader(new TempClassLoader("IncludedClassLoader")) .setContextPath("/included") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("included.war") .addServlet(s); manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testCrossContextRequest() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/includer/a"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals( "Including Servlet Class Loader: IncluderClassLoader\r\n" + "Including Servlet Context Path: /includer\r\n" + "Included Servlet Class Loader: IncludedClassLoader\r\n" + "Including Servlet Context Path: /included\r\n", response); } finally { client.getConnectionManager().shutdown(); } } private static final class IncludeServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().println("Including Servlet Class Loader: " + Thread.currentThread().getContextClassLoader().toString()); resp.getWriter().println("Including Servlet Context Path: " + req.getServletContext().getContextPath()); ServletContext context = req.getServletContext().getContext("/included"); context.getRequestDispatcher("/a").include(req, resp); } } private static final class IncludedServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().println("Included Servlet Class Loader: " + Thread.currentThread().getContextClassLoader().toString()); resp.getWriter().println("Including Servlet Context Path: " + req.getServletContext().getContextPath()); } } private static final class TempClassLoader extends ClassLoader { private final String name; private TempClassLoader(String name) { super(TempClassLoader.class.getClassLoader()); this.name = name; } @Override public String toString() { return name; } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/000077500000000000000000000000001420065311100306175ustar00rootroot00000000000000DefaultServletCachingListenerTestCase.java000066400000000000000000000313451420065311100407610ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.defaultservlet; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.concurrent.TimeUnit; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.server.handlers.cache.DirectBufferCache; import io.undertow.server.handlers.resource.CachingResourceManager; import io.undertow.server.handlers.resource.PathResourceManager; import io.undertow.server.session.SecureRandomSessionIdGenerator; import io.undertow.servlet.Servlets; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.path.ServletPathMappingTestCase; import io.undertow.servlet.test.util.PathTestServlet; import io.undertow.servlet.test.util.MessageFilter; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.FileUtils; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.BufferAllocator; /** *

    Same test case than DefaultServletCachingTestCase but enabling the * resource change listeners to detect changes in the file system.

    * * @author rmartinc */ @RunWith(DefaultServer.class) public class DefaultServletCachingListenerTestCase { private static final int MAX_FILE_SIZE = 20; private static final int MAX_WAIT_TIME = 300000; private static final int WAIT_TIME = 10000; public static final String DIR_NAME = "cacheTest"; private static Path tmpDir; private static final DirectBufferCache dataCache = new DirectBufferCache(1000, 10, 1000 * 10 * 1000, BufferAllocator.DIRECT_BYTE_BUFFER_ALLOCATOR, -1); @Before public void before() { for(Object k : dataCache.getAllKeys()) { dataCache.remove(k); } } @BeforeClass public static void setup() throws ServletException, IOException { tmpDir = Files.createTempDirectory(DIR_NAME); // assume tmp is in the default file system and watch-service is not the slow polling impl Assume.assumeTrue("WatchService is going to work OK", FileSystems.getDefault().equals(tmpDir.getFileSystem()) && !FileSystems.getDefault().newWatchService().getClass().getName().equals("sun.nio.fs.PollingWatchService")); final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setClassLoader(ServletPathMappingTestCase.class.getClassLoader()) .setContextPath("/servletContext") .addWelcomePage("index.html") .setDeploymentName("servletContext.war") // PathResourceManager enables the resource change listeners in this test and max-age is infinite/-1 .setResourceManager(new CachingResourceManager(100, MAX_FILE_SIZE, dataCache, new PathResourceManager(tmpDir, 10485760, false, false, true), -1)); builder.addServlet(new ServletInfo("DefaultTestServlet", PathTestServlet.class) .addMapping("/path/default")) .addFilter(Servlets.filter("message", MessageFilter.class).addInitParam(MessageFilter.MESSAGE, "FILTER_TEXT ")) .addFilterUrlMapping("message", "*.txt", DispatcherType.REQUEST); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @AfterClass public static void after() throws IOException{ FileUtils.deleteRecursive(tmpDir); } private static boolean waitUntilRefreshed(TestHttpClient client, String uri, int expectedStatus) throws IOException, InterruptedException { return waitUntilRefreshed(client, uri, expectedStatus, null); } private static boolean waitUntilRefreshed(TestHttpClient client, String uri, int expectedStatus, String expectedResponse) throws IOException, InterruptedException { boolean ok = false; long startTime = System.currentTimeMillis(); while (!ok && System.currentTimeMillis() - startTime < MAX_WAIT_TIME) { HttpGet get = new HttpGet(uri); HttpResponse result = client.execute(get); String response = HttpClientUtils.readResponse(result); if (result.getStatusLine().getStatusCode() == expectedStatus && (expectedResponse == null || expectedResponse.equals(response))) { ok = true; } else { TimeUnit.MILLISECONDS.sleep(WAIT_TIME); } } return ok; } @Test public void testFileExistanceCheckCached() throws IOException, InterruptedException { TestHttpClient client = new TestHttpClient(); String fileName = new SecureRandomSessionIdGenerator().createSessionId() + ".html"; try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.NOT_FOUND, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); Path f = tmpDir.resolve(fileName); Files.write(f, "hello".getBytes()); Assert.assertTrue("File was not refreshed in " + MAX_WAIT_TIME + "ms", waitUntilRefreshed(client, DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName, StatusCodes.OK, "hello")); Files.delete(f); } finally { client.getConnectionManager().shutdown(); } } @Test public void testFileContentsCached() throws IOException, InterruptedException { TestHttpClient client = new TestHttpClient(); String fileName = "hello.html"; Path f = tmpDir.resolve(fileName); Files.write(f, "hello".getBytes()); try { for (int i = 0; i < 10; ++i) { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("hello", response); } Files.write(f, "hello world".getBytes()); Assert.assertTrue("File was not refreshed in " + MAX_WAIT_TIME + "ms", waitUntilRefreshed(client, DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName, StatusCodes.OK, "hello world")); Files.delete(f); } finally { client.getConnectionManager().shutdown(); } } @Test public void testFileContentsCachedWithFilter() throws IOException, InterruptedException { TestHttpClient client = new TestHttpClient(); String fileName = "hello.txt"; Path f = tmpDir.resolve(fileName); Files.write(f, "hello".getBytes()); try { for (int i = 0; i < 10; ++i) { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("FILTER_TEXT hello", response); } Files.write(f, "hello world".getBytes()); Assert.assertTrue("File was not refreshed in " + MAX_WAIT_TIME + "ms", waitUntilRefreshed(client, DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName, StatusCodes.OK, "FILTER_TEXT hello world")); Files.delete(f); } finally { client.getConnectionManager().shutdown(); } } @Test public void testRangeRequest() throws IOException { TestHttpClient client = new TestHttpClient(); try { String fileName = "range.html"; Path f = tmpDir.resolve(fileName); Files.write(f, "hello".getBytes()); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/range.html"); get.addHeader("range", "bytes=2-3"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("ll", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testRangeRequestFileNotInCache() throws IOException { TestHttpClient client = new TestHttpClient(); try { String fileName = "range_not_in_cache.html"; Path f = tmpDir.resolve(fileName); Files.write(f, "hello world and once again hello world".getBytes()); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/range_not_in_cache.html"); get.addHeader("range", "bytes=2-3"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("ll", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testWelcomePages() throws IOException, InterruptedException { TestHttpClient client = new TestHttpClient(); try { String fileName = "index.html"; String content = ""; HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.FORBIDDEN, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName); result = client.execute(get); Assert.assertEquals(StatusCodes.NOT_FOUND, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); Path f = tmpDir.resolve(fileName); Files.write(f, content.getBytes()); Assert.assertTrue("File was not refreshed in " + MAX_WAIT_TIME + "ms", waitUntilRefreshed(client, DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName, StatusCodes.OK, content)); Assert.assertTrue("File was not refreshed in " + MAX_WAIT_TIME + "ms", waitUntilRefreshed(client, DefaultServer.getDefaultServerURL() + "/servletContext/", StatusCodes.OK, content)); Files.delete(f); Assert.assertTrue("File was not refreshed in " + MAX_WAIT_TIME + "ms", waitUntilRefreshed(client, DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName, StatusCodes.NOT_FOUND)); Assert.assertTrue("File was not refreshed in " + MAX_WAIT_TIME + "ms", waitUntilRefreshed(client, DefaultServer.getDefaultServerURL() + "/servletContext/", StatusCodes.FORBIDDEN)); } finally { client.getConnectionManager().shutdown(); } } } DefaultServletCachingTestCase.java000066400000000000000000000340521420065311100372510ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.defaultservlet; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.server.handlers.cache.DirectBufferCache; import io.undertow.server.handlers.resource.CachingResourceManager; import io.undertow.server.handlers.resource.PathResourceManager; import io.undertow.server.session.SecureRandomSessionIdGenerator; import io.undertow.servlet.Servlets; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.path.ServletPathMappingTestCase; import io.undertow.servlet.test.util.PathTestServlet; import io.undertow.servlet.test.util.MessageFilter; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.FileUtils; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.BufferAllocator; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class DefaultServletCachingTestCase { private static final int MAX_FILE_SIZE = 20; private static final int METADATA_MAX_AGE = 2000; public static final String DIR_NAME = "cacheTest"; static Path tmpDir; static DirectBufferCache dataCache = new DirectBufferCache(1000, 10, 1000 * 10 * 1000, BufferAllocator.DIRECT_BYTE_BUFFER_ALLOCATOR, METADATA_MAX_AGE); @Before public void before() { for(Object k : dataCache.getAllKeys()) { dataCache.remove(k); } } @BeforeClass public static void setup() throws ServletException, IOException { tmpDir = Files.createTempDirectory(DIR_NAME); final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setClassLoader(ServletPathMappingTestCase.class.getClassLoader()) .setContextPath("/servletContext") .addWelcomePage("index.html") .setDeploymentName("servletContext.war") .setResourceManager(new CachingResourceManager(100, MAX_FILE_SIZE, dataCache, new PathResourceManager(tmpDir, 10485760, false, false, false), METADATA_MAX_AGE)); builder.addServlet(new ServletInfo("DefaultTestServlet", PathTestServlet.class) .addMapping("/path/default")) .addFilter(Servlets.filter("message", MessageFilter.class).addInitParam(MessageFilter.MESSAGE, "FILTER_TEXT ")) .addFilterUrlMapping("message", "*.txt", DispatcherType.REQUEST); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @AfterClass public static void after() throws IOException{ FileUtils.deleteRecursive(tmpDir); } @Test public void testFileExistanceCheckCached() throws IOException, InterruptedException { TestHttpClient client = new TestHttpClient(); String fileName = new SecureRandomSessionIdGenerator().createSessionId() + ".html"; try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.NOT_FOUND, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); Path f = tmpDir.resolve(fileName); Files.write(f, "hello".getBytes()); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName); result = client.execute(get); Assert.assertEquals(StatusCodes.NOT_FOUND, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); Thread.sleep(METADATA_MAX_AGE); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("hello", response); Files.delete(f); } finally { client.getConnectionManager().shutdown(); } } @Test public void testFileContentsCached() throws IOException, InterruptedException { TestHttpClient client = new TestHttpClient(); String fileName = "hello.html"; Path f = tmpDir.resolve(fileName); Files.write(f, "hello".getBytes()); try { for (int i = 0; i < 10; ++i) { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("hello", response); } Files.write(f, "hello world".getBytes()); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("hello", response); Thread.sleep(METADATA_MAX_AGE); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("hello world", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testFileContentsCachedWithFilter() throws IOException, InterruptedException { TestHttpClient client = new TestHttpClient(); String fileName = "hello.txt"; Path f = tmpDir.resolve(fileName); Files.write(f, "hello".getBytes()); try { for (int i = 0; i < 10; ++i) { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("FILTER_TEXT hello", response); } Files.write(f, "hello world".getBytes()); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("FILTER_TEXT hello", response); Thread.sleep(METADATA_MAX_AGE); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("FILTER_TEXT hello world", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testRangeRequest() throws IOException { TestHttpClient client = new TestHttpClient(); try { String fileName = "range.html"; Path f = tmpDir.resolve(fileName); Files.write(f, "hello".getBytes()); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/range.html"); get.addHeader("range", "bytes=2-3"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("ll", response); } finally { client.getConnectionManager().shutdown(); } } /** * Regression test for UNDERTOW-1444. * * Tested file is bigger then {@value #MAX_FILE_SIZE} bytes. */ @Test public void testRangeRequestFileNotInCache() throws IOException { TestHttpClient client = new TestHttpClient(); try { String fileName = "range_not_in_cache.html"; Path f = tmpDir.resolve(fileName); Files.write(f, "hello world and once again hello world".getBytes()); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/range_not_in_cache.html"); get.addHeader("range", "bytes=2-3"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("ll", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testWelcomePages() throws IOException, InterruptedException { TestHttpClient client = new TestHttpClient(); try { String fileName = "index.html"; String content = ""; HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.FORBIDDEN, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName); result = client.execute(get); Assert.assertEquals(StatusCodes.NOT_FOUND, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); Path f = tmpDir.resolve(fileName); Files.write(f, content.getBytes()); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/"); result = client.execute(get); Assert.assertEquals(StatusCodes.FORBIDDEN, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName); result = client.execute(get); Assert.assertEquals(StatusCodes.NOT_FOUND, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); Thread.sleep(METADATA_MAX_AGE); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(content, HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(content, HttpClientUtils.readResponse(result)); Files.delete(f); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(content, HttpClientUtils.readResponse(result)); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(content, HttpClientUtils.readResponse(result)); Thread.sleep(METADATA_MAX_AGE); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/"); result = client.execute(get); Assert.assertEquals(StatusCodes.FORBIDDEN, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/" + fileName); result = client.execute(get); Assert.assertEquals(StatusCodes.NOT_FOUND, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } } DefaultServletTestCase.java000066400000000000000000000353141420065311100357760ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.defaultservlet; import java.io.IOException; import java.util.Date; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.handlers.DefaultServlet; import io.undertow.servlet.test.path.ServletPathMappingTestCase; import io.undertow.servlet.test.util.PathTestServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestResourceLoader; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.DateUtils; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.util.EntityUtils; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class DefaultServletTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setClassLoader(ServletPathMappingTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setDeploymentName("servletContext.war") .setResourceManager(new TestResourceLoader(DefaultServletTestCase.class)); builder.addServlet(new ServletInfo("DefaultTestServlet", PathTestServlet.class) .addMapping("/path/default")); builder.addServlet(new ServletInfo("default", DefaultServlet.class) .addInitParam("directory-listing", "true") .addMapping("/*")); //see UNDERTOW-458 builder.addFilter(new FilterInfo("date-header", GetDateFilter.class)); builder.addFilterUrlMapping("date-header", "/*", DispatcherType.REQUEST); builder.addFilter(new FilterInfo("Filter", HelloFilter.class)); builder.addFilterUrlMapping("Filter", "/filterpath/*", DispatcherType.REQUEST); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testSimpleResource() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/index.html"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.contains("Redirected home page")); } finally { client.getConnectionManager().shutdown(); } } @Test public void testRangeRequest() throws IOException, InterruptedException { String uri = DefaultServer.getDefaultServerURL() + "/servletContext/range.txt"; TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/index.html"); get.addHeader(Headers.RANGE_STRING, "bytes=2-3"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("--", response); get = new HttpGet(uri); get.addHeader(Headers.RANGE_STRING, "bytes=3-100"); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("3456789", response); Assert.assertEquals("bytes 3-9/10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(uri); get.addHeader(Headers.RANGE_STRING, "bytes=3-9"); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("3456789", response); Assert.assertEquals("bytes 3-9/10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(uri); get.addHeader(Headers.RANGE_STRING, "bytes=2-3"); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("23", response); Assert.assertEquals( "bytes 2-3/10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(uri); get.addHeader(Headers.RANGE_STRING, "bytes=0-0"); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("0", response); Assert.assertEquals( "bytes 0-0/10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(uri); get.addHeader(Headers.RANGE_STRING, "bytes=1-"); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("123456789", response); Assert.assertEquals( "bytes 1-9/10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(uri); get.addHeader(Headers.RANGE_STRING, "bytes=0-"); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("0123456789", response); Assert.assertEquals("bytes 0-9/10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(uri); get.addHeader(Headers.RANGE_STRING, "bytes=9-"); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("9", response); Assert.assertEquals("bytes 9-9/10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(uri); get.addHeader(Headers.RANGE_STRING, "bytes=-1"); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("9", response); Assert.assertEquals("bytes 9-9/10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(uri); get.addHeader(Headers.RANGE_STRING, "bytes=-100"); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("0123456789", response); Assert.assertEquals("bytes 0-9/10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(uri); get.addHeader(Headers.RANGE_STRING, "bytes=99-100"); result = client.execute(get); Assert.assertEquals(StatusCodes.REQUEST_RANGE_NOT_SATISFIABLE, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("", response); Assert.assertEquals("bytes */10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(uri); get.addHeader(Headers.RANGE_STRING, "bytes=2-1"); result = client.execute(get); Assert.assertEquals(StatusCodes.REQUEST_RANGE_NOT_SATISFIABLE, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("", response); Assert.assertEquals("bytes */10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); //test if-range get = new HttpGet(uri); get.addHeader(Headers.RANGE_STRING, "bytes=2-3"); get.addHeader(Headers.IF_RANGE_STRING, DateUtils.toDateString(new Date(System.currentTimeMillis() + 1000))); result = client.execute(get); Assert.assertEquals(StatusCodes.PARTIAL_CONTENT, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("23", response); Assert.assertEquals( "bytes 2-3/10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue()); get = new HttpGet(uri); get.addHeader(Headers.RANGE_STRING, "bytes=2-3"); get.addHeader(Headers.IF_RANGE_STRING, DateUtils.toDateString(new Date(0))); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = EntityUtils.toString(result.getEntity()); Assert.assertEquals("0123456789", response); Assert.assertNull(result.getFirstHeader(Headers.CONTENT_RANGE_STRING)); } finally { client.getConnectionManager().shutdown(); } } @Test public void testResourceWithFilter() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/filterpath/filtered.txt"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("Hello Stuart", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testDisallowedResource() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/disallowed.sh"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } @Test public void testNoAccessToMetaInfResource() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/meta-inf/secret"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.NOT_FOUND, result.getStatusLine().getStatusCode()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testDirectoryListing() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/path"); HttpResponse result = client.execute(get); Header contentType = result.getFirstHeader(Headers.CONTENT_TYPE_STRING); Assert.assertNotNull(contentType); Assert.assertTrue(contentType.getValue().contains("text/html")); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } @Test public void testIfMoodifiedSince() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/index.html"); //UNDERTOW-458 get.addHeader("date-header", "Fri, 10 Oct 2014 21:35:55 CEST"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.contains("Redirected home page")); String lm = result.getHeaders("Last-Modified")[0].getValue(); System.out.println(lm); Assert.assertTrue(lm.endsWith("GMT")); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/index.html"); get.addHeader("IF-Modified-Since", lm); result = client.execute(get); Assert.assertEquals(StatusCodes.NOT_MODIFIED, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertFalse(response.contains("Redirected home page")); } finally { client.getConnectionManager().shutdown(); } } } GetDateFilter.java000066400000000000000000000030111420065311100340610ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.defaultservlet; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * @author Stuart Douglas */ public class GetDateFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ((HttpServletRequest)request).getDateHeader("date-header"); chain.doFilter(request, response); } @Override public void destroy() { } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/HelloFilter.java000066400000000000000000000027321420065311100336770ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.defaultservlet; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; /** * @author Stuart Douglas */ public class HelloFilter implements Filter { @Override public void init(final FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { response.getWriter().write("Hello "); chain.doFilter(request, response); } @Override public void destroy() { } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/NoOpFilter.java000066400000000000000000000026521420065311100335100ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.defaultservlet; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; /** * @author Stuart Douglas */ public class NoOpFilter implements Filter{ @Override public void init(final FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { chain.doFilter(request, response); } @Override public void destroy() { } } SecurityRedirectTestCase.java000066400000000000000000000173641420065311100363430ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.defaultservlet; import static io.undertow.util.Headers.AUTHORIZATION; import static io.undertow.util.Headers.BASIC; import static io.undertow.util.Headers.LOCATION; import static io.undertow.util.Headers.WWW_AUTHENTICATE; import static org.junit.Assert.assertEquals; import java.io.IOException; import javax.servlet.ServletException; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.HttpClientBuilder; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.WebResourceCollection; import io.undertow.servlet.test.path.ServletPathMappingTestCase; import io.undertow.servlet.test.security.constraint.ServletIdentityManager; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestResourceLoader; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.FlexBase64; import io.undertow.util.StatusCodes; /** * TestCase on redirect with or without trailing slash when requesting protected path. * * @author Lin Gao */ @RunWith(DefaultServer.class) public class SecurityRedirectTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletIdentityManager identityManager = new ServletIdentityManager(); identityManager.addUser("user1", "password1", "role1"); DeploymentInfo builder = new DeploymentInfo() .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setClassLoader(ServletPathMappingTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setDeploymentName("servletContext.war") .setResourceManager(new TestResourceLoader(SecurityRedirectTestCase.class)) .addWelcomePages("index.html") .setIdentityManager(identityManager) .setLoginConfig(new LoginConfig("BASIC", "Test Realm")) .addSecurityConstraint(new SecurityConstraint() .addRoleAllowed("role1") .addWebResourceCollection(new WebResourceCollection() .addUrlPatterns("/index.html", "/filterpath/*"))); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @SuppressWarnings("deprecation") @Test public void testSecurityWithWelcomeFileRedirect() throws IOException { // disable following redirect HttpClient client = HttpClientBuilder.create().disableRedirectHandling().build(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.FOUND, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(LOCATION.toString()); assertEquals(1, values.length); assertEquals(DefaultServer.getDefaultServerURL() + "/servletContext/", values[0].getValue()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/"); result = client.execute(get); Assert.assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); values = result.getHeaders(WWW_AUTHENTICATE.toString()); assertEquals(1, values.length); assertEquals(BASIC + " realm=\"Test Realm\"", values[0].getValue()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/"); get.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString("user1:password1".getBytes(), false)); result = client.execute(get); String response = HttpClientUtils.readResponse(result); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertTrue(response.contains("Redirected home page")); } finally { client.getConnectionManager().shutdown(); } } @SuppressWarnings("deprecation") @Test public void testSecurityWithoutWelcomeFileRedirect() throws IOException { // disable following redirect HttpClient client = HttpClientBuilder.create().disableRedirectHandling().build(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/filterpath"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/filterpath/"); result = client.execute(get); Assert.assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); assertEquals(1, values.length); assertEquals(BASIC + " realm=\"Test Realm\"", values[0].getValue()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/filterpath"); get.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString("user1:password1".getBytes(), false)); result = client.execute(get); Assert.assertEquals(StatusCodes.FOUND, result.getStatusLine().getStatusCode()); values = result.getHeaders(LOCATION.toString()); assertEquals(1, values.length); assertEquals(DefaultServer.getDefaultServerURL() + "/servletContext/filterpath/", values[0].getValue()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/filterpath/filtered.txt"); get.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString("user1:password1".getBytes(), false)); result = client.execute(get); String response = HttpClientUtils.readResponse(result); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertTrue(response.equals("Stuart")); } finally { client.getConnectionManager().shutdown(); } } } ServletAndResourceWelcomeFileTestCase.java000066400000000000000000000067071420065311100407440ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.defaultservlet; import java.io.IOException; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.path.ServletPathMappingTestCase; import io.undertow.servlet.test.util.PathTestServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestResourceLoader; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ServletAndResourceWelcomeFileTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setClassLoader(ServletPathMappingTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setDeploymentName("servletContext.war") .setResourceManager(new TestResourceLoader(ServletAndResourceWelcomeFileTestCase.class)) .addWelcomePages("doesnotexist.html", "index.html", "default"); builder.addServlet(new ServletInfo("DefaultTestServlet", PathTestServlet.class) .addMapping("*.html")); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testWelcomeFileRedirect() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("pathInfo:null queryString:null servletPath:/index.html requestUri:/servletContext/", response); } finally { client.getConnectionManager().shutdown(); } } } WelcomeFileSecurityTestCase.java000066400000000000000000000150471420065311100367710ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.defaultservlet; import java.io.IOException; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.ServletSecurityInfo; import io.undertow.servlet.api.WebResourceCollection; import io.undertow.servlet.test.path.ServletPathMappingTestCase; import io.undertow.servlet.test.security.constraint.ServletIdentityManager; import io.undertow.servlet.test.util.PathTestServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestResourceLoader; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.FlexBase64; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import static io.undertow.util.Headers.AUTHORIZATION; import static io.undertow.util.Headers.BASIC; import static io.undertow.util.Headers.WWW_AUTHENTICATE; import static org.junit.Assert.assertEquals; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class WelcomeFileSecurityTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletIdentityManager identityManager = new ServletIdentityManager(); identityManager.addUser("user1", "password1", "role1"); DeploymentInfo builder = new DeploymentInfo() .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setClassLoader(ServletPathMappingTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setDeploymentName("servletContext.war") .setResourceManager(new TestResourceLoader(WelcomeFileSecurityTestCase.class)) .addWelcomePages("doesnotexist.html", "index.html", "default") .setIdentityManager(identityManager) .setLoginConfig(new LoginConfig("BASIC", "Test Realm")) .addServlet( new ServletInfo("DefaultTestServlet", PathTestServlet.class) .setServletSecurityInfo( new ServletSecurityInfo() .addRoleAllowed("role1")) .addMapping("/path/default")) .addSecurityConstraint(new SecurityConstraint() .addRoleAllowed("role1") .addWebResourceCollection(new WebResourceCollection() .addUrlPattern("/index.html"))); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testWelcomeFileRedirect() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); assertEquals(1, values.length); assertEquals(BASIC + " realm=\"Test Realm\"", values[0].getValue()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/"); get.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString("user1:password1".getBytes(), false)); result = client.execute(get); String response = HttpClientUtils.readResponse(result); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertTrue(response.contains("Redirected home page")); } finally { client.getConnectionManager().shutdown(); } } @Test public void testWelcomeServletRedirect() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/path?a=b"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); assertEquals(1, values.length); assertEquals(BASIC + " realm=\"Test Realm\"", values[0].getValue()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/path?a=b"); get.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString("user1:password1".getBytes(), false)); result = client.execute(get); String response = HttpClientUtils.readResponse(result); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("pathInfo:null queryString:a=b servletPath:/path/default requestUri:/servletContext/path/", response); } finally { client.getConnectionManager().shutdown(); } } } WelcomeFileTestCase.java000066400000000000000000000156721420065311100352450ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.defaultservlet; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.path.ServletPathMappingTestCase; import io.undertow.servlet.test.util.PathTestServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestResourceLoader; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import java.io.IOException; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class WelcomeFileTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setClassLoader(ServletPathMappingTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setDeploymentName("servletContext.war") .setResourceManager(new TestResourceLoader(WelcomeFileTestCase.class)) .addWelcomePages("doesnotexist.html", "index.html", "default", "servletPath/servletFile.xhtml") .addServlet(new ServletInfo("DefaultTestServlet", PathTestServlet.class) .addMapping("/path/default")) .addServlet(new ServletInfo("ServletPath", PathTestServlet.class) .addMapping("/foo/servletPath/*")) .addFilter(new FilterInfo("Filter", NoOpFilter.class)) .addFilterUrlMapping("Filter", "/*", DispatcherType.REQUEST) .addFilterUrlMapping("Filter", "/", DispatcherType.REQUEST); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); builder = new DeploymentInfo() .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setClassLoader(ServletPathMappingTestCase.class.getClassLoader()) .setContextPath("/servletContext2") .setDeploymentName("servletContext2.war") .setResourceManager(new TestResourceLoader(WelcomeFileTestCase.class)) .addWelcomePages("doesnotexist.html", "index.do") .addServlet(new ServletInfo("*.do", PathTestServlet.class) .addMapping("*.do")); manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testWelcomeFileRedirect() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.contains("Redirected home page")); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.contains("Redirected home page")); } finally { client.getConnectionManager().shutdown(); } } @Test public void testWelcomeFileExtensionBasedMapping() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext2"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("pathInfo:null queryString:null servletPath:/index.do requestUri:/servletContext2/", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testWelcomeServletRedirect() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/path?a=b"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("pathInfo:null queryString:a=b servletPath:/path/default requestUri:/servletContext/path/", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testWelcomeFileStarMappedPathRedirect() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/foo/?a=b"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("pathInfo:/servletFile.xhtml queryString:a=b servletPath:/foo/servletPath requestUri:/servletContext/foo/", response); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/disallowed.sh000066400000000000000000000001261420065311100333010ustar00rootroot00000000000000This is not a real shell script, but the default servlet will not serve it by default.undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/filterpath/000077500000000000000000000000001420065311100327615ustar00rootroot00000000000000filtered.txt000066400000000000000000000000061420065311100352350ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/filterpathStuartundertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/foo/000077500000000000000000000000001420065311100314025ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/foo/.gitkeep000066400000000000000000000000001420065311100330210ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/index.html000066400000000000000000000014761420065311100326240ustar00rootroot00000000000000 a page Redirected home page undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/meta-inf/000077500000000000000000000000001420065311100323175ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/meta-inf/secret000066400000000000000000000000151420065311100335230ustar00rootroot00000000000000confidential undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/path/000077500000000000000000000000001420065311100315535ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/path/.gitkeep000066400000000000000000000000001420065311100331720ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/range.txt000066400000000000000000000000121420065311100324450ustar00rootroot000000000000000123456789undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/dispatcher/000077500000000000000000000000001420065311100277145ustar00rootroot00000000000000DispatcherForwardTestCase.java000066400000000000000000000272731420065311100355620ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/dispatcher/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.dispatcher; import io.undertow.server.handlers.PathHandler; import io.undertow.server.handlers.accesslog.AccessLogFileTestCase; import io.undertow.server.handlers.accesslog.AccessLogHandler; import io.undertow.server.handlers.accesslog.AccessLogReceiver; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.util.MessageFilter; import io.undertow.servlet.test.util.MessageServlet; import io.undertow.servlet.test.util.PathTestServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestResourceLoader; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Protocols; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.junit.Assert; import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import java.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class DispatcherForwardTestCase { private static volatile String message; private static volatile CountDownLatch latch = new CountDownLatch(1); private static final AccessLogReceiver RECEIVER = new AccessLogReceiver() { @Override public void logMessage(final String msg) { message = msg; latch.countDown(); } }; @BeforeClass public static void setup() throws ServletException { //we don't run this test on h2 upgrade, as if it is run with the original request //the protocols will not match Assume.assumeFalse(DefaultServer.isH2upgrade()); final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setResourceManager(new TestResourceLoader(DispatcherForwardTestCase.class)) .addServlet( new ServletInfo("forward", MessageServlet.class) .addInitParam(MessageServlet.MESSAGE, "forwarded") .addMapping("/forward")) .addServlet( new ServletInfo("dispatcher", ForwardServlet.class) .addMapping("/dispatch")) .addServlet( new ServletInfo("pathTest", PathTestServlet.class) .addMapping("/path")) .addFilter( new FilterInfo("notforwarded", MessageFilter.class) .addInitParam(MessageFilter.MESSAGE, "Not forwarded")) .addFilter( new FilterInfo("inc", MessageFilter.class) .addInitParam(MessageFilter.MESSAGE, "Path!")) .addFilter( new FilterInfo("nameFilter", MessageFilter.class) .addInitParam(MessageFilter.MESSAGE, "Name!")) .addFilterUrlMapping("notforwarded", "/forward", DispatcherType.REQUEST) .addFilterUrlMapping("inc", "/forward", DispatcherType.FORWARD) .addFilterServletNameMapping("nameFilter", "forward", DispatcherType.FORWARD); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(new AccessLogHandler(root, RECEIVER, "%r %U %R", AccessLogFileTestCase.class.getClassLoader())); } @Test public void testPathBasedInclude() throws IOException, InterruptedException { resetLatch(); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/dispatch"); get.setHeader("forward", "/forward"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("Path!Name!forwarded", response); latch.await(30, TimeUnit.SECONDS); //UNDERTOW-327 make sure that the access log includes the original path String protocol = DefaultServer.isH2() ? Protocols.HTTP_2_0_STRING : Protocols.HTTP_1_1_STRING; Assert.assertEquals("GET /servletContext/dispatch " + protocol + " /servletContext/dispatch /dispatch", message); } finally { client.getConnectionManager().shutdown(); } } private void resetLatch() { latch.countDown(); latch = new CountDownLatch(1); } @Test public void testNameBasedInclude() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/dispatch"); get.setHeader("forward", "forward"); get.setHeader("name", "true"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("Name!forwarded", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testNameBasedForwardOutServletContext() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/dispatch"); get.setHeader("forward", "../forward"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.INTERNAL_SERVER_ERROR, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); MatcherAssert.assertThat(response, CoreMatchers.containsString("dispatcher was null!")); } finally { client.getConnectionManager().shutdown(); } } @Test public void testPathBasedStaticInclude() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/dispatch"); get.setHeader("forward", "/snippet.html"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("SnippetText", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testPathBasedStaticIncludePost() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext/dispatch"); post.setHeader("forward", "/snippet.html"); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("SnippetText", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testIncludeAggregatesQueryString() throws IOException, InterruptedException { TestHttpClient client = new TestHttpClient(); String protocol = DefaultServer.isH2() ? Protocols.HTTP_2_0_STRING : Protocols.HTTP_1_1_STRING; try { resetLatch(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/dispatch?a=b"); get.setHeader("forward", "/path"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("pathInfo:null queryString:a=b servletPath:/path requestUri:/servletContext/path", response); latch.await(30, TimeUnit.SECONDS); //UNDERTOW-327 and UNDERTOW-1599 - make sure that the access log includes the original path and query string Assert.assertEquals("GET /servletContext/dispatch?a=b " + protocol + " /servletContext/dispatch /dispatch", message); resetLatch(); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/dispatch?a=b"); get.setHeader("forward", "/path?foo=bar"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("pathInfo:null queryString:foo=bar servletPath:/path requestUri:/servletContext/path", response); latch.await(30, TimeUnit.SECONDS); //UNDERTOW-327 and UNDERTOW-1599 - make sure that the access log includes the original path and query string Assert.assertEquals("GET /servletContext/dispatch?a=b " + protocol + " /servletContext/dispatch /dispatch", message); } finally { client.getConnectionManager().shutdown(); } } @Test public void testIncludesPathParameters() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/dispatch?a=b"); get.setHeader("forward", "/path;pathparam=foo"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("pathInfo:null queryString:a=b servletPath:/path requestUri:/servletContext/path;pathparam=foo", response); } finally { client.getConnectionManager().shutdown(); } } } DispatcherIncludeTestCase.java000066400000000000000000000201611420065311100355260ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/dispatcher/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.dispatcher; import java.io.IOException; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.util.MessageFilter; import io.undertow.servlet.test.util.MessageServlet; import io.undertow.servlet.test.util.PathTestServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestResourceLoader; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import io.undertow.testutils.TestHttpClient; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class DispatcherIncludeTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setResourceManager(new TestResourceLoader(DispatcherIncludeTestCase.class)) .addServlet( new ServletInfo("include", MessageServlet.class) .addInitParam(MessageServlet.MESSAGE, "included") .addMapping("/include")) .addServlet( new ServletInfo("dispatcher", IncludeServlet.class) .addMapping("/dispatch")) .addServlet( new ServletInfo("pathTest", PathTestServlet.class) .addMapping("/path")) .addFilter( new FilterInfo("notIncluded", MessageFilter.class) .addInitParam(MessageFilter.MESSAGE, "Not Included")) .addFilter( new FilterInfo("inc", MessageFilter.class) .addInitParam(MessageFilter.MESSAGE, "Path!")) .addFilter( new FilterInfo("nameFilter", MessageFilter.class) .addInitParam(MessageFilter.MESSAGE, "Name!")) .addFilterUrlMapping("notIncluded", "/include", DispatcherType.REQUEST) .addFilterUrlMapping("inc", "/include", DispatcherType.INCLUDE) .addFilterServletNameMapping("nameFilter", "include", DispatcherType.INCLUDE); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testPathBasedInclude() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/dispatch"); get.setHeader("include", "/include"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(IncludeServlet.MESSAGE + "Path!Name!included", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testNameBasedInclude() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/dispatch"); get.setHeader("include", "include"); get.setHeader("name", "true"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(IncludeServlet.MESSAGE + "Name!included", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testPathBasedStaticInclude() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/dispatch"); get.setHeader("include", "/snippet.html"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(IncludeServlet.MESSAGE + "SnippetText", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testPathBasedStaticIncludePost() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext/dispatch"); post.setHeader("include", "/snippet.html"); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(IncludeServlet.MESSAGE + "SnippetText", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testIncludeAggregatesQueryString() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/dispatch?a=b"); get.setHeader("include", "/path"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals(IncludeServlet.MESSAGE + "pathInfo:null queryString:a=b servletPath:/dispatch requestUri:/servletContext/dispatch", response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/dispatch?a=b"); get.setHeader("include", "/path?foo=bar"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals(IncludeServlet.MESSAGE + "pathInfo:null queryString:a=b servletPath:/dispatch requestUri:/servletContext/dispatch", response); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/dispatcher/ForwardServlet.java000066400000000000000000000036441420065311100335370ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.dispatcher; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author Stuart Douglas */ public class ForwardServlet extends HttpServlet { public static final String MESSAGE = "Hello "; @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write(MESSAGE); //this should be ignored RequestDispatcher dispatcher; if (req.getHeader("name") != null) { dispatcher = req.getServletContext().getNamedDispatcher(req.getHeader("forward")); } else { dispatcher = req.getRequestDispatcher(req.getHeader("forward")); } if (dispatcher != null) { dispatcher.forward(req, resp); } else { resp.sendError(500, "dispatcher was null!"); } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/dispatcher/IncludeServlet.java000066400000000000000000000034221420065311100335100ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.dispatcher; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class IncludeServlet extends HttpServlet { public static final String MESSAGE = "Hello "; @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write(MESSAGE); RequestDispatcher dispatcher; if (req.getHeader("name") != null) { dispatcher = req.getServletContext().getNamedDispatcher(req.getHeader("include")); } else { dispatcher = req.getRequestDispatcher(req.getHeader("include")); } dispatcher.include(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/dispatcher/snippet.html000066400000000000000000000000131420065311100322560ustar00rootroot00000000000000SnippetTextundertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/errorpage/000077500000000000000000000000001420065311100275545ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/errorpage/ChildException.java000066400000000000000000000015201420065311100333170ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.errorpage; /** * @author Stuart Douglas */ public class ChildException extends ParentException { } ErrorPageTestCase.java000066400000000000000000000333371420065311100336730ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/errorpage/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.errorpage; import java.io.IOException; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ErrorPage; import io.undertow.servlet.api.LoggingExceptionHandler; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.ServletStackTraces; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import javax.servlet.RequestDispatcher; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.hamcrest.CoreMatchers; import org.jboss.logging.Logger; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ErrorPageTestCase { @BeforeClass public static void setup() throws IOException, ServletException { final ServletContainer container = ServletContainer.Factory.newInstance(); final PathHandler root = new PathHandler(); DefaultServer.setRootHandler(root); DeploymentInfo builder1 = new DeploymentInfo(); builder1.addServlet(new ServletInfo("error", ErrorServlet.class) .addMapping("/error")); builder1.addServlet(new ServletInfo("path", PathServlet.class) .addMapping("/*")); builder1.addErrorPage(new ErrorPage("/defaultErrorPage")); builder1.addErrorPage(new ErrorPage("/404", StatusCodes.NOT_FOUND)); builder1.addErrorPage(new ErrorPage("/parentException", ParentException.class)); builder1.addErrorPage(new ErrorPage("/childException", ChildException.class)); builder1.addErrorPage(new ErrorPage("/runtimeException", RuntimeException.class)); builder1.setExceptionHandler(LoggingExceptionHandler.builder() .add(ParentException.class, "io.undertow", Logger.Level.DEBUG) .add(ChildException.class, "io.undertow", Logger.Level.DEBUG) .add(RuntimeException.class, "io.undertow", Logger.Level.DEBUG) .add(ServletException.class, "io.undertow", Logger.Level.DEBUG) .build()); builder1.setClassIntrospecter(TestClassIntrospector.INSTANCE) .setClassLoader(ErrorPageTestCase.class.getClassLoader()) .setContextPath("/servletContext1") .setServletStackTraces(ServletStackTraces.NONE) .setDeploymentName("servletContext1.war"); final DeploymentManager manager1 = container.addDeployment(builder1); manager1.deploy(); root.addPrefixPath(builder1.getContextPath(), manager1.start()); DeploymentInfo builder2 = new DeploymentInfo(); builder2.addServlet(new ServletInfo("error", ErrorServlet.class) .addMapping("/error")); builder2.addServlet(new ServletInfo("path", PathServlet.class) .addMapping("/*")); builder2.addErrorPage(new ErrorPage("/404", StatusCodes.NOT_FOUND)); builder2.addErrorPage(new ErrorPage("/501", StatusCodes.NOT_IMPLEMENTED)); builder2.addErrorPage(new ErrorPage("/parentException", ParentException.class)); builder2.addErrorPage(new ErrorPage("/childException", ChildException.class)); builder2.addErrorPage(new ErrorPage("/runtimeException", RuntimeException.class)); builder2.setExceptionHandler(LoggingExceptionHandler.builder() .add(ParentException.class, "io.undertow", Logger.Level.DEBUG) .add(ChildException.class, "io.undertow", Logger.Level.DEBUG) .add(RuntimeException.class, "io.undertow", Logger.Level.DEBUG) .add(ServletException.class, "io.undertow", Logger.Level.DEBUG) .build()); builder2.setClassIntrospecter(TestClassIntrospector.INSTANCE) .setClassLoader(ErrorPageTestCase.class.getClassLoader()) .setContextPath("/servletContext2") .setServletStackTraces(ServletStackTraces.NONE) .setDeploymentName("servletContext2.war"); final DeploymentManager manager2 = container.addDeployment(builder2); manager2.deploy(); root.addPrefixPath(builder2.getContextPath(), manager2.start()); DeploymentInfo builder3 = new DeploymentInfo(); builder3.addServlet(new ServletInfo("error", ErrorServlet.class) .addMapping("/error")); builder3.addServlet(new ServletInfo("path", PathServlet.class) .addMapping("/*")); builder3.addErrorPage(new ErrorPage("/defaultErrorPage")); builder3.addErrorPage(new ErrorPage("/404", StatusCodes.NOT_FOUND)); builder3.addErrorPage(new ErrorPage("/500", StatusCodes.INTERNAL_SERVER_ERROR)); builder3.addErrorPage(new ErrorPage("/parentException", ParentException.class)); builder3.addErrorPage(new ErrorPage("/childException", ChildException.class)); builder3.addErrorPage(new ErrorPage("/runtimeException", RuntimeException.class)); builder3.setClassIntrospecter(TestClassIntrospector.INSTANCE) .setClassLoader(ErrorPageTestCase.class.getClassLoader()) .setContextPath("/servletContext3") .setServletStackTraces(ServletStackTraces.NONE) .setDeploymentName("servletContext3.war"); builder3.setExceptionHandler(LoggingExceptionHandler.builder() .add(ParentException.class, "io.undertow", Logger.Level.DEBUG) .add(ChildException.class, "io.undertow", Logger.Level.DEBUG) .add(RuntimeException.class, "io.undertow", Logger.Level.DEBUG) .add(ServletException.class, "io.undertow", Logger.Level.DEBUG) .build()); final DeploymentManager manager3 = container.addDeployment(builder3); manager3.deploy(); root.addPrefixPath(builder3.getContextPath(), manager3.start()); } @Test public void testErrorPages() throws IOException { TestHttpClient client = new TestHttpClient(); try { runTest(1, client, StatusCodes.NOT_FOUND, null, "/404"); runTest(1, client, StatusCodes.INTERNAL_SERVER_ERROR, null, "/defaultErrorPage"); runTest(1, client, StatusCodes.NOT_IMPLEMENTED, null, "/defaultErrorPage"); runTest(1, client, null, ParentException.class, "/parentException"); runTest(1, client, null, ChildException.class, "/childException"); runTest(1, client, null, RuntimeException.class, "/runtimeException"); runTest(1, client, null, IllegalStateException.class, "/runtimeException"); runTest(1, client, null, Exception.class, "/defaultErrorPage"); runTest(1, client, null, IOException.class, "/defaultErrorPage"); runTest(1, client, null, ServletException.class, "/defaultErrorPage"); } finally { client.getConnectionManager().shutdown(); } } @Test public void testErrorPagesWithNoDefaultErrorPage() throws IOException { TestHttpClient client = new TestHttpClient(); try { runTest(2, client, StatusCodes.NOT_FOUND, null, "/404"); runTest(2, client, StatusCodes.NOT_IMPLEMENTED, null, "/501"); runTest(2, client, StatusCodes.INTERNAL_SERVER_ERROR, null, "ErrorInternal Server Error", false); runTest(2, client, null, ParentException.class, "/parentException"); runTest(2, client, null, ChildException.class, "/childException"); runTest(2, client, null, RuntimeException.class, "/runtimeException"); runTest(2, client, null, IllegalStateException.class, "/runtimeException"); runTest(2, client, null, Exception.class, "ErrorInternal Server Error", false); runTest(2, client, null, IOException.class, "ErrorInternal Server Error", false); runTest(2, client, null, ServletException.class, "ErrorInternal Server Error", false); } finally { client.getConnectionManager().shutdown(); } } //see UNDERTOW-249 @Test public void testErrorPagesWith500PageMapped() throws IOException { TestHttpClient client = new TestHttpClient(); try { runTest(3, client, StatusCodes.NOT_FOUND, null, "/404"); runTest(3, client, StatusCodes.INTERNAL_SERVER_ERROR, null, "/500"); runTest(3, client, StatusCodes.NOT_IMPLEMENTED, null, "/defaultErrorPage"); runTest(3, client, null, ParentException.class, "/parentException"); runTest(3, client, null, ChildException.class, "/childException"); runTest(3, client, null, RuntimeException.class, "/runtimeException"); runTest(3, client, null, IllegalStateException.class, "/runtimeException"); runTest(3, client, null, Exception.class, "/500"); runTest(3, client, null, IOException.class, "/500"); runTest(3, client, null, ServletException.class, "/500"); } finally { client.getConnectionManager().shutdown(); } } private void runTest(int deploymentNo, final TestHttpClient client, Integer statusCode, Class exception, String expected) throws IOException { this.runTest(deploymentNo, client, statusCode, exception, expected, true); } private void runTest(int deploymentNo, final TestHttpClient client, Integer statusCode, Class exception, String expected, boolean checkAttributes) throws IOException { final HttpGet get; final HttpResponse result; final String response; get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext" + deploymentNo + "/error?" + (statusCode != null ? "statusCode=" + statusCode : "exception=" + exception.getName())); result = client.execute(get); Assert.assertEquals(statusCode == null ? StatusCodes.INTERNAL_SERVER_ERROR : statusCode, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertThat(response, CoreMatchers.startsWith(expected)); if (checkAttributes) { // check error attributes Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.ERROR_REQUEST_URI + "=/servletContext" + deploymentNo + "/error")); Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.ERROR_SERVLET_NAME + "=error")); if (statusCode == null) { if (RuntimeException.class.isAssignableFrom(exception)) { Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.ERROR_EXCEPTION_TYPE + "=" + exception)); Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.ERROR_EXCEPTION + "=" + exception.getName())); // RequestDispatcher.ERROR_MESSAGE is null Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.ERROR_STATUS_CODE + "=500")); } else { Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.ERROR_EXCEPTION_TYPE + "=" + exception)); Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.ERROR_EXCEPTION + "=" + exception.getName())); Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.ERROR_MESSAGE + "=" + exception.getName())); Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.ERROR_STATUS_CODE + "=500")); } } else { Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.ERROR_MESSAGE + "=" + StatusCodes.getReason(statusCode))); Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.ERROR_STATUS_CODE + "=" + statusCode)); } // check forward attributes Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.FORWARD_REQUEST_URI + "=/servletContext" + deploymentNo + "/error")); Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.FORWARD_CONTEXT_PATH + "=/servletContext" + deploymentNo)); Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.FORWARD_QUERY_STRING + "=" + (statusCode != null ? "statusCode=" + statusCode : "exception=" + exception.getName()))); // RequestDispatcher.FORWARD_PATH_INFO is null Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.FORWARD_SERVLET_PATH + "=/error")); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/errorpage/ErrorServlet.java000066400000000000000000000032061420065311100330560ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.errorpage; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class ErrorServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { String statusCode = req.getParameter("statusCode"); if (statusCode != null) { resp.sendError(Integer.parseInt(statusCode)); } else { try { throw (Exception) getClass().getClassLoader().loadClass(req.getParameter("exception")).newInstance(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new ServletException(e); } } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/errorpage/ParentException.java000066400000000000000000000015131420065311100335270ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.errorpage; /** * @author Stuart Douglas */ public class ParentException extends Exception { } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/errorpage/PathServlet.java000066400000000000000000000031771420065311100326700ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.errorpage; import java.io.IOException; import java.io.PrintWriter; import java.util.Enumeration; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class PathServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { try (PrintWriter w = resp.getWriter()) { w.println(req.getPathInfo()); // write all the attributes Enumeration e = req.getAttributeNames(); while (e.hasMoreElements()) { String attr = e.nextElement(); w.print(attr); w.print("="); w.println(req.getAttribute(attr)); } } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/errorpage/SecureServlet.java000066400000000000000000000015631420065311100332170ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.errorpage; import javax.servlet.http.HttpServlet; /** * @author Stuart Douglas */ public class SecureServlet extends HttpServlet { } SecurityErrorPageTestCase.java000066400000000000000000000124031420065311100354120ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/errorpage/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.errorpage; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.Servlets; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ErrorPage; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.ServletStackTraces; import io.undertow.servlet.test.security.constraint.ServletIdentityManager; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import java.io.IOException; import javax.servlet.RequestDispatcher; import org.hamcrest.CoreMatchers; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class SecurityErrorPageTestCase { @BeforeClass public static void setup() throws IOException, ServletException { final ServletContainer container = ServletContainer.Factory.newInstance(); final PathHandler root = new PathHandler(); DefaultServer.setRootHandler(root); DeploymentInfo builder = new DeploymentInfo(); builder.addServlet(new ServletInfo("secure", SecureServlet.class) .addMapping("/secure")) .addSecurityConstraint(Servlets.securityConstraint().addRoleAllowed("user").addWebResourceCollection(Servlets.webResourceCollection().addUrlPattern("/*"))); builder.addServlet(new ServletInfo("path", PathServlet.class) .addMapping("/*")); builder.addErrorPage(new ErrorPage("/401", StatusCodes.UNAUTHORIZED)); ServletIdentityManager identityManager = new ServletIdentityManager(); identityManager.addUser("user1", "password1"); // Just one role less user. builder.setClassIntrospecter(TestClassIntrospector.INSTANCE) .setClassLoader(ErrorPageTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setServletStackTraces(ServletStackTraces.NONE) .setIdentityManager(identityManager) .setLoginConfig(Servlets.loginConfig("BASIC", "Test Realm")) .setDeploymentName("servletContext.war"); final DeploymentManager manager1 = container.addDeployment(builder); manager1.deploy(); root.addPrefixPath(builder.getContextPath(), manager1.start()); } @Test public void testErrorPages() throws IOException { TestHttpClient client = new TestHttpClient(); try { runTest(client, StatusCodes.UNAUTHORIZED, "/401"); } finally { client.getConnectionManager().shutdown(); } } private void runTest(final TestHttpClient client, int statusCode, String expected) throws IOException { final HttpGet get; final HttpResponse result; final String response; get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/secure"); result = client.execute(get); Assert.assertEquals(statusCode, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertThat(response, CoreMatchers.startsWith(expected)); // check error attributes Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.ERROR_REQUEST_URI + "=/servletContext/secure")); Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.ERROR_SERVLET_NAME + "=secure")); Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.ERROR_MESSAGE + "=" + StatusCodes.getReason(statusCode))); Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.ERROR_STATUS_CODE + "=" + statusCode)); // check forward attributes Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.FORWARD_REQUEST_URI + "=/servletContext/secure")); Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.FORWARD_CONTEXT_PATH + "=/servletContext")); // RequestDispatcher.FORWARD_QUERY_STRING is null // RequestDispatcher.FORWARD_PATH_INFO is null Assert.assertThat(response, CoreMatchers.containsString(RequestDispatcher.FORWARD_SERVLET_PATH + "=/secure")); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/handlers/000077500000000000000000000000001420065311100273665ustar00rootroot00000000000000AbstractHttpContinueServletTestCase.java000066400000000000000000000200031420065311100372560ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.handlers; import io.undertow.servlet.Servlets; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpParams; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * @author Stuart Douglas */ public abstract class AbstractHttpContinueServletTestCase { private static volatile boolean accept = false; @BeforeClass public static void setup() { DeploymentUtils.setupServlet(Servlets.servlet(ContinueConsumeServlet.class).addMappings("/path"), Servlets.servlet(ContinueIgnoreServlet.class).addMappings("/ignore")); } @Before public void before() throws Exception { Assume.assumeFalse(DefaultServer.isAjp()); } protected abstract String getServerAddress(); protected abstract TestHttpClient getClient(); @Test public void testHttpContinueRejected() throws IOException { accept = false; String message = "My HTTP Request!"; HttpParams httpParams = new BasicHttpParams(); httpParams.setParameter("http.protocol.wait-for-continue", Integer.MAX_VALUE); TestHttpClient client = getClient(); client.setParams(httpParams); try { HttpPost post = new HttpPost(getServerAddress() + "/servletContext/path"); post.addHeader("Expect", "100-continue"); post.setEntity(new StringEntity(message)); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.EXPECTATION_FAILED, result.getStatusLine().getStatusCode()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testHttpContinueAccepted() throws IOException { accept = true; String message = "My HTTP Request!"; HttpParams httpParams = new BasicHttpParams(); httpParams.setParameter("http.protocol.wait-for-continue", Integer.MAX_VALUE); TestHttpClient client = getClient(); client.setParams(httpParams); try { HttpPost post = new HttpPost(getServerAddress() + "/servletContext/path"); post.addHeader("Expect", "100-continue"); post.setEntity(new StringEntity(message)); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(message, HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } @Test public void testHttpContinueIgnored() throws IOException { accept = true; String message = "My HTTP Request!"; HttpParams httpParams = new BasicHttpParams(); httpParams.setParameter("http.protocol.wait-for-continue", Integer.MAX_VALUE); TestHttpClient client = getClient(); client.setParams(httpParams); try { HttpPost post = new HttpPost(getServerAddress() + "/servletContext/ignore"); post.addHeader("Expect", "100-continue"); post.setEntity(new StringEntity(message)); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("", HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } @Test public void testEmptyHttpContinue() throws IOException { accept = true; String message = "My HTTP Request!"; HttpParams httpParams = new BasicHttpParams(); httpParams.setParameter("http.protocol.wait-for-continue", Integer.MAX_VALUE); TestHttpClient client = getClient(); client.setParams(httpParams); try { HttpGet post = new HttpGet(getServerAddress() + "/servletContext/ignore"); post.addHeader("Expect", "100-continue"); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("", HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } //UNDERTOW-162 @Test public void testHttpContinueAcceptedWithChunkedRequest() throws IOException { accept = true; String message = "My HTTP Request!"; HttpParams httpParams = new BasicHttpParams(); httpParams.setParameter("http.protocol.wait-for-continue", Integer.MAX_VALUE); TestHttpClient client = getClient(); client.setParams(httpParams); try { HttpPost post = new HttpPost(getServerAddress() + "/servletContext/path"); post.addHeader("Expect", "100-continue"); post.setEntity(new StringEntity(message) { @Override public long getContentLength() { return -1; } }); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals(message, HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } public static class ContinueConsumeServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { if (!accept) { resp.setStatus(StatusCodes.EXPECTATION_FAILED); return; } byte[] buffer = new byte[1024]; final ByteArrayOutputStream b = new ByteArrayOutputStream(); int r = 0; final OutputStream outputStream = resp.getOutputStream(); final InputStream inputStream = req.getInputStream(); while ((r = inputStream.read(buffer)) > 0) { b.write(buffer, 0, r); } outputStream.write(b.toByteArray()); outputStream.close(); } catch (IOException e) { throw new RuntimeException(e); } } } public static class ContinueIgnoreServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } } } HttpContinueServletTestCase.java000066400000000000000000000023061420065311100356000ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.handlers; import org.junit.runner.RunWith; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.TestHttpClient; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class HttpContinueServletTestCase extends AbstractHttpContinueServletTestCase { protected String getServerAddress() { return DefaultServer.getDefaultServerURL(); } protected TestHttpClient getClient() { return new TestHttpClient(); } } HttpContinueSslServletTestCase.java000066400000000000000000000031551420065311100362650ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.handlers; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.TestHttpClient; import org.junit.After; import org.junit.Before; import org.junit.runner.RunWith; import java.io.IOException; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class HttpContinueSslServletTestCase extends AbstractHttpContinueServletTestCase { protected String getServerAddress() { return DefaultServer.getDefaultServerSSLAddress(); } protected TestHttpClient getClient() { TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); return client; } @Before public void before() throws Exception { super.before(); DefaultServer.startSSLServer(); } @After public void after() throws IOException { DefaultServer.stopSSLServer(); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/handlers/IsSecureFilter.java000066400000000000000000000033151420065311100331230ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.handlers; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author Stuart Douglas */ public class IsSecureFilter implements Filter { @Override public void init(final FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { ((HttpServletResponse) response).setHeader("issecure", Boolean.toString(request.isSecure())); Cookie cookie = new Cookie("foo","bar"); ((HttpServletResponse) response).addCookie(cookie); chain.doFilter(request,response); } @Override public void destroy() { } } MarkSecureHandlerTestCase.java000066400000000000000000000142151420065311100351500ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/handlers/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.handlers; import io.undertow.server.handlers.PathHandler; import io.undertow.server.handlers.SecureCookieHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.handlers.MarkSecureHandler; import io.undertow.servlet.test.util.MessageServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import java.io.IOException; import java.security.GeneralSecurityException; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class MarkSecureHandlerTestCase { public static final String HELLO_WORLD = "Hello World"; @Test public void testMarkSecureHandler() throws IOException, GeneralSecurityException, ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("servlet", MessageServlet.class) .addInitParam(MessageServlet.MESSAGE, HELLO_WORLD) .addMapping("/issecure"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(MarkSecureHandlerTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlet(s); builder.addFilter(new FilterInfo("issecure-filter", IsSecureFilter.class)); builder.addFilterUrlMapping("issecure-filter", "/*", DispatcherType.REQUEST); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(new MarkSecureHandler(root)); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/issecure"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); // When MarkSecureHandler is enabled, req.isSecure() should be true Assert.assertEquals("true", result.getHeaders("issecure")[0].getValue()); // When SecureCookieHandler is not enabled, secure cookie is not automatically enabled. Header header = result.getFirstHeader("set-cookie"); Assert.assertEquals("foo=bar", header.getValue()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(HELLO_WORLD, response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testMarkSecureHandlerWithSecureCookieHandler() throws IOException, GeneralSecurityException, ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("servlet", MessageServlet.class) .addInitParam(MessageServlet.MESSAGE, HELLO_WORLD) .addMapping("/issecure"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(MarkSecureHandlerTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlet(s); builder.addFilter(new FilterInfo("issecure-filter", IsSecureFilter.class)); builder.addFilterUrlMapping("issecure-filter", "/*", DispatcherType.REQUEST); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(new MarkSecureHandler(new SecureCookieHandler(root))); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/issecure"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); // When MarkSecureHandler is enabled, req.isSecure() should be true Assert.assertEquals("true", result.getHeaders("issecure")[0].getValue()); // When SecureCookieHandler is enabled with MarkSecureHandler, secure cookie is enabled as this channel is treated as secure Header header = result.getFirstHeader("set-cookie"); Assert.assertEquals("foo=bar; secure", header.getValue()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(HELLO_WORLD, response); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/lifecycle/000077500000000000000000000000001420065311100275255ustar00rootroot00000000000000EagerServletLifecycleTestCase.java000066400000000000000000000045401420065311100361600ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/lifecycle/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.lifecycle; import javax.servlet.DispatcherType; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; /** * Tests if eager filter init works properly * * @author Tomaz Cerar */ @RunWith(DefaultServer.class) public class EagerServletLifecycleTestCase { @Test public void testServletLifecycle() throws Exception { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); FilterInfo f = new FilterInfo("filter", LifecycleFilter.class); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(EagerServletLifecycleTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setEagerFilterInit(true) .addFilter(f) .addFilterUrlMapping("filter", "/aa", DispatcherType.REQUEST); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); Assert.assertTrue(LifecycleFilter.initCalled); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/lifecycle/FirstServlet.java000066400000000000000000000031211420065311100330210ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.lifecycle; import org.junit.Assert; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.IOException; /** * @author Stuart Douglas */ public class FirstServlet implements Servlet { public static volatile boolean init; @Override public void init(ServletConfig config) throws ServletException { Assert.assertFalse(SecondServlet.init); init = true; } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { } @Override public String getServletInfo() { return null; } @Override public void destroy() { } } InitializeInOrderTestCase.java000066400000000000000000000031041420065311100353270ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/lifecycle/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.lifecycle; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.DeploymentUtils; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; /** * @author Stuart Douglas */ public class InitializeInOrderTestCase { @BeforeClass public static void setup() { DeploymentUtils.setupServlet(new ServletInfo("s1", FirstServlet.class) .setLoadOnStartup(1), new ServletInfo("s2", SecondServlet.class) .setLoadOnStartup(2) ,new ServletInfo("s3", ThirdServlet.class) .setLoadOnStartup(3)); } @Test public void testInitializeInOrder() throws Exception { Assert.assertTrue(FirstServlet.init); Assert.assertTrue(SecondServlet.init); Assert.assertTrue(ThirdServlet.init); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/lifecycle/LifeCycleServlet.java000066400000000000000000000031511420065311100335740ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.lifecycle; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.IOException; /** * @author Stuart Douglas */ public class LifeCycleServlet implements Servlet { public static volatile boolean initCalled; public static volatile boolean destroyCalled; @Override public void init(ServletConfig config) throws ServletException { initCalled = true; } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { } @Override public String getServletInfo() { return null; } @Override public void destroy() { destroyCalled = true; } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/lifecycle/LifecycleFilter.java000066400000000000000000000030301420065311100334310ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.lifecycle; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.IOException; /** * @author Stuart Douglas */ public class LifecycleFilter implements Filter { public static boolean initCalled; public static boolean destroyCalled; @Override public void init(FilterConfig filterConfig) throws ServletException { initCalled = true; } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(request, response); } @Override public void destroy() { destroyCalled = true; } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/lifecycle/SecondServlet.java000066400000000000000000000031211420065311100331450ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.lifecycle; import org.junit.Assert; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.IOException; /** * @author Stuart Douglas */ public class SecondServlet implements Servlet { public static volatile boolean init; @Override public void init(ServletConfig config) throws ServletException { Assert.assertTrue(FirstServlet.init); init = true; } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { } @Override public String getServletInfo() { return null; } @Override public void destroy() { } } ServletLifecycleTestCase.java000066400000000000000000000065361420065311100352230ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/lifecycle/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.lifecycle; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.DispatcherType; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ServletLifecycleTestCase { @Test public void testServletLifecycle() throws Exception { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("servlet", LifeCycleServlet.class) .addMapping("/aa"); FilterInfo f = new FilterInfo("filter", LifecycleFilter.class); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(ServletLifecycleTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlet(s) .addFilter(f) .addFilterUrlMapping("filter", "/aa", DispatcherType.REQUEST); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/aa"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); manager.stop(); manager.undeploy(); Assert.assertTrue(LifeCycleServlet.initCalled); Assert.assertTrue(LifeCycleServlet.destroyCalled); Assert.assertTrue(LifecycleFilter.initCalled); Assert.assertTrue(LifecycleFilter.destroyCalled); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/lifecycle/ThirdServlet.java000066400000000000000000000032721420065311100330130ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.lifecycle; import org.junit.Assert; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.SingleThreadModel; import java.io.IOException; /** * @author Stuart Douglas */ public class ThirdServlet implements Servlet, SingleThreadModel { public static volatile boolean init; @Override public void init(ServletConfig config) throws ServletException { Assert.assertTrue(FirstServlet.init); Assert.assertTrue(SecondServlet.init); init = true; } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { } @Override public String getServletInfo() { return null; } @Override public void destroy() { } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/000077500000000000000000000000001420065311100274135ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/ordering/000077500000000000000000000000001420065311100312245ustar00rootroot00000000000000FirstListener.java000066400000000000000000000023511420065311100346060ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/ordering/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.ordering; import io.undertow.servlet.test.util.Tracker; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; public class FirstListener implements ServletRequestListener { @Override public void requestInitialized(ServletRequestEvent sre) { Tracker.addAction(FirstListener.class.getSimpleName()); } @Override public void requestDestroyed(ServletRequestEvent sre) { Tracker.addAction(FirstListener.class.getSimpleName()); } } SecondListener.java000066400000000000000000000023541420065311100347350ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/ordering/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.ordering; import io.undertow.servlet.test.util.Tracker; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; public class SecondListener implements ServletRequestListener { @Override public void requestInitialized(ServletRequestEvent sre) { Tracker.addAction(SecondListener.class.getSimpleName()); } @Override public void requestDestroyed(ServletRequestEvent sre) { Tracker.addAction(SecondListener.class.getSimpleName()); } } ServletSessionListenerOrderingTestCase.java000066400000000000000000000072351420065311100416430ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/ordering/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.ordering; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ListenerInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.util.EmptyServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.Tracker; import io.undertow.testutils.DefaultServer; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import io.undertow.testutils.TestHttpClient; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @see https://issues.jboss.org/browse/UNDERTOW-23 * * @author Jozef Hartinger * */ @RunWith(DefaultServer.class) public class ServletSessionListenerOrderingTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler path = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/listener") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("listener.war") .addListener(new ListenerInfo(FirstListener.class)) .addListener(new ListenerInfo(SecondListener.class)) .addServlet(new ServletInfo("message", EmptyServlet.class) .addMapping("/*")); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); path.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(path); } @Test public void testSimpleSessionUsage() throws IOException { TestHttpClient client = new TestHttpClient(); try { Tracker.reset(); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/listener/test"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); List expected = new ArrayList<>(); expected.add(FirstListener.class.getSimpleName()); expected.add(SecondListener.class.getSimpleName()); expected.add(SecondListener.class.getSimpleName()); expected.add(FirstListener.class.getSimpleName()); Assert.assertEquals(expected, Tracker.getActions()); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/000077500000000000000000000000001420065311100311035ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/000077500000000000000000000000001420065311100322205ustar00rootroot00000000000000AnotherAsyncServlet.java000066400000000000000000000033771420065311100367610ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.request.async; import java.io.IOException; import javax.servlet.AsyncContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Jozef Hartinger */ public class AnotherAsyncServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { final AsyncContext ctx = req.startAsync(); Thread t = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(100); resp.setContentType("text/plain"); resp.getWriter().write(AnotherAsyncServlet.class.getSimpleName()); ctx.complete(); } catch (Exception e) { throw new RuntimeException(e); } } }); t.start(); } } AsyncListenerExceptionTest.java000066400000000000000000000171401420065311100403110ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/* * JBoss, Home of Professional Open Source. * Copyright 2017 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.request.async; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; /** * Test that AsyncListener failures do not block execution of other listeners. * * @author ckozak */ @RunWith(DefaultServer.class) public class AsyncListenerExceptionTest { @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo runtime = new ServletInfo("runtime", RuntimeExceptionServlet.class) .addMapping("/runtime") .setAsyncSupported(true); ServletInfo io = new ServletInfo("io", IOExceptionServlet.class) .addMapping("/io") .setAsyncSupported(true); ServletInfo error = new ServletInfo("error", ErrorServlet.class) .addMapping("/error") .setAsyncSupported(true); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(AsyncListenerExceptionTest.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlets(runtime, io, error); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Before public void setUp() { AbstractAsyncServlet.QUEUE.clear(); } @Test public void onCompleteThrowsRuntimeException() throws IOException, InterruptedException { doTest("runtime", false); } @Test public void onCompleteThrowsIOException() throws IOException, InterruptedException { doTest("io", false); } @Test public void onCompleteThrowsError() throws IOException, InterruptedException { doTest("error", false); } @Test public void onTimeoutThrowsRuntimeException() throws IOException, InterruptedException { doTest("runtime", true); } @Test public void onTimeoutThrowsIOException() throws IOException, InterruptedException { doTest("io", true); } @Test public void onTimeoutThrowsError() throws IOException, InterruptedException { doTest("error", true); } private void doTest(String urlTail, boolean timeout) throws IOException, InterruptedException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/" + urlTail); if (timeout) { get.addHeader("timeout", "true"); } HttpResponse result = client.execute(get); Assert.assertEquals(timeout ? 500 : 200, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); List expected = new LinkedList<>(); expected.add("onComplete"); expected.add("onComplete"); if (timeout) { expected.add("onTimeout"); expected.add("onTimeout"); } List actual = new LinkedList<>(); for (int i = 0; i < expected.size(); i++) { actual.add(AbstractAsyncServlet.QUEUE.poll(10, TimeUnit.SECONDS)); } actual.sort(Comparator.naturalOrder()); Assert.assertEquals(expected, actual); } finally { client.getConnectionManager().shutdown(); } } public abstract static class AbstractAsyncServlet extends HttpServlet { static final BlockingQueue QUEUE = new LinkedBlockingDeque<>(); @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { AsyncContext context = req.startAsync(); context.setTimeout(1000); for (int i = 0; i < 2; i++) { context.addListener(new AsyncListener() { @Override public void onComplete(AsyncEvent asyncEvent) throws IOException { QUEUE.add("onComplete"); throwException(); } @Override public void onTimeout(AsyncEvent asyncEvent) throws IOException { QUEUE.add("onTimeout"); throwException(); } @Override public void onError(AsyncEvent asyncEvent) throws IOException { QUEUE.add("onError"); throwException(); } @Override public void onStartAsync(AsyncEvent asyncEvent) throws IOException { QUEUE.add("onStartAsync"); } }); } if (req.getHeader("timeout") == null) { context.complete(); } } protected abstract void throwException() throws IOException; } public static final class RuntimeExceptionServlet extends AbstractAsyncServlet { @Override protected void throwException() throws IOException { throw new RuntimeException(); } } public static final class IOExceptionServlet extends AbstractAsyncServlet { @Override protected void throwException() throws IOException { throw new IOException(); } } public static final class ErrorServlet extends AbstractAsyncServlet { @Override protected void throwException() throws IOException { throw new Error(); } } } AsyncServlet.java000066400000000000000000000031201420065311100354220ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.request.async; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class AsyncServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { req.startAsync(); Thread t = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } req.getAsyncContext().dispatch("/message"); } }); t.start(); } } CompleteAsyncServlet.java000066400000000000000000000040221420065311100371150ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/* * JBoss, Home of Professional Open Source. * Copyright 2018 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * 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. */ package io.undertow.servlet.test.listener.request.async; import java.io.IOException; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import io.undertow.servlet.test.util.TestListener; /** * @author Stuart Douglas */ public class CompleteAsyncServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { req.startAsync(); req.getAsyncContext().addListener(new AsyncListener() { @Override public void onComplete(AsyncEvent event) throws IOException { TestListener.addMessage("onComplete"); } @Override public void onTimeout(AsyncEvent event) throws IOException { } @Override public void onError(AsyncEvent event) throws IOException { } @Override public void onStartAsync(AsyncEvent event) throws IOException { TestListener.addMessage("onStartAsync"); } }); req.getAsyncContext().complete(); resp.getWriter().write("asynccomplete"); } } RequestListenerAsyncRequestTestCase.java000066400000000000000000000136611420065311100421540ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.request.async; import java.io.IOException; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ListenerInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.util.MessageServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestListener; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class RequestListenerAsyncRequestTestCase { public static final String HELLO_WORLD = "Hello World"; @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo m = new ServletInfo("messageServlet", MessageServlet.class) .addInitParam(MessageServlet.MESSAGE, HELLO_WORLD) .setAsyncSupported(true) .addMapping("/message"); ServletInfo a = new ServletInfo("asyncServlet", AsyncServlet.class) .addInitParam(MessageServlet.MESSAGE, HELLO_WORLD) .setAsyncSupported(true) .addMapping("/async"); ServletInfo comp = new ServletInfo("completeAsyncServlet", CompleteAsyncServlet.class) .addInitParam(MessageServlet.MESSAGE, HELLO_WORLD) .setAsyncSupported(true) .addMapping("/asynccomplete"); ServletInfo a2 = new ServletInfo("asyncServlet2", AnotherAsyncServlet.class) .setAsyncSupported(true) .addMapping("/async2"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlets(m, a, a2, comp) .addListener(new ListenerInfo(TestListener.class)); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testSimpleHttpServlet() throws IOException { TestListener.init(4); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/async"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(HELLO_WORLD, response); Assert.assertArrayEquals(new String[]{"created REQUEST", "destroyed REQUEST", "created ASYNC", "destroyed ASYNC"}, TestListener.results().toArray()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testSimpleHttpServletCompleteInInitialRequest() throws IOException { TestListener.init(3); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/asynccomplete"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("asynccomplete", response); Assert.assertArrayEquals(new String[]{"created REQUEST", "onComplete", "destroyed REQUEST"}, TestListener.results().toArray()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testSimpleAsyncHttpServletWithoutDispatch() throws IOException { TestListener.init(2); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/async2"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(AnotherAsyncServlet.class.getSimpleName(), response); Assert.assertArrayEquals(new String[]{"created REQUEST", "destroyed REQUEST", "created REQUEST", "destroyed REQUEST"}, TestListener.results().toArray()); } finally { client.getConnectionManager().shutdown(); } } } onComplete/000077500000000000000000000000001420065311100342465ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/asyncAsyncListenerOnCompleteTest.java000066400000000000000000000073441420065311100425320ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onComplete/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.request.async.onComplete; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.LoggingExceptionHandler; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.MessageServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.jboss.logging.Logger; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import java.io.IOException; import java.util.concurrent.TimeUnit; @RunWith(DefaultServer.class) public class AsyncListenerOnCompleteTest { @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo f = new ServletInfo("asyncServlet", OnCompleteServlet.class) .addMapping("/async") .setAsyncSupported(true); ServletInfo a1 = new ServletInfo("message", MessageServlet.class) .setAsyncSupported(true) .addInitParam(MessageServlet.MESSAGE, "Hello") .addMapping("/message"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(AsyncListenerOnCompleteTest.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlets(f, a1); builder.setExceptionHandler(LoggingExceptionHandler.builder() .add(IllegalStateException.class, "io.undertow", Logger.Level.DEBUG) .build()); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testOnCompleteWithNoCompleteCalled() throws IOException, InterruptedException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/async"); HttpResponse result = client.execute(get); Assert.assertEquals(200, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("Hello", response); Assert.assertEquals("onComplete", OnCompleteServlet.QUEUE.poll(10, TimeUnit.SECONDS)); } finally { client.getConnectionManager().shutdown(); } } } OnCompleteServlet.java000066400000000000000000000044731420065311100405330ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onComplete/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.request.async.onComplete; import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; public class OnCompleteServlet extends HttpServlet { public static final BlockingQueue QUEUE = new LinkedBlockingDeque<>(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { final AsyncContext ctx = req.startAsync(); ctx.addListener(new AsyncListener() { @Override public void onComplete(AsyncEvent event) throws IOException { QUEUE.add("onComplete"); } @Override public void onTimeout(AsyncEvent event) throws IOException { QUEUE.add("onTimeout"); } @Override public void onError(AsyncEvent event) throws IOException { QUEUE.add("onError"); } @Override public void onStartAsync(AsyncEvent event) throws IOException { QUEUE.add("onStartAsync"); } }); Thread thread = new Thread(new Runnable() { @Override public void run() { ctx.dispatch("/message"); } }); thread.start(); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onError/000077500000000000000000000000001420065311100336465ustar00rootroot00000000000000AsyncEventListener.java000066400000000000000000000050221420065311100402160ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onError/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.request.async.onError; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import javax.servlet.AsyncEvent; /** * @author Stuart Douglas */ public class AsyncEventListener implements javax.servlet.AsyncListener { private static final LinkedBlockingDeque EVENTS = new LinkedBlockingDeque<>(); public static String[] results(int expected) { List poll = new ArrayList<>(); String current = EVENTS.poll(); while (current != null) { poll.add(current); current = EVENTS.poll(); } try { if (poll.size() < expected) { current = EVENTS.poll(5, TimeUnit.SECONDS); while (current != null) { poll.add(current); if (poll.size() < expected) { current = EVENTS.poll(5, TimeUnit.SECONDS); } else { current = EVENTS.poll(); } } } } catch (InterruptedException e) { throw new RuntimeException(e); } String[] ret = poll.toArray(new String[poll.size()]); EVENTS.clear(); return ret; } @Override public void onComplete(final AsyncEvent event) throws IOException { EVENTS.add("COMPLETE"); } @Override public void onTimeout(final AsyncEvent event) throws IOException { EVENTS.add("TIMEOUT"); } @Override public void onError(final AsyncEvent event) throws IOException { EVENTS.add("ERROR"); } @Override public void onStartAsync(final AsyncEvent event) throws IOException { EVENTS.add("START"); } } AsyncListenerOnErrorTest.java000066400000000000000000000170271420065311100413730ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onError/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.request.async.onError; import java.io.IOException; import javax.servlet.ServletException; import io.undertow.testutils.ProxyIgnore; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.jboss.logging.Logger; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.LoggingExceptionHandler; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; /** * @author Jozef Hartinger * @see https://issues.jboss.org/browse/UNDERTOW-30 * @see https://issues.jboss.org/browse/UNDERTOW-31 * @see https://issues.jboss.org/browse/UNDERTOW-32 */ @RunWith(DefaultServer.class) public class AsyncListenerOnErrorTest { @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo f = new ServletInfo("faultyServlet", FaultyServlet.class) .addMapping("/faulty"); ServletInfo a1 = new ServletInfo("asyncServlet1", AsyncServlet1.class) .setAsyncSupported(true) .addMapping("/async1"); ServletInfo a2 = new ServletInfo("asyncServlet2", AsyncServlet2.class) .setAsyncSupported(true) .addMapping("/async2"); ServletInfo a3 = new ServletInfo("asyncServlet3", AsyncServlet3.class) .setAsyncSupported(true) .addMapping("/async3"); ServletInfo a4 = new ServletInfo("asyncServlet4", AsyncServlet4.class) .setAsyncSupported(true) .addMapping("/async4"); ServletInfo a5 = new ServletInfo("asyncServlet5", AsyncServlet5.class) .setAsyncSupported(true) .addMapping("/async5"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(AsyncListenerOnErrorTest.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlets(f, a1, a2, a3, a4, a5); builder.setExceptionHandler(LoggingExceptionHandler.builder() .add(IllegalStateException.class, "io.undertow", Logger.Level.DEBUG) .build()); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testAsyncListenerOnErrorInvoked1() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/async1"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(SimpleAsyncListener.MESSAGE, response); Assert.assertArrayEquals(new String[]{"ERROR", "COMPLETE"}, AsyncEventListener.results(2)); } finally { client.getConnectionManager().shutdown(); } } @Test public void testAsyncListenerOnErrorInvoked2() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/async2"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(SimpleAsyncListener.MESSAGE, response); Assert.assertArrayEquals(new String[]{"ERROR", "COMPLETE"}, AsyncEventListener.results(2)); } finally { client.getConnectionManager().shutdown(); } } @Test public void testMultiAsyncDispatchError() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/async3"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(SimpleAsyncListener.MESSAGE, response); Assert.assertArrayEquals(new String[]{"START", "ERROR", "COMPLETE"}, AsyncEventListener.results(3)); } finally { client.getConnectionManager().shutdown(); } } /** * Regression test for UNDERTOW-1455 * * Compared to testAsyncListenerOnErrorInvoked* tests, exception is thrown in * entering servlet not in asynchronous dispatch part. */ @Test public void testAsyncListenerOnErrorExceptionInFirstServlet() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/async4"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(SimpleAsyncListener.MESSAGE, response); Assert.assertArrayEquals(new String[]{"ERROR", "COMPLETE"}, AsyncEventListener.results(2)); } finally { client.getConnectionManager().shutdown(); } } @Test @ProxyIgnore // FIXME UNDERTOW-1523 public void testAsyncErrorOnClientBreakdown() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/async5"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); org.apache.http.client.utils.HttpClientUtils.closeQuietly(client); Assert.assertArrayEquals(new String[]{"ERROR", "COMPLETE"}, AsyncEventListener.results(3)); } finally { client.getConnectionManager().shutdown(); } } } AsyncServlet1.java000066400000000000000000000026271420065311100371440ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onError/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.request.async.onError; import java.io.IOException; import javax.servlet.AsyncContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class AsyncServlet1 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { AsyncContext ctx = req.startAsync(); ctx.addListener(new AsyncEventListener()); ctx.addListener(new SimpleAsyncListener(ctx)); Thread thread = new Thread(new AsyncTask(ctx)); thread.start(); } } AsyncServlet2.java000066400000000000000000000026241420065311100371420ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onError/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.request.async.onError; import java.io.IOException; import javax.servlet.AsyncContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class AsyncServlet2 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { AsyncContext ctx = req.startAsync(); ctx.addListener(new SimpleAsyncListener()); ctx.addListener(new AsyncEventListener()); Thread thread = new Thread(new AsyncTask(ctx)); thread.start(); } } AsyncServlet3.java000066400000000000000000000030171420065311100371400ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onError/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.request.async.onError; import java.io.IOException; import javax.servlet.AsyncContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class AsyncServlet3 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { final AsyncContext ctx = req.startAsync(); ctx.addListener(new SimpleAsyncListener()); ctx.addListener(new AsyncEventListener()); Thread thread = new Thread(new Runnable() { @Override public void run() { ctx.dispatch("/async2"); } }); thread.start(); } } AsyncServlet4.java000066400000000000000000000014321420065311100371400ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onErrorpackage io.undertow.servlet.test.listener.request.async.onError; import java.io.IOException; import javax.servlet.AsyncContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Exception occurs during its processing. No in delegated dispatch part. * */ public class AsyncServlet4 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { AsyncContext ctx = req.startAsync(); ctx.addListener(new AsyncEventListener()); ctx.addListener(new SimpleAsyncListener(ctx)); throw new NullPointerException(); } }AsyncServlet5.java000066400000000000000000000066621420065311100371530ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onError/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.request.async.onError; import static java.util.concurrent.TimeUnit.SECONDS; import java.io.IOException; import java.time.LocalDateTime; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.ServletException; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author baranowb */ public class AsyncServlet5 extends HttpServlet { private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { final CountDownLatch latch = new CountDownLatch(1); final AsyncContext ctx = req.startAsync(); resp.setContentType("text/event-stream"); resp.setHeader("Cache-Control", "no-cache"); ctx.setTimeout(10_000L); ctx.addListener(new AsyncEventListener() { @Override public void onError(AsyncEvent event) throws IOException { super.onError(event); latch.countDown(); } }); Thread t = new Thread(new Runnable() { @Override public void run() { final ScheduledFuture scheduledFuture = executorService.scheduleAtFixedRate(new Job(ctx), 0, 1, SECONDS); try { latch.await(11, SECONDS); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } scheduledFuture.cancel(true); ctx.complete(); } }); t.start(); } private class Job implements Runnable { private final AsyncContext asyncContext; Job(AsyncContext asyncContext) { this.asyncContext = asyncContext; } @Override public void run() { try { final ServletResponse response = asyncContext.getResponse(); response.getOutputStream().print(getMessage()); response.flushBuffer(); } catch (IOException e) { e.printStackTrace(); } } private String getMessage() { return new StringBuilder("data:") .append(LocalDateTime.now()) .append("\n\n") .toString(); } } } AsyncTask.java000066400000000000000000000020301420065311100363250ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onError/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.request.async.onError; import javax.servlet.AsyncContext; public class AsyncTask implements Runnable { private final AsyncContext ctx; public AsyncTask(AsyncContext ctx) { this.ctx = ctx; } @Override public void run() { ctx.dispatch("/faulty"); } } FaultyServlet.java000066400000000000000000000022701420065311100372440ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onError/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.request.async.onError; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class FaultyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { throw new IllegalStateException(); } } SimpleAsyncListener.java000066400000000000000000000043021420065311100403660ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onError/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.request.async.onError; import io.undertow.util.StatusCodes; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; public class SimpleAsyncListener implements AsyncListener { public static final String MESSAGE = "handled by " + SimpleAsyncListener.class.getSimpleName(); private final AsyncContext ctx; public SimpleAsyncListener() { this.ctx = null; } public SimpleAsyncListener(AsyncContext ctx) { this.ctx = ctx; } @Override public void onComplete(AsyncEvent event) throws IOException { } @Override public void onTimeout(AsyncEvent event) throws IOException { } @Override public void onError(AsyncEvent event) throws IOException { ServletResponse response = event.getSuppliedResponse(); HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setContentType("text/plain"); httpResponse.setStatus(StatusCodes.OK); PrintWriter writer = httpResponse.getWriter(); writer.write(MESSAGE); writer.flush(); if (this.ctx != null) { ctx.complete(); } else { event.getAsyncContext().complete(); } } @Override public void onStartAsync(AsyncEvent event) throws IOException { } } onTimeout/000077500000000000000000000000001420065311100341245ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/asyncAsyncServlet.java000066400000000000000000000033051420065311100374120ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onTimeout/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.request.async.onTimeout; import java.io.IOException; import javax.servlet.AsyncContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Jozef Hartinger */ public class AsyncServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { AsyncContext ctx = req.startAsync(); ctx.setTimeout(100L); // make the request timeout ctx.addListener(new SimpleAsyncListener()); Thread t = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000L); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t.start(); } } NestedListenerInvocationTestCase.java000066400000000000000000000063041420065311100434100ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onTimeout/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.request.async.onTimeout; import java.io.IOException; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ListenerInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Jozef Hartinger * * @see https://issues.jboss.org/browse/UNDERTOW-81 */ @RunWith(DefaultServer.class) public class NestedListenerInvocationTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo a = new ServletInfo("asyncServlet", AsyncServlet.class) .setAsyncSupported(true) .addMapping("/async"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(NestedListenerInvocationTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlets(a) .addListener(new ListenerInfo(SimpleRequestListener.class)); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testSimpleHttpServlet() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/async"); HttpResponse response = client.execute(get); Assert.assertEquals(StatusCodes.OK, response.getStatusLine().getStatusCode()); Assert.assertFalse(SimpleRequestListener.hasNestedInvocationOccured()); } finally { client.getConnectionManager().shutdown(); } } } SimpleAsyncListener.java000066400000000000000000000030171420065311100407250ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onTimeout/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.request.async.onTimeout; import io.undertow.util.StatusCodes; import java.io.IOException; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; import javax.servlet.http.HttpServletResponse; public class SimpleAsyncListener implements AsyncListener { @Override public void onComplete(AsyncEvent event) throws IOException { } @Override public void onTimeout(AsyncEvent event) throws IOException { HttpServletResponse response = (HttpServletResponse) event.getSuppliedResponse(); response.setStatus(StatusCodes.OK); event.getAsyncContext().complete(); } @Override public void onError(AsyncEvent event) throws IOException { } @Override public void onStartAsync(AsyncEvent event) throws IOException { } } SimpleRequestListener.java000066400000000000000000000030751420065311100413040ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/request/async/onTimeout/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.request.async.onTimeout; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; public class SimpleRequestListener implements ServletRequestListener { private static final ThreadLocal IN_REQUEST = new ThreadLocal<>(); private static boolean nestedInvocationOccured; @Override public void requestInitialized(ServletRequestEvent sre) { if (IN_REQUEST.get() != null) { nestedInvocationOccured = true; } IN_REQUEST.set(Boolean.TRUE); } @Override public void requestDestroyed(ServletRequestEvent sre) { if (IN_REQUEST.get() == null) { nestedInvocationOccured = true; } IN_REQUEST.remove(); } public static boolean hasNestedInvocationOccured() { return nestedInvocationOccured; } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/servletcontext/000077500000000000000000000000001420065311100325045ustar00rootroot00000000000000CheckRolesServlet.java000066400000000000000000000034471420065311100366670ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/servletcontext/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.servletcontext; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * * @author baranowb * */ public class CheckRolesServlet extends HttpServlet { @Override public void init(final ServletConfig config) throws ServletException { super.init(config); } @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { PrintWriter writer = resp.getWriter(); for(String role:DeclareRolesServletContextListener.ROLES) { final boolean isInRole = req.isUserInRole(role); writer.write(role+":"+isInRole+"\n"); } writer.close(); } @Override protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } } DeclareRolesServletContextListener.java000066400000000000000000000023351420065311100422570ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/servletcontext/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.servletcontext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; /** * * @author baranowb * */ public class DeclareRolesServletContextListener implements ServletContextListener{ public static final String[] ROLES = new String[] {"dobby","was","here"}; @Override public void contextInitialized(ServletContextEvent sce) { System.err.println(sce.getServletContext()); sce.getServletContext().declareRoles(ROLES); } } ServletContextListenerTestCase.java000066400000000000000000000102471420065311100414270ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/servletcontext/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.servletcontext; import java.io.IOException; import java.util.Collections; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ListenerInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletContainerInitializerInfo; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.spec.ServletContextImpl; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.util.MessageServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ServletContextListenerTestCase { static DeploymentManager manager; @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServletContainerInitializer(new ServletContainerInitializerInfo(TestSci.class, Collections.>emptySet())) .addServlet( new ServletInfo("servlet", MessageServlet.class) .addMapping("/aa") ) .addListener(new ListenerInfo(ServletContextTestListener.class)); manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testServletContextInitialized() throws IOException { Assert.assertNotNull(ServletContextTestListener.servletContextInitializedEvent); } @Test public void testServletContextAttributeListener() throws IOException { ServletContextImpl sc = manager.getDeployment().getServletContext(); sc.setAttribute("test", "1"); Assert.assertNotNull(ServletContextTestListener.servletContextAttributeEvent); Assert.assertEquals(ServletContextTestListener.servletContextAttributeEvent.getName(), "test"); Assert.assertEquals(ServletContextTestListener.servletContextAttributeEvent.getValue(), "1"); sc.setAttribute("test", "2"); Assert.assertEquals(ServletContextTestListener.servletContextAttributeEvent.getName(), "test"); Assert.assertEquals(ServletContextTestListener.servletContextAttributeEvent.getValue(), "1"); sc.setAttribute("test", "3"); Assert.assertEquals(ServletContextTestListener.servletContextAttributeEvent.getName(), "test"); Assert.assertEquals(ServletContextTestListener.servletContextAttributeEvent.getValue(), "2"); sc.removeAttribute("test"); Assert.assertEquals(ServletContextTestListener.servletContextAttributeEvent.getName(), "test"); Assert.assertEquals(ServletContextTestListener.servletContextAttributeEvent.getValue(), "3"); } } ServletContextRolesTestCase.java000066400000000000000000000127541420065311100407330ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/servletcontext/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.servletcontext; import static io.undertow.util.Headers.AUTHORIZATION; import static io.undertow.util.Headers.BASIC; import static org.junit.Assert.assertEquals; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletException; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.AuthMethodConfig; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ListenerInfo; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.SecurityInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.WebResourceCollection; import io.undertow.servlet.test.security.constraint.ServletIdentityManager; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.FlexBase64; import io.undertow.util.Headers; /** * @author baranowb */ @RunWith(DefaultServer.class) public class ServletContextRolesTestCase { private static final String REALM_NAME = "Servlet_Realm-1"; static DeploymentManager manager; @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); final ServletIdentityManager identityManager = new ServletIdentityManager(); identityManager.addUser("user1", "password1", "unspecified-role"); LoginConfig loginConfig = new LoginConfig(REALM_NAME); Map props = new HashMap<>(); props.put("charset", "ISO_8859_1"); props.put("user-agent-charsets", "Chrome,UTF-8,OPR,UTF-8"); loginConfig.addFirstAuthMethod(new AuthMethodConfig("BASIC", props)); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(ServletContextRolesTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlet( new ServletInfo("servlet", CheckRolesServlet.class) .addMapping("/aa") ) .addListener(new ListenerInfo(DeclareRolesServletContextListener.class)) .setIdentityManager(identityManager) .setLoginConfig(loginConfig); builder.addPrincipalVsRoleMappings("user1", DeclareRolesServletContextListener.ROLES); builder.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection() .addUrlPattern("/*")) .addRolesAllowed(DeclareRolesServletContextListener.ROLES) .setEmptyRoleSemantic(SecurityInfo.EmptyRoleSemantic.DENY)); manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testRoles() throws Exception { final StringBuilder sb = new StringBuilder(40); sb.append("dobby:true\n") .append("was:true\n") .append("here:true\n"); testCall(sb.toString(), StandardCharsets.UTF_8, "Chrome", "user1", "password1", 200); } public void testCall( final String expectedResponse, Charset charset, String userAgent, String user, String password, int expect) throws Exception { TestHttpClient client = new TestHttpClient(); try { String url = DefaultServer.getDefaultServerURL() + "/servletContext/aa"; HttpGet get = new HttpGet(url); get = new HttpGet(url); get.addHeader(Headers.USER_AGENT_STRING, userAgent); get.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString((user + ":" + password).getBytes(charset), false)); HttpResponse result = client.execute(get); assertEquals(expect, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); if(expect == 200) { assertEquals(expectedResponse, response); } } finally { client.getConnectionManager().shutdown(); } } } ServletContextTestListener.java000066400000000000000000000061521420065311100406330ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/servletcontext/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.servletcontext; import javax.servlet.ServletContextAttributeEvent; import javax.servlet.ServletContextAttributeListener; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.ServletRequestAttributeEvent; import javax.servlet.ServletRequestAttributeListener; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; /** * @author Stuart Douglas */ public class ServletContextTestListener implements ServletContextAttributeListener, ServletContextListener, ServletRequestListener, ServletRequestAttributeListener { public static ServletContextAttributeEvent servletContextAttributeEvent; public static ServletContextEvent servletContextInitializedEvent; public static ServletContextEvent servletContextDestroyedEvent; public static ServletRequestAttributeEvent servletRequestAttributeEvent; public static ServletRequestEvent servletRequestInitializedEvent; public static ServletRequestEvent servletRequestDestroyedEvent; @Override public void attributeAdded(final ServletContextAttributeEvent event) { servletContextAttributeEvent = event; } @Override public void attributeRemoved(final ServletContextAttributeEvent event) { servletContextAttributeEvent = event; } @Override public void attributeReplaced(final ServletContextAttributeEvent event) { servletContextAttributeEvent = event; } @Override public void contextInitialized(final ServletContextEvent sce) { servletContextInitializedEvent = sce; } @Override public void contextDestroyed(final ServletContextEvent sce) { servletContextDestroyedEvent = sce; } @Override public void attributeAdded(final ServletRequestAttributeEvent srae) { servletRequestAttributeEvent = srae; } @Override public void attributeRemoved(final ServletRequestAttributeEvent srae) { servletRequestAttributeEvent = srae; } @Override public void attributeReplaced(final ServletRequestAttributeEvent srae) { servletRequestAttributeEvent = srae; } @Override public void requestDestroyed(final ServletRequestEvent sre) { servletRequestDestroyedEvent = sre; } @Override public void requestInitialized(final ServletRequestEvent sre) { servletRequestInitializedEvent = sre; } } TestSci.java000066400000000000000000000041121420065311100346440ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/servletcontext/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.servletcontext; import org.junit.Assert; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletContextAttributeEvent; import javax.servlet.ServletContextAttributeListener; import javax.servlet.ServletException; import java.util.Set; import java.util.concurrent.LinkedBlockingDeque; /** * @author Stuart Douglas */ public class TestSci implements ServletContainerInitializer { public static LinkedBlockingDeque DEQUE = new LinkedBlockingDeque<>(); @Override public void onStartup(Set> c, ServletContext ctx) throws ServletException { ctx.addListener(new DynamicListener()); ctx.setAttribute("testDL", "foo"); Assert.assertNotNull(ctx.getAttribute(ServletContext.TEMPDIR)); } public static class DynamicListener implements ServletContextAttributeListener { @Override public void attributeAdded(ServletContextAttributeEvent event) { DEQUE.add("dl add " + event.getName()); } @Override public void attributeRemoved(ServletContextAttributeEvent event) { DEQUE.add("dl remove " + event.getName()); } @Override public void attributeReplaced(ServletContextAttributeEvent event) { DEQUE.add("dl replace " + event.getName()); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/session/000077500000000000000000000000001420065311100310765ustar00rootroot00000000000000ServletSessionInvalidateWithListenerTestCase.java000066400000000000000000000060321420065311100426520ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/session/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.session; import java.io.IOException; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ListenerInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @see UNDERTOW-80 * * @author Jozef Hartinger */ @RunWith(DefaultServer.class) public class ServletSessionInvalidateWithListenerTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler path = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/listener") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("listener.war") .addListener(new ListenerInfo(SimpleSessionListener.class)) .addServlet(new ServletInfo("servlet", SessionServlet.class) .addMapping("/test")); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); path.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(path); } @Test public void testSimpleSessionUsage() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/listener/test"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); } finally { client.getConnectionManager().shutdown(); } } } SessionServlet.java000066400000000000000000000024451420065311100346570ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/session/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.session; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; public class SessionServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(true); session.setAttribute("FOO", "BAR"); session.invalidate(); } } SimpleSessionListener.java000066400000000000000000000022421420065311100361650ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/listener/session/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.listener.session; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; import org.junit.Assert; public class SimpleSessionListener implements HttpSessionListener { @Override public void sessionCreated(HttpSessionEvent arg0) { } @Override public void sessionDestroyed(HttpSessionEvent event) { Assert.assertEquals("BAR", event.getSession().getAttribute("FOO")); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/metrics/000077500000000000000000000000001420065311100272345ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/metrics/MetricTestServlet.java000066400000000000000000000026051420065311100335320ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.metrics; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Tomaz Cerar */ public class MetricTestServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { PrintWriter out = resp.getWriter(); out.print("metric"); try { Thread.sleep(5); } catch (InterruptedException e) { //we dont care } } } ServletMetricsHandlerTestCase.java000066400000000000000000000112071420065311100357260ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/metrics/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.metrics; import io.undertow.server.handlers.MetricsHandler; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.defaultservlet.DefaultServletTestCase; import io.undertow.servlet.test.defaultservlet.HelloFilter; import io.undertow.servlet.test.path.ServletPathMappingTestCase; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestResourceLoader; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.CompletionLatchHandler; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.DispatcherType; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ServletMetricsHandlerTestCase { @Test public void testMetrics() throws Exception { final TestMetricsCollector metricsCollector = new TestMetricsCollector(); CompletionLatchHandler completionLatchHandler; final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setClassLoader(ServletPathMappingTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setDeploymentName("servletContext.war") .setResourceManager(new TestResourceLoader(DefaultServletTestCase.class)); builder.addServlet(new ServletInfo("MetricTestServlet", MetricTestServlet.class) .addMapping("/path/default")); builder.addFilter(new FilterInfo("Filter", HelloFilter.class)); builder.addFilterUrlMapping("Filter", "/filterpath/*", DispatcherType.REQUEST); builder.setMetricsCollector(metricsCollector); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(completionLatchHandler = new CompletionLatchHandler(root)); HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/path/default"); TestHttpClient client = new TestHttpClient(); try { HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertTrue(HttpClientUtils.readResponse(result).contains("metric")); completionLatchHandler.await(); completionLatchHandler.reset(); MetricsHandler.MetricResult metrics = metricsCollector.getMetrics("MetricTestServlet"); Assert.assertEquals(1, metrics.getTotalRequests()); Assert.assertTrue(metrics.getMaxRequestTime() > 0); Assert.assertEquals(metrics.getMinRequestTime(), metrics.getMaxRequestTime()); Assert.assertEquals(metrics.getMaxRequestTime(), metrics.getTotalRequestTime()); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertTrue(HttpClientUtils.readResponse(result).contains("metric")); completionLatchHandler.await(); completionLatchHandler.reset(); metrics = metricsCollector.getMetrics("MetricTestServlet"); Assert.assertEquals(2, metrics.getTotalRequests()); } finally { client.getConnectionManager().shutdown(); } } } TestMetricsCollector.java000066400000000000000000000026061420065311100341410ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/metrics/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.metrics; import io.undertow.server.handlers.MetricsHandler; import io.undertow.servlet.api.MetricsCollector; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * @author Tomaz Cerar (c) 2014 Red Hat Inc. */ public class TestMetricsCollector implements MetricsCollector { private final ConcurrentMap metrics = new ConcurrentHashMap<>(); @Override public void registerMetric(String name, MetricsHandler handler) { metrics.putIfAbsent(name,handler); } public MetricsHandler.MetricResult getMetrics(String name) { return metrics.get(name).getMetrics(); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/mock/000077500000000000000000000000001420065311100265175ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/mock/MockRequestTestCase.java000066400000000000000000000370141420065311100332650ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.mock; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.spec.RequestDispatcherImpl; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.AsyncContext; import javax.servlet.DispatcherType; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.WriteListener; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpUpgradeHandler; import javax.servlet.http.Part; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.security.Principal; import java.util.Collection; import java.util.Enumeration; import java.util.Locale; import java.util.Map; /** * @author Stuart Douglas * @author Ales Justin */ @RunWith(DefaultServer.class) public class MockRequestTestCase { public static final String HELLO_WORLD = "Hello World"; private static Deployment deployment; @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("servlet", HelloServlet.class).addMapping("/aa"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(MockRequestTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlet(s); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); deployment = manager.getDeployment(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testMockHttpRequest() throws Exception { MockHttpResponse response = new MockHttpResponse(); MockHttpRequest request = new MockHttpRequest(); deployment.getServletDispatcher().dispatchMockRequest(request, response); Assert.assertEquals(HELLO_WORLD, new String(response.out.toByteArray())); } @Test public void testMockDispatch() throws Exception { MockHttpResponse response = new MockHttpResponse(); MockHttpRequest request = new MockHttpRequest(); RequestDispatcher rd = deployment.getServletContext().getRequestDispatcher("/aa"); Assert.assertNotNull(rd); Assert.assertTrue(rd instanceof RequestDispatcherImpl); RequestDispatcherImpl rdi = (RequestDispatcherImpl) rd; rdi.mock(request, response); Assert.assertEquals(HELLO_WORLD, new String(response.out.toByteArray())); } private static class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getOutputStream().write(HELLO_WORLD.getBytes()); } } private static final class MockHttpRequest implements HttpServletRequest { @Override public String getAuthType() { return null; } @Override public Cookie[] getCookies() { return new Cookie[0]; } @Override public long getDateHeader(String name) { return 0; } @Override public String getHeader(String name) { return null; } @Override public Enumeration getHeaders(String name) { return null; } @Override public Enumeration getHeaderNames() { return null; } @Override public int getIntHeader(String name) { return 0; } @Override public String getMethod() { return "GET"; } @Override public String getPathInfo() { return null; } @Override public String getPathTranslated() { return null; } @Override public String getContextPath() { return null; } @Override public String getQueryString() { return null; } @Override public String getRemoteUser() { return null; } @Override public boolean isUserInRole(String role) { return false; } @Override public Principal getUserPrincipal() { return null; } @Override public String getRequestedSessionId() { return null; } @Override public String getRequestURI() { return null; } @Override public StringBuffer getRequestURL() { return null; } @Override public String getServletPath() { return "/aa"; } @Override public HttpSession getSession(boolean create) { return null; } @Override public HttpSession getSession() { return null; } @Override public String changeSessionId() { return null; } @Override public boolean isRequestedSessionIdValid() { return false; } @Override public boolean isRequestedSessionIdFromCookie() { return false; } @Override public boolean isRequestedSessionIdFromURL() { return false; } @Override public boolean isRequestedSessionIdFromUrl() { return false; } @Override public boolean authenticate(HttpServletResponse response) throws IOException, ServletException { return false; } @Override public void login(String username, String password) throws ServletException { } @Override public void logout() throws ServletException { } @Override public Collection getParts() throws IOException, ServletException { return null; } @Override public Part getPart(String name) throws IOException, ServletException { return null; } @Override public T upgrade(Class handlerClass) throws IOException, ServletException { return null; } @Override public Object getAttribute(String name) { return null; } @Override public Enumeration getAttributeNames() { return null; } @Override public String getCharacterEncoding() { return null; } @Override public void setCharacterEncoding(String env) throws UnsupportedEncodingException { } @Override public int getContentLength() { return 0; } @Override public long getContentLengthLong() { return 0; } @Override public String getContentType() { return null; } @Override public ServletInputStream getInputStream() throws IOException { return null; } @Override public String getParameter(String name) { return null; } @Override public Enumeration getParameterNames() { return null; } @Override public String[] getParameterValues(String name) { return new String[0]; } @Override public Map getParameterMap() { return null; } @Override public String getProtocol() { return null; } @Override public String getScheme() { return "http"; } @Override public String getServerName() { return null; } @Override public int getServerPort() { return 0; } @Override public BufferedReader getReader() throws IOException { return null; } @Override public String getRemoteAddr() { return null; } @Override public String getRemoteHost() { return null; } @Override public void setAttribute(String name, Object o) { } @Override public void removeAttribute(String name) { } @Override public Locale getLocale() { return null; } @Override public Enumeration getLocales() { return null; } @Override public boolean isSecure() { return false; } @Override public RequestDispatcher getRequestDispatcher(String path) { return null; } @Override public String getRealPath(String path) { return null; } @Override public int getRemotePort() { return 0; } @Override public String getLocalName() { return null; } @Override public String getLocalAddr() { return null; } @Override public int getLocalPort() { return 0; } @Override public ServletContext getServletContext() { return null; } @Override public AsyncContext startAsync() throws IllegalStateException { return null; } @Override public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { return null; } @Override public boolean isAsyncStarted() { return false; } @Override public boolean isAsyncSupported() { return false; } @Override public AsyncContext getAsyncContext() { return null; } @Override public DispatcherType getDispatcherType() { return null; } } private static final class MockHttpResponse implements HttpServletResponse { ByteArrayOutputStream out = new ByteArrayOutputStream(); ServletOutputStream servlet = new ServletOutputStream() { @Override public boolean isReady() { return false; } @Override public void setWriteListener(WriteListener writeListener) { } @Override public void write(int b) throws IOException { out.write(b); } }; @Override public void addCookie(Cookie cookie) { } @Override public boolean containsHeader(String name) { return false; } @Override public String encodeURL(String url) { return null; } @Override public String encodeRedirectURL(String url) { return null; } @Override public String encodeUrl(String url) { return null; } @Override public String encodeRedirectUrl(String url) { return null; } @Override public void sendError(int sc, String msg) throws IOException { } @Override public void sendError(int sc) throws IOException { } @Override public void sendRedirect(String location) throws IOException { } @Override public void setDateHeader(String name, long date) { } @Override public void addDateHeader(String name, long date) { } @Override public void setHeader(String name, String value) { } @Override public void addHeader(String name, String value) { } @Override public void setIntHeader(String name, int value) { } @Override public void addIntHeader(String name, int value) { } @Override public void setStatus(int sc) { } @Override public void setStatus(int sc, String sm) { } @Override public int getStatus() { return 0; } @Override public String getHeader(String name) { return null; } @Override public Collection getHeaders(String name) { return null; } @Override public Collection getHeaderNames() { return null; } @Override public String getCharacterEncoding() { return null; } @Override public String getContentType() { return null; } @Override public ServletOutputStream getOutputStream() throws IOException { return servlet; } @Override public PrintWriter getWriter() throws IOException { return null; } @Override public void setCharacterEncoding(String charset) { } @Override public void setContentLength(int len) { } @Override public void setContentLengthLong(long len) { } @Override public void setContentType(String type) { } @Override public void setBufferSize(int size) { } @Override public int getBufferSize() { return 0; } @Override public void flushBuffer() throws IOException { } @Override public void resetBuffer() { } @Override public boolean isCommitted() { return false; } @Override public void reset() { } @Override public void setLocale(Locale loc) { } @Override public Locale getLocale() { return null; } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/multipart/000077500000000000000000000000001420065311100276075ustar00rootroot00000000000000AddMultipartServetListener.java000066400000000000000000000013421420065311100356640ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/multipartpackage io.undertow.servlet.test.multipart; import javax.servlet.MultipartConfigElement; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.ServletRegistration; /** * @author Stuart Douglas */ public class AddMultipartServetListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { ServletRegistration.Dynamic reg = sce.getServletContext().addServlet("added", new MultiPartServlet()); reg.addMapping("/added"); reg.setMultipartConfig(new MultipartConfigElement(System.getProperty("java.io.tmpdir"))); } @Override public void contextDestroyed(ServletContextEvent sce) { } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/multipart/MultiPartServlet.java000066400000000000000000000053531420065311100337460ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.multipart; import java.io.IOException; import java.io.PrintWriter; import java.util.Collection; import java.util.Enumeration; import java.util.TreeSet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; import io.undertow.util.FileUtils; /** * @author Stuart Douglas */ public class MultiPartServlet extends HttpServlet { @Override protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { try { Collection parts = req.getParts(); resp.setContentType("text/plain; charset=UTF-8"); resp.setCharacterEncoding("UTF-8"); PrintWriter writer = resp.getWriter(); writer.println("PARAMS:"); writer.println("parameter count: " + req.getParameterMap().size()); writer.println("parameter name count: " + count(req.getParameterNames())); for (Part part : parts) { writer.println("name: " + part.getName()); writer.println("filename: " + part.getSubmittedFileName()); writer.println("content-type: " + part.getContentType()); Collection headerNames = new TreeSet<>(part.getHeaderNames()); for (String header : headerNames) { writer.println(header + ": " + part.getHeader(header)); } writer.println("size: " + part.getSize()); writer.println("content: " + FileUtils.readFile(part.getInputStream())); } } catch (Exception e) { resp.getWriter().write("EXCEPTION: " + e.getClass()); } } private int count(Enumeration parameterNames) { int count = 0; while(parameterNames.hasMoreElements()) { parameterNames.nextElement(); count++; } return count; } } MultiPartTestCase.java000066400000000000000000000263501420065311100337560ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/multipart/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.multipart; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import javax.servlet.ServletContext; import javax.servlet.ServletException; import io.undertow.servlet.ServletExtension; import io.undertow.servlet.Servlets; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.LoggingExceptionHandler; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.ProxyIgnore; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntity; import org.apache.http.entity.mime.content.FileBody; import org.apache.http.entity.mime.content.StringBody; import org.jboss.logging.Logger; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import static io.undertow.servlet.Servlets.multipartConfig; import static io.undertow.servlet.Servlets.servlet; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) @ProxyIgnore public class MultiPartTestCase { @BeforeClass public static void setup() throws ServletException { DeploymentUtils.setupServlet(new ServletExtension() { @Override public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servletContext) { deploymentInfo.addListener(Servlets.listener(AddMultipartServetListener.class)); deploymentInfo.setExceptionHandler(LoggingExceptionHandler.builder().add(RuntimeException.class, "io.undertow", Logger.Level.DEBUG).build()); } }, servlet("mp0", MultiPartServlet.class) .addMapping("/0"), servlet("mp1", MultiPartServlet.class) .addMapping("/1") .setMultipartConfig(multipartConfig(null, 0, 0, 0)), servlet("mp2", MultiPartServlet.class) .addMapping("/2") .setMultipartConfig(multipartConfig(null, 0, 3, 0)), servlet("mp3", MultiPartServlet.class) .addMapping("/3") .setMultipartConfig(multipartConfig(null, 3, 0, 0))); } @Test public void testMultiPartRequestWithNoMultipartConfig() throws IOException { TestHttpClient client = new TestHttpClient(); try { String uri = DefaultServer.getDefaultServerURL() + "/servletContext/0"; HttpPost post = new HttpPost(uri); MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); entity.addPart("formValue", new StringBody("myValue", "text/plain", StandardCharsets.UTF_8)); entity.addPart("file", new FileBody(new File(MultiPartTestCase.class.getResource("uploadfile.txt").getFile()))); post.setEntity(entity); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("EXCEPTION: class java.lang.IllegalStateException", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testMultiPartRequest() throws IOException { TestHttpClient client = new TestHttpClient(); try { String uri = DefaultServer.getDefaultServerURL() + "/servletContext/1"; HttpPost post = new HttpPost(uri); MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE, null, StandardCharsets.UTF_8); entity.addPart("formValue", new StringBody("myValue", "text/plain", StandardCharsets.UTF_8)); entity.addPart("file", new FileBody(new File(MultiPartTestCase.class.getResource("uploadfile.txt").getFile()))); post.setEntity(entity); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("PARAMS:\r\n" + "parameter count: 1\r\n" + "parameter name count: 1\r\n" + "name: formValue\r\n" + "filename: null\r\n" + "content-type: null\r\n" + "Content-Disposition: form-data; name=\"formValue\"\r\n" + "size: 7\r\n" + "content: myValue\r\n" + "name: file\r\n" + "filename: uploadfile.txt\r\n" + "content-type: application/octet-stream\r\n" + "Content-Disposition: form-data; name=\"file\"; filename=\"uploadfile.txt\"\r\n" + "Content-Type: application/octet-stream\r\n" + "size: 13\r\n" + "content: file contents\r\n", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testMultiPartRequestWithAddedServlet() throws IOException { TestHttpClient client = new TestHttpClient(); try { String uri = DefaultServer.getDefaultServerURL() + "/servletContext/added"; HttpPost post = new HttpPost(uri); MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); entity.addPart("formValue", new StringBody("myValue", "text/plain", StandardCharsets.UTF_8)); entity.addPart("file", new FileBody(new File(MultiPartTestCase.class.getResource("uploadfile.txt").getFile()))); post.setEntity(entity); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("PARAMS:\r\n" + "parameter count: 1\r\n" + "parameter name count: 1\r\n" + "name: formValue\r\n" + "filename: null\r\n" + "content-type: null\r\n" + "Content-Disposition: form-data; name=\"formValue\"\r\n" + "size: 7\r\n" + "content: myValue\r\n" + "name: file\r\n" + "filename: uploadfile.txt\r\n" + "content-type: application/octet-stream\r\n" + "Content-Disposition: form-data; name=\"file\"; filename=\"uploadfile.txt\"\r\n" + "Content-Type: application/octet-stream\r\n" + "size: 13\r\n" + "content: file contents\r\n", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testMultiPartRequestToLarge() throws IOException { TestHttpClient client = new TestHttpClient(); try { String uri = DefaultServer.getDefaultServerURL() + "/servletContext/2"; HttpPost post = new HttpPost(uri); MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); entity.addPart("formValue", new StringBody("myValue", "text/plain", StandardCharsets.UTF_8)); entity.addPart("file", new FileBody(new File(MultiPartTestCase.class.getResource("uploadfile.txt").getFile()))); post.setEntity(entity); HttpResponse result = client.execute(post); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("EXCEPTION: class java.lang.IllegalStateException", response); } catch (IOException expected) { //in some environments the forced close of the read side will cause a connection reset }finally { client.getConnectionManager().shutdown(); } } @Test public void testMultiPartIndividualFileToLarge() throws IOException { TestHttpClient client = new TestHttpClient(); try { String uri = DefaultServer.getDefaultServerURL() + "/servletContext/3"; HttpPost post = new HttpPost(uri); MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); entity.addPart("formValue", new StringBody("myValue", "text/plain", StandardCharsets.UTF_8)); entity.addPart("file", new FileBody(new File(MultiPartTestCase.class.getResource("uploadfile.txt").getFile()))); post.setEntity(entity); HttpResponse result = client.execute(post); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("EXCEPTION: class java.lang.IllegalStateException", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testMultiPartRequestUtf8CharsetInPart() throws IOException { TestHttpClient client = new TestHttpClient(); try { String uri = DefaultServer.getDefaultServerURL() + "/servletContext/1"; HttpPost post = new HttpPost(uri); MultipartEntity entity = new MultipartEntity(); entity.addPart("formValue", new StringBody("myValue\u00E5", ContentType.create("text/plain", StandardCharsets.UTF_8))); post.setEntity(entity); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("PARAMS:\r\n" + "parameter count: 1\r\n" + "parameter name count: 1\r\n" + "name: formValue\r\n" + "filename: null\r\n" + "content-type: text/plain; charset=UTF-8\r\n" + "Content-Disposition: form-data; name=\"formValue\"\r\n" + "Content-Transfer-Encoding: 8bit\r\n" + "Content-Type: text/plain; charset=UTF-8\r\n" + "size: 9\r\n" + "content: myValue\u00E5\r\n", response); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/multipart/forward/000077500000000000000000000000001420065311100312535ustar00rootroot00000000000000ForwardingServlet.java000066400000000000000000000026141420065311100355110ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/multipart/forward/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.multipart.forward; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Christian Kaltepoth */ public class ForwardingServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { // access the parameter map for the first time req.getParameterMap(); req.getRequestDispatcher("/multipart").forward(req, resp); } } MultiPartCapableServlet.java000066400000000000000000000030501420065311100365730ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/multipart/forward/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.multipart.forward; import java.io.IOException; import java.io.PrintWriter; import java.util.Map.Entry; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Christian Kaltepoth */ public class MultiPartCapableServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { PrintWriter writer = resp.getWriter(); writer.println("Params:"); for (Entry entry : req.getParameterMap().entrySet()) { writer.println(entry.getKey() + ": " + entry.getValue()[0]); } } } MultiPartForwardTestCase.java000066400000000000000000000105061420065311100367430ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/multipart/forward/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.multipart.forward; import static io.undertow.servlet.Servlets.multipartConfig; import io.undertow.servlet.Servlets; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import java.io.IOException; import java.util.Arrays; import javax.servlet.ServletException; import io.undertow.util.StatusCodes; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntity; import org.apache.http.entity.mime.content.StringBody; import org.apache.http.message.BasicNameValuePair; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(DefaultServer.class) public class MultiPartForwardTestCase { @BeforeClass public static void setup() throws ServletException { DeploymentUtils.setupServlet( Servlets.servlet("MultiPartCapableServlet", MultiPartCapableServlet.class) .addMapping("/multipart") .setMultipartConfig(multipartConfig(null, 0, 0, 0)), Servlets.servlet("ForwardingServlet", ForwardingServlet.class) .addMapping("/forward")); } @Test public void urlEncodedFormRequestDirectlyToMultipartServlet() throws IOException { String response = sendRequest("/multipart", createUrlEncodedFormPostEntity()); Assert.assertEquals("Params:\r\n" + "foo: bar", response); } @Test public void urlEncodedFormRequestForwardedToMultipartServlet() throws IOException { String response = sendRequest("/forward", createUrlEncodedFormPostEntity()); Assert.assertEquals("Params:\r\n" + "foo: bar", response); } @Test public void multiPartFormRequestDirectlyToMultipartServlet() throws IOException { String response = sendRequest("/multipart", createMultiPartFormPostEntity()); Assert.assertEquals("Params:\r\n" + "foo: bar", response); } @Test public void multiPartFormRequestForwardedToMultipartServlet() throws IOException { String response = sendRequest("/forward", createMultiPartFormPostEntity()); Assert.assertEquals("Params:\r\n" + "foo: bar", response); } private String sendRequest(String path, HttpEntity postEntity) throws IOException { TestHttpClient client = new TestHttpClient(); try { String uri = DefaultServer.getDefaultServerURL() + "/servletContext" + path; HttpPost post = new HttpPost(uri); post.setEntity(postEntity); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); return HttpClientUtils.readResponse(result).trim(); } finally { client.getConnectionManager().shutdown(); } } private MultipartEntity createMultiPartFormPostEntity() throws IOException { MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); entity.addPart("foo", new StringBody("bar")); return entity; } private UrlEncodedFormEntity createUrlEncodedFormPostEntity() throws IOException { BasicNameValuePair nameValuePair = new BasicNameValuePair("foo", "bar"); return new UrlEncodedFormEntity(Arrays.asList(nameValuePair)); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/multipart/uploadfile.txt000066400000000000000000000000151420065311100324700ustar00rootroot00000000000000file contentsundertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/path/000077500000000000000000000000001420065311100265225ustar00rootroot00000000000000FilterPathMappingTestCase.java000066400000000000000000000272221420065311100343250ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/path/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.path; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class FilterPathMappingTestCase { @Test public void testBasicFilterMappings() throws IOException, ServletException { DeploymentInfo builder = new DeploymentInfo(); final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); builder.addServlet(new ServletInfo("/a/*", PathMappingServlet.class) .addMapping("/a/*")); builder.addServlet(new ServletInfo("/aa", PathMappingServlet.class) .addMapping("/aa")); builder.addServlet(new ServletInfo("/", PathMappingServlet.class) .addMapping("/")); builder.addServlet(new ServletInfo("contextRoot", PathMappingServlet.class) .addMapping("")); builder.addServlet(new ServletInfo("/myservlet/*", PathMappingServlet.class) .addMapping("/myservlet/*")); builder.addServlet(new ServletInfo("*.jsp", PathMappingServlet.class) .addMapping("*.jsp")); builder.addServlet(new ServletInfo("/hello/*", PathMappingServlet.class) .addMapping("/hello/*")); builder.addServlet(new ServletInfo("/test/*", PathMappingServlet.class) .addMapping("/test/*")); builder.addServlet(new ServletInfo("/test2", PathMappingServlet.class) .addMapping("/test2")); builder.addFilter(new FilterInfo("/*", PathFilter.class)); builder.addFilterUrlMapping("/*", "/*", DispatcherType.REQUEST); //non standard, but we still support it builder.addFilter(new FilterInfo("*", PathFilter.class)); builder.addFilterUrlMapping("*", "*", DispatcherType.REQUEST); builder.addFilter(new FilterInfo("/a/*", PathFilter.class)); builder.addFilterUrlMapping("/a/*", "/a/*", DispatcherType.REQUEST); builder.addFilter(new FilterInfo("/aa", PathFilter.class)); builder.addFilterUrlMapping("/aa", "/aa", DispatcherType.REQUEST); builder.addFilter(new FilterInfo("*.bop", PathFilter.class)); builder.addFilterUrlMapping("*.bop", "*.bop", DispatcherType.REQUEST); builder.addFilter(new FilterInfo("/myservlet/myfilter/*", PathFilter.class)); builder.addFilterUrlMapping("/myservlet/myfilter/*", "/myservlet/myfilter/*", DispatcherType.REQUEST); builder.addFilter(new FilterInfo("/myfilter/*", PathFilter.class)); builder.addFilterUrlMapping("/myfilter/*", "/myfilter/*", DispatcherType.REQUEST); builder.addFilter(new FilterInfo("contextRoot", PathFilter.class)); builder.addFilterServletNameMapping("contextRoot", "contextRoot", DispatcherType.REQUEST); builder.addFilter(new FilterInfo("defaultName", PathFilter.class)); builder.addFilterServletNameMapping("defaultName", "/", DispatcherType.REQUEST); builder.addFilter(new FilterInfo("/helloworld/index.html", PathFilter.class)); builder.addFilterUrlMapping("/helloworld/index.html", "/helloworld/index.html", DispatcherType.REQUEST); builder.addFilter(new FilterInfo("/test", PathFilter.class)); builder.addFilterUrlMapping("/test", "/test", DispatcherType.REQUEST); builder.addFilter(new FilterInfo("/test2/*", PathFilter.class)); builder.addFilterUrlMapping("/test2/*", "/test2/*", DispatcherType.REQUEST); builder.addFilter(new FilterInfo("allByName", PathFilter.class)); builder.addFilterServletNameMapping("allByName", "*", DispatcherType.REQUEST); builder.setClassIntrospecter(TestClassIntrospector.INSTANCE) .setClassLoader(FilterPathMappingTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setDeploymentName("servletContext.war"); final DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); TestHttpClient client = new TestHttpClient(); try { runTest(client, "test", "/test/* - /test - null", "/*", "*", "/test", "allByName"); runTest(client, "test2", "/test2 - /test2 - null", "/*", "*", "/test2/*", "allByName"); runTest(client, "aa", "/aa - /aa - null", "/*", "*", "/aa", "allByName"); runTest(client, "a/c", "/a/* - /a - /c", "/*", "*", "/a/*", "allByName"); runTest(client, "a", "/a/* - /a - null", "/*", "*", "/a/*", "allByName"); runTest(client, "aa/b", "/ - /aa/b - null", "/*", "*", "defaultName", "allByName"); runTest(client, "a/b/c/d", "/a/* - /a - /b/c/d", "/*", "*", "/a/*", "allByName"); runTest(client, "defaultStuff", "/ - /defaultStuff - null", "/*", "*", "defaultName", "allByName"); runTest(client, "", "contextRoot - / - null", "/*", "*", "contextRoot", "allByName"); runTest(client, "yyyy.bop", "/ - /yyyy.bop - null", "/*", "*", "*.bop", "defaultName", "allByName"); runTest(client, "a/yyyy.bop", "/a/* - /a - /yyyy.bop", "/*", "*", "*.bop", "/a/*", "allByName"); runTest(client, "myservlet/myfilter/file.dat", "/myservlet/* - /myservlet - /myfilter/file.dat", "/*", "*", "/myservlet/myfilter/*", "allByName"); runTest(client, "myservlet/myfilter/file.jsp", "/myservlet/* - /myservlet - /myfilter/file.jsp", "/*", "*", "/myservlet/myfilter/*", "allByName"); runTest(client, "otherservlet/myfilter/file.jsp", "*.jsp - /otherservlet/myfilter/file.jsp - null", "/*", "*", "allByName"); runTest(client, "myfilter/file.jsp", "*.jsp - /myfilter/file.jsp - null", "/*", "*", "/myfilter/*", "allByName"); runTest(client, "helloworld/index.html", "/ - /helloworld/index.html - null", "/*", "*", "/helloworld/index.html", "defaultName", "allByName"); } finally { client.getConnectionManager().shutdown(); } } @Test public void testExtensionMatchServletWithGlobalFilter() throws IOException, ServletException { DeploymentInfo builder = new DeploymentInfo(); final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); builder.addServlet(new ServletInfo("*.jsp", PathMappingServlet.class) .addMapping("*.jsp")); builder.addFilter(new FilterInfo("/*", PathFilter.class)); builder.addFilterUrlMapping("/*", "/*", DispatcherType.REQUEST); builder.setClassIntrospecter(TestClassIntrospector.INSTANCE) .setClassLoader(FilterPathMappingTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setDeploymentName("servletContext.war"); final DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); TestHttpClient client = new TestHttpClient(); try { runTest(client, "aa.jsp", "*.jsp - /aa.jsp - null", "/*"); } finally { client.getConnectionManager().shutdown(); } } @Test public void test_WFLY_1935() throws IOException, ServletException { DeploymentInfo builder = new DeploymentInfo(); final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); builder.addServlet(new ServletInfo("*.a", PathMappingServlet.class) .addMapping("*.a")); builder.addFilter(new FilterInfo("/*", PathFilter.class)); builder.addFilterUrlMapping("/*", "/*", DispatcherType.REQUEST); //non standard, but we still support it builder.addFilter(new FilterInfo("/SimpleServlet.a", PathFilter.class)); builder.addFilterUrlMapping("/SimpleServlet.a", "/SimpleServlet.a", DispatcherType.REQUEST); builder.setClassIntrospecter(TestClassIntrospector.INSTANCE) .setClassLoader(FilterPathMappingTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setDeploymentName("servletContext.war"); final DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); TestHttpClient client = new TestHttpClient(); try { runTest(client, "SimpleServlet.a", "*.a - /SimpleServlet.a - null", "/*", "/SimpleServlet.a"); } finally { client.getConnectionManager().shutdown(); } } private void runTest(final TestHttpClient client, final String path, final String expected, final String... headers) throws IOException { final HttpGet get; final HttpResponse result; final String response; get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/" + path); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); requireHeaders(result, headers); response = HttpClientUtils.readResponse(result); Assert.assertEquals(expected, response); } private void requireHeaders(final HttpResponse result, final String... headers) { final Header[] resultHeaders = result.getAllHeaders(); final List
    realResultHeaders = new ArrayList<>(); for (Header header : resultHeaders) { if (header.getName().startsWith("filter")) { realResultHeaders.add(header); } } final Set found = new HashSet<>(Arrays.asList(headers)); for (Header header : realResultHeaders) { if (!found.remove(header.getValue())) { Assert.fail("Found unexpected header " + header.getValue()); } } if (!found.isEmpty()) { Assert.fail("header(s) not found " + found); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/path/GetMappingServlet.java000066400000000000000000000031301420065311100327620ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.path; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletMapping; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author Stuart Douglas */ public class GetMappingServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { HttpServletMapping mapping = request.getHttpServletMapping(); response.getWriter() .append("Mapping match:") .append(mapping.getMappingMatch().name()) .append("\nMatch value:") .append(mapping.getMatchValue()) .append("\nPattern:") .append(mapping.getPattern()) .append("\nServlet:") .append(mapping.getServletName()); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/path/MappingTestCase.java000066400000000000000000000103061420065311100324140ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.path; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import java.io.IOException; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class MappingTestCase { @BeforeClass public static void setup() throws ServletException { DeploymentUtils.setupServlet( new ServletInfo("path", GetMappingServlet.class) .addMapping("/path/*") .addMapping("/exact") .addMapping("*.ext") .addMapping("") .addMapping("/")); } @Test public void testGetMapping() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/path/foo"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("Mapping match:PATH\n" + "Match value:foo\n" + "Pattern:/path/*\nServlet:path", response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/foo.ext"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("Mapping match:EXTENSION\n" + "Match value:foo\n" + "Pattern:*.ext\nServlet:path", response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("Mapping match:CONTEXT_ROOT\n" + "Match value:\n" + "Pattern:\nServlet:path", response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/doesnotexist"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("Mapping match:DEFAULT\n" + "Match value:\n" + "Pattern:/\nServlet:path", response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/exact"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("Mapping match:EXACT\n" + "Match value:exact\n" + "Pattern:/exact\nServlet:path", response); } finally { client.getConnectionManager().shutdown(); } } } MultipleMatchingMappingTestCase.java000066400000000000000000000066411420065311100355330ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/path/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.path; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import javax.servlet.http.MappingMatch; import java.io.IOException; /** * Test cases for UNDERTOW-1844. * * @author Carter Kozak */ @RunWith(DefaultServer.class) public class MultipleMatchingMappingTestCase { @BeforeClass public static void setup() throws ServletException { DeploymentUtils.setupServlet( new ServletInfo("path", GetMappingServlet.class) .addMapping("/path/*") .addMapping("/*") // This extension prefix is impossible to reach due to the '/*' path match. .addMapping("*.ext")); } @Test public void testMatchesPathAndExtension1() { doTest("/foo.ext", MappingMatch.PATH, "foo.ext", "/*", "path"); } @Test public void testMatchesPathAndExtension2() { doTest("/other/foo.ext", MappingMatch.PATH, "other/foo.ext", "/*", "path"); } @Test public void testMatchesPathAndExtension3() { doTest("/path/foo.ext", MappingMatch.PATH, "foo.ext", "/path/*", "path"); } private static void doTest( // Input request path excluding the servlet context path String path, // Expected HttpServletMapping result values MappingMatch mappingMatch, String matchValue, String pattern, String servletName) { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext" + path); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); String expected = String.format("Mapping match:%s\nMatch value:%s\nPattern:%s\nServlet:%s", mappingMatch.name(), matchValue, pattern, servletName); Assert.assertEquals(expected, response); } catch (IOException e) { throw new AssertionError(e); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/path/PathFilter.java000066400000000000000000000033261420065311100314330ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.path; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class PathFilter implements Filter { private FilterConfig filterConfig; @Override public void init(final FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; } @Override public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { HttpServletResponse resp = (HttpServletResponse) response; resp.addHeader("filter" + filterConfig.getFilterName().replace("/", "-"), filterConfig.getFilterName()); chain.doFilter(request, response); } @Override public void destroy() { } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/path/PathMappingServlet.java000066400000000000000000000025561420065311100331520ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.path; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class PathMappingServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { PrintWriter writer = resp.getWriter(); writer.write(getServletName() + " - " + req.getServletPath() + " - " + req.getPathInfo()); writer.close(); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/path/RealPathServlet.java000066400000000000000000000027511420065311100324370ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.path; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Tomaz Cerar */ public class RealPathServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { PrintWriter writer = resp.getWriter(); if (req.getPathInfo().equals("/real-path")) { writer.write(req.getRealPath("file.txt")); } else if (req.getPathInfo().equals("/file.txt")) { writer.write(req.getPathTranslated()); } writer.close(); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/path/RealPathTestCase.java000066400000000000000000000071171420065311100325270ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.path; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestResourceLoader; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.nio.file.Paths; /** * @author Tomaz Cerar */ @RunWith(DefaultServer.class) public class RealPathTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo realPathServlet = new ServletInfo("real path servlet", RealPathServlet.class) .addMapping("/path/*"); DeploymentInfo builder = new DeploymentInfo() .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setClassLoader(RealPathTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setDeploymentName("servletContext.war") .setResourceManager(new TestResourceLoader(RealPathTestCase.class)) .addServlets(realPathServlet); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testRealPath() throws Exception { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/path/real-path"); HttpResponse result = new TestHttpClient().execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals(Paths.get(RealPathTestCase.class.getResource("file.txt").toURI()).toString(), response); } @Test public void testPathTranslated() throws Exception { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/path/file.txt"); HttpResponse result = new TestHttpClient().execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals(Paths.get(RealPathTestCase.class.getResource("file.txt").toURI()).toString(), response); } } ServletPathMappingTestCase.java000066400000000000000000000141101420065311100345140ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/path/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.path; import java.io.IOException; import javax.servlet.ServletException; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ServletPathMappingTestCase { @BeforeClass public static void setup() throws ServletException { DeploymentUtils.setupServlet( new ServletInfo("/a/*", PathMappingServlet.class) .addMapping("/a/*"), new ServletInfo("/aa", PathMappingServlet.class) .addMapping("/aa"), new ServletInfo("/aa/*", PathMappingServlet.class) .addMapping("/aa/*"), new ServletInfo("/a/b/*", PathMappingServlet.class) .addMapping("/a/b/*"), new ServletInfo("/", PathMappingServlet.class) .addMapping("/"), new ServletInfo("*.jsp", PathMappingServlet.class) .addMapping("*.jsp"), new ServletInfo("contextRoot", PathMappingServlet.class) .addMapping(""), new ServletInfo("foo", PathMappingServlet.class) .addMapping("foo.html")); } @Test public void testSimpleHttpServlet() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/aa"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("/aa - /aa - null", response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/a/c"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("/a/* - /a - /c", response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/aa/b"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("/aa/* - /aa - /b", response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/a/b/c/d"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("/a/b/* - /a/b - /c/d", response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/a/b"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("/a/b/* - /a/b - null", response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/defaultStuff"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("/ - /defaultStuff - null", response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("contextRoot - / - null", response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/bob.jsp"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("*.jsp - /bob.jsp - null", response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/a/bob.jsp"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("/a/* - /a - /bob.jsp", response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/foo.html"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("foo - /foo.html - null", response); } finally { client.getConnectionManager().shutdown(); } } } ServletSpecExampleTestCase.java000066400000000000000000000073341420065311100345240ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/pathpackage io.undertow.servlet.test.path; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import javax.servlet.http.MappingMatch; import java.io.IOException; /** * Test cases for the servlet mapping examples in section 12.2.2 of the * Servlet 4.0 specification. * * @author Carter Kozak */ @RunWith(DefaultServer.class) public class ServletSpecExampleTestCase { @BeforeClass public static void setup() throws ServletException { // Servlet 4.0 table 12-1 Example Set of Maps DeploymentUtils.setupServlet( new ServletInfo("servlet1", GetMappingServlet.class) .addMapping("/foo/bar/*"), new ServletInfo("servlet2", GetMappingServlet.class) .addMapping("/baz/*"), new ServletInfo("servlet3", GetMappingServlet.class) .addMapping("/catalog"), new ServletInfo("servlet4", GetMappingServlet.class) .addMapping("*.bop"), new ServletInfo("default", GetMappingServlet.class)); } @Test public void testOne() { doTest("/foo/bar/index.html", MappingMatch.PATH, "index.html", "/foo/bar/*", "servlet1"); } @Test public void testTwo() { doTest("/foo/bar/index.bop", MappingMatch.PATH, "index.bop", "/foo/bar/*", "servlet1"); } @Test public void testThree() { doTest("/baz", MappingMatch.PATH, "", "/baz/*", "servlet2"); } @Test public void testFour() { doTest("/baz/index.html", MappingMatch.PATH, "index.html", "/baz/*", "servlet2"); } @Test public void testFive() { doTest("/catalog", MappingMatch.EXACT, "catalog", "/catalog", "servlet3"); } @Test public void testSix() { doTest("/catalog/index.html", MappingMatch.DEFAULT, "", "/", "default"); } @Test public void testSeven() { doTest("/catalog/racecar.bop", MappingMatch.EXTENSION, "catalog/racecar", "*.bop", "servlet4"); } @Test public void testEight() { doTest("/index.bop", MappingMatch.EXTENSION, "index", "*.bop", "servlet4"); } private static void doTest( // Input request path excluding the servlet context path String path, // Expected HttpServletMapping result values MappingMatch mappingMatch, String matchValue, String pattern, String servletName) { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext" + path); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); String expected = String.format("Mapping match:%s\nMatch value:%s\nPattern:%s\nServlet:%s", mappingMatch.name(), matchValue, pattern, servletName); Assert.assertEquals(expected, response); } catch (IOException e) { throw new AssertionError(e); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/path/file.txt000066400000000000000000000000211420065311100301730ustar00rootroot00000000000000some file contentundertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/proprietry/000077500000000000000000000000001420065311100300055ustar00rootroot00000000000000BypassServletTestCase.java000066400000000000000000000123371420065311100350410ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/proprietry/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.proprietry; import java.io.IOException; import javax.servlet.ServletException; import io.undertow.io.IoCallback; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ListenerInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.util.MessageServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestListener; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class BypassServletTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlet( new ServletInfo("servlet", MessageServlet.class) .addMapping("/") .addInitParam(MessageServlet.MESSAGE, "This is a servlet") ) .addListener(new ListenerInfo(TestListener.class)) .addInitialHandlerChainWrapper(new HandlerWrapper() { @Override public HttpHandler wrap(final HttpHandler handler) { return new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { if (exchange.getRelativePath().equals("/async")) { exchange.getResponseSender().send("This is not a servlet", IoCallback.END_EXCHANGE); } else { handler.handleRequest(exchange); } } }; } }); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testServletRequest() throws IOException { TestListener.init(2); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/aa"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("This is a servlet", response); Assert.assertArrayEquals(new String[]{"created REQUEST", "destroyed REQUEST"}, TestListener.results().toArray()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testServletBypass() throws IOException { TestListener.init(0); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/async"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("This is not a servlet", response); Assert.assertArrayEquals(new String[0], TestListener.results().toArray()); } finally { client.getConnectionManager().shutdown(); } } } ExchangeCompletionTestCase.java000066400000000000000000000136101420065311100360020ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/proprietrypackage io.undertow.servlet.test.proprietry; import static org.junit.Assert.assertEquals; import io.undertow.server.ExchangeCompletionListener; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.TestHttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import java.io.IOException; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; /** * @see UNDERTOW-1573 */ @RunWith(DefaultServer.class) public class ExchangeCompletionTestCase { private static final String AN_ATTRIBUTE = "an.attribute"; private static final String A_VALUE = "a.value"; private static final BlockingQueue completedExchangeAttributes = new LinkedBlockingQueue<>(); @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlet( new ServletInfo("servlet", IgnoresRequestAndSetsAttributeServlet.class) .addMapping("/sync") .addInitParam(IgnoresRequestAndSetsAttributeServlet.ATTRIBUTE_KEY, AN_ATTRIBUTE) .addInitParam(IgnoresRequestAndSetsAttributeServlet.ATTRIBUTE_VALUE, A_VALUE)) .addServlet( new ServletInfo("asyncservlet", IgnoresRequestAndSetsAttributeAsyncServlet.class) .addMapping("/async") .addInitParam(IgnoresRequestAndSetsAttributeServlet.ATTRIBUTE_KEY, AN_ATTRIBUTE) .addInitParam(IgnoresRequestAndSetsAttributeServlet.ATTRIBUTE_VALUE, A_VALUE) .setAsyncSupported(true)) .addInitialHandlerChainWrapper(new HandlerWrapper() { @Override public HttpHandler wrap(final HttpHandler handler) { return new HttpHandler() { @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { exchange.addExchangeCompleteListener(new ExchangeCompletionListener() { @Override public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); if (context != null) { Object result = context.getServletRequest().getAttribute(AN_ATTRIBUTE); if (result instanceof String) { completedExchangeAttributes.add((String) result); } } nextListener.proceed(); } }); handler.handleRequest(exchange); } }; } }); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Before public void clearLoggedAttributes() { completedExchangeAttributes.clear(); } @Test public void exchangeCompletionListenersSeeRequestAttributesEvenIfRequestBodyIsNotRead() throws IOException, InterruptedException { TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext/sync"); post.setEntity(new StringEntity("some body that isn't read")); client.execute(post); assertEquals(A_VALUE, completedExchangeAttributes.poll(1, TimeUnit.SECONDS)); } finally { client.getConnectionManager().shutdown(); } } @Test public void exchangeCompletionListenersSeeRequestAttributesEvenIfRequestBodyIsNotReadAsync() throws IOException, InterruptedException { TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext/async"); post.setEntity(new StringEntity("some body that isn't read")); client.execute(post); assertEquals(A_VALUE, completedExchangeAttributes.poll(1, TimeUnit.SECONDS)); } finally { client.getConnectionManager().shutdown(); } } } IgnoresRequestAndSetsAttributeAsyncServlet.java000066400000000000000000000032211420065311100412570ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/proprietrypackage io.undertow.servlet.test.proprietry; import javax.servlet.AsyncContext; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class IgnoresRequestAndSetsAttributeAsyncServlet extends HttpServlet { public static final String ATTRIBUTE_KEY = IgnoresRequestAndSetsAttributeServlet.ATTRIBUTE_KEY; public static final String ATTRIBUTE_VALUE = IgnoresRequestAndSetsAttributeServlet.ATTRIBUTE_VALUE; private String attributeKey; private String attributeValue; @Override public void init(final ServletConfig config) throws ServletException { super.init(config); attributeKey = config.getInitParameter(ATTRIBUTE_KEY); attributeValue = config.getInitParameter(ATTRIBUTE_VALUE); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setAttribute(attributeKey, attributeValue); final AsyncContext ctx = req.startAsync(); Thread t = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(100); ctx.complete(); } catch (Exception e) { throw new RuntimeException(e); } } }); t.start(); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } } IgnoresRequestAndSetsAttributeServlet.java000066400000000000000000000022141420065311100402620ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/proprietrypackage io.undertow.servlet.test.proprietry; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class IgnoresRequestAndSetsAttributeServlet extends HttpServlet { public static final String ATTRIBUTE_KEY = "attributeKey"; public static final String ATTRIBUTE_VALUE = "attributeValue"; private String attributeKey; private String attributeValue; @Override public void init(final ServletConfig config) throws ServletException { super.init(config); attributeKey = config.getInitParameter(ATTRIBUTE_KEY); attributeValue = config.getInitParameter(ATTRIBUTE_VALUE); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setAttribute(attributeKey, attributeValue); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } } TransferTestCase.java000066400000000000000000000072311420065311100340140ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/proprietry/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.proprietry; import java.io.DataInputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.util.TXServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestListener; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Jason T. Greene */ @RunWith(DefaultServer.class) public class TransferTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlet( new ServletInfo("servlet", TXServlet.class) .addMapping("/") ); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testServletRequest() throws Exception { TestListener.init(2); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/aa"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final byte[] response = HttpClientUtils.readRawResponse(result); Path file = Paths.get(TXServlet.class.getResource(TXServlet.class.getSimpleName() + ".class").toURI()); byte[] expected = new byte[(int) Files.size(file)]; DataInputStream dataInputStream = new DataInputStream(Files.newInputStream(file)); dataInputStream.readFully(expected); dataInputStream.close(); Assert.assertArrayEquals(expected, response); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/push/000077500000000000000000000000001420065311100265455ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/push/PushPromisesTestCase.java000066400000000000000000000233421420065311100335110ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.push; import io.undertow.UndertowOptions; import io.undertow.client.ClientCallback; import io.undertow.client.ClientConnection; import io.undertow.client.ClientExchange; import io.undertow.client.ClientRequest; import io.undertow.client.ClientResponse; import io.undertow.client.PushCallback; import io.undertow.client.UndertowClient; import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.server.OpenListener; import io.undertow.server.handlers.PathHandler; import io.undertow.server.protocol.http.AlpnOpenListener; import io.undertow.server.protocol.http2.Http2OpenListener; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.ProxyIgnore; import io.undertow.util.AttachmentKey; import io.undertow.util.Headers; import io.undertow.util.Methods; import io.undertow.util.SingleByteStreamSinkConduit; import io.undertow.util.SingleByteStreamSourceConduit; import io.undertow.util.StatusCodes; import io.undertow.util.StringReadChannelListener; import java.io.IOException; import java.net.URI; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.IoUtils; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.StreamConnection; import org.xnio.Xnio; import org.xnio.XnioWorker; /** *

    Test that checks that push promises are returned and double promises * are avoid (a resource sent as a promise trigers another promise).

    * * @author rmartinc */ @RunWith(DefaultServer.class) @ProxyIgnore public class PushPromisesTestCase { private static final AttachmentKey RESPONSE_BODY = AttachmentKey.create(String.class); private static OpenListener openListener; private static ChannelListener acceptListener; private static XnioWorker worker; private static ChannelListener wrapOpenListener(final ChannelListener listener) { return (StreamConnection channel) -> { channel.getSinkChannel().setConduit(new SingleByteStreamSinkConduit(channel.getSinkChannel().getConduit(), 10000)); channel.getSourceChannel().setConduit(new SingleByteStreamSourceConduit(channel.getSourceChannel().getConduit(), 10000)); listener.handleEvent(channel); }; } @BeforeClass public static void setup() throws Exception { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("servlet", PushServlet.class) .addMappings("/index.html", "/resources/*"); DeploymentInfo info = new DeploymentInfo() .setClassLoader(PushPromisesTestCase.class.getClassLoader()) .setContextPath("/push-example") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("push-example.war") .addServlet(s); DeploymentManager manager = container.addDeployment(info); manager.deploy(); root.addPrefixPath(info.getContextPath(), manager.start()); openListener = new Http2OpenListener(DefaultServer.getBufferPool(), OptionMap.create(UndertowOptions.ENABLE_HTTP2, true, UndertowOptions.HTTP2_PADDING_SIZE, 10)); acceptListener = ChannelListeners.openListenerAdapter(wrapOpenListener(new AlpnOpenListener(DefaultServer.getBufferPool()).addProtocol(Http2OpenListener.HTTP2, (io.undertow.server.DelegateOpenListener) openListener, 10))); openListener.setRootHandler(root); DefaultServer.startSSLServer(OptionMap.EMPTY, acceptListener); final Xnio xnio = Xnio.getInstance(); final XnioWorker xnioWorker = xnio.createWorker(null, OptionMap.builder() .set(Options.WORKER_IO_THREADS, 8) .set(Options.TCP_NODELAY, true) .set(Options.KEEP_ALIVE, true) .set(Options.WORKER_NAME, "Client").getMap()); worker = xnioWorker; } @AfterClass public static void cleanUp() throws Exception { openListener.closeConnections(); DefaultServer.stopSSLServer(); } private PushCallback createPushCallback(final Map responses, final CountDownLatch latch) { return new PushCallback() { @Override public boolean handlePush(ClientExchange originalRequest, ClientExchange pushedRequest) { pushedRequest.setResponseListener(new ResponseListener(responses, latch)); return true; } }; } private ClientCallback createClientCallback(final Map responses, final CountDownLatch latch) { return new ClientCallback() { @Override public void completed(final ClientExchange result) { result.setResponseListener(new ResponseListener(responses, latch)); result.setPushHandler(createPushCallback(responses, latch)); } @Override public void failed(IOException e) { e.printStackTrace(); latch.countDown(); } }; } private static class ResponseListener implements ClientCallback { private final Map responses; private final CountDownLatch latch; ResponseListener(Map responses, CountDownLatch latch) { this.responses = responses; this.latch = latch; } @Override public void completed(final ClientExchange result) { responses.put(result.getRequest().getPath(), result.getResponse()); new StringReadChannelListener(result.getConnection().getBufferPool()) { @Override protected void stringDone(String string) { result.getResponse().putAttachment(RESPONSE_BODY, string); latch.countDown(); } @Override protected void error(IOException e) { e.printStackTrace(); latch.countDown(); } }.setup(result.getResponseChannel()); } @Override public void failed(IOException e) { e.printStackTrace(); latch.countDown(); } } @Test public void testPushPromises() throws Exception { URI uri = new URI(DefaultServer.getDefaultServerSSLAddress()); final UndertowClient client = UndertowClient.getInstance(); final Map responses = new ConcurrentHashMap<>(); final CountDownLatch latch = new CountDownLatch(3); final ClientConnection connection = client.connect(uri, worker, new UndertowXnioSsl(worker.getXnio(), OptionMap.EMPTY, DefaultServer.getClientSSLContext()), DefaultServer.getBufferPool(), OptionMap.create(UndertowOptions.ENABLE_HTTP2, true)) .get(); try { connection.getIoThread().execute(new Runnable() { @Override public void run() { final ClientRequest request = new ClientRequest().setMethod(Methods.GET).setPath("/push-example/index.html"); request.getRequestHeaders().put(Headers.HOST, DefaultServer.getHostAddress()); connection.sendRequest(request, createClientCallback(responses, latch)); } }); latch.await(10, TimeUnit.SECONDS); Assert.assertEquals(3, responses.size()); Assert.assertTrue(responses.containsKey("/push-example/index.html")); Assert.assertEquals(StatusCodes.OK, responses.get("/push-example/index.html").getResponseCode()); Assert.assertNotNull(responses.get("/push-example/index.html").getAttachment(RESPONSE_BODY)); Assert.assertTrue(responses.containsKey("/push-example/resources/one.js")); Assert.assertEquals(StatusCodes.OK, responses.get("/push-example/resources/one.js").getResponseCode()); Assert.assertNotNull(responses.get("/push-example/resources/one.js").getAttachment(RESPONSE_BODY)); Assert.assertTrue(responses.containsKey("/push-example/resources/one.css")); Assert.assertEquals(StatusCodes.OK, responses.get("/push-example/resources/one.css").getResponseCode()); Assert.assertNotNull(responses.get("/push-example/resources/one.css").getAttachment(RESPONSE_BODY)); } finally { IoUtils.safeClose(connection); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/push/PushServlet.java000066400000000000000000000077141420065311100317050ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.push; import java.io.IOException; import java.util.Base64; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.PushBuilder; /** *

    Simple servlet that pushes resources for index.html and *.css. The idea * is that double promises are not sent as they are forbidden by the spec.

    * * @author rmartinc */ public class PushServlet extends HttpServlet { // A simple blue pixel in PNG format private static final String PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { if (request.getServletPath().endsWith(".html") || request.getPathInfo().endsWith(".html")) { PushBuilder pushBuilder = request.newPushBuilder(); if (pushBuilder != null) { // pushing css and js in advance pushBuilder.path("resources/one.css").push(); pushBuilder.path("resources/one.js").push(); } try (ServletOutputStream out = response.getOutputStream()) { out.println(""); out.println(" "); out.println(" "); out.println(" "); out.println(" "); out.println(" "); out.println(" PUSH PROMISES"); out.println(" "); out.println(""); } } else if (request.getPathInfo().endsWith(".css")) { PushBuilder pushBuilder = request.newPushBuilder(); if (pushBuilder != null) { // pushing images in advance pushBuilder.path("resources/one.png").push(); } response.setContentType("text/css"); try (ServletOutputStream out = response.getOutputStream()) { out.println("body, html {"); out.println(" height: 100%;"); out.println(" margin: 0;"); out.println(" background-image: url(\"one.png\");"); out.println(" background-repeat: repeat;"); out.println("}"); } } else if (request.getPathInfo().endsWith(".js")) { response.setContentType("application/javascript"); try (ServletOutputStream out = response.getOutputStream()) { out.println("console.log('loading js file ' + location.pathname);"); } } else if (request.getPathInfo().endsWith(".png")) { byte[] bytes = Base64.getDecoder().decode(PNG_BASE64); response.setContentType("image/png"); try (ServletOutputStream out = response.getOutputStream()) { out.write(bytes); } } else { throw new ServletException("Invalid request"); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/redirect/000077500000000000000000000000001420065311100273675ustar00rootroot00000000000000RedirectEncodedServlet.java000066400000000000000000000032441420065311100345460ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/redirect/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.redirect; import java.io.IOException; import java.net.URLEncoder; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * * @author baranowb * @author Emond Papegaaij * */ public class RedirectEncodedServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (req.getRequestURI().contains("/invalid-")) { if (req.getRequestURI().contains("subpath")) { resp.getWriter().write(req.getRequestURI()); } else { resp.sendRedirect(resp.encodeRedirectURL("./subpath")); } } else { final String x = "./" + URLEncoder.encode("invalid-[123]", "UTF-8") + "/main"; resp.sendRedirect(resp.encodeRedirectURL(x)); } } } RedirectEncodedToSubPathTestCase.java000066400000000000000000000066351420065311100364360ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/redirect/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.redirect; import static io.undertow.servlet.Servlets.servlet; import javax.servlet.ServletException; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; /** * Test redirection to subpath of percent encoded URL * @author baranowb * */ @RunWith(DefaultServer.class) public class RedirectEncodedToSubPathTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler pathHandler = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(RedirectEncodedToSubPathTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlets( servlet("redirect", RedirectEncodedServlet.class) .addMapping("/redirect/*")); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); try { pathHandler.addPrefixPath(builder.getContextPath(), manager.start()); } catch (ServletException e) { throw new RuntimeException(e); } DefaultServer.setRootHandler(pathHandler); } @Test public void testServletRedirect() throws Exception { //test redirects runtest("/servletContext/redirect/", "/servletContext/redirect/invalid-%5B123%5D/subpath"); } private void runtest(String request, String expectedBody) throws Exception { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + request); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(expectedBody, response); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/request/000077500000000000000000000000001420065311100272565ustar00rootroot00000000000000ExecutorPerServletTestCase.java000066400000000000000000000113051420065311100353100ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/request/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.request; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import javax.servlet.ServletException; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ExecutorPerServletTestCase { private ExecutorService executorService; public static final int NUM_THREADS = 10; public static final int NUM_REQUESTS = 100; @Before public void setup() throws ServletException { DeploymentUtils.setupServlet( new ServletInfo("racey", RaceyAddServlet.class) .addMapping("/racey"), new ServletInfo("single", RaceyAddServlet.class) .addMapping("/single") .setExecutor(executorService = Executors.newSingleThreadExecutor())); } @After public void after() { executorService.shutdown(); } @Test @Ignore("This won't pass every run, but on most machines it should pass consistently") public void testRaceyServlet() throws InterruptedException, ExecutionException, IOException { Assert.assertNotEquals(NUM_REQUESTS * NUM_THREADS, runTest("/racey")); } @Test public void testSingleThreadExecutor() throws InterruptedException, ExecutionException, IOException { Assert.assertEquals(NUM_REQUESTS * NUM_THREADS, runTest("/single")); } public int runTest(final String path) throws IOException, ExecutionException, InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS); try { final List> futures = new ArrayList<>(); for (int i = 0; i < NUM_THREADS; ++i) { futures.add(executor.submit(new Runnable() { @Override public void run() { TestHttpClient client = new TestHttpClient(); try { for (int i = 0; i < NUM_REQUESTS; ++i) { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext" + path); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); } } catch (IOException e) { throw new RuntimeException(e); } finally { client.getConnectionManager().shutdown(); } } })); } for (Future future : futures) { future.get(); } TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext" + path); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); return Integer.parseInt(HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } finally { executor.shutdown(); } } } HttpHostValuesTestCase.java000066400000000000000000000072171420065311100344420ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/request/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.request; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import static io.undertow.servlet.Servlets.servlet; /** * Tests that ge get ip or host name depending on the method call * @author tmiyar * */ @RunWith(DefaultServer.class) public class HttpHostValuesTestCase { @BeforeClass public static void setup() { final PathHandler pathHandler = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(HttpHostValuesTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlets( servlet("request", RequestHostValuesServlet.class) .addMapping("/")); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); try { pathHandler.addPrefixPath(builder.getContextPath(), manager.start()); } catch (ServletException e) { throw new RuntimeException(e); } DefaultServer.setRootHandler(pathHandler); } @Test public void testRequestSpec() throws Exception { //test request values TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); if(System.getProperty("os.name").toLowerCase().contains("windows") || System.getSecurityManager() != null) { Assert.assertTrue(String.format("hostName: %s , response: %s", DefaultServer.getDefaultServerAddress().toString(), response), DefaultServer.getDefaultServerAddress().toString().contains(response)); } else { Assert.assertTrue(String.format("hostName: %s , response: %s", DefaultServer.getHostAddress(), response), DefaultServer.getHostAddress().equals(response)); } } finally { client.close(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/request/RaceyAddServlet.java000066400000000000000000000023601420065311100331430ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.request; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class RaceyAddServlet extends HttpServlet { volatile int value; @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("" + (value++)); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/request/RedirectServlet.java000066400000000000000000000010121420065311100332210ustar00rootroot00000000000000package io.undertow.servlet.test.request; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author Stuart Douglas */ public class RedirectServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.sendRedirect(req.getParameter("redirect")); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/request/RedirectTestCase.java000066400000000000000000000106711420065311100333230ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.request; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import java.util.ArrayList; import java.util.List; import static io.undertow.servlet.Servlets.servlet; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class RedirectTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler pathHandler = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlets( servlet("request", RequestPathServlet.class) .addMapping("/"), servlet("redirect", RedirectServlet.class) .addMapping("/redirect/*")); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); try { pathHandler.addPrefixPath(builder.getContextPath(), manager.start()); } catch (ServletException e) { throw new RuntimeException(e); } DefaultServer.setRootHandler(pathHandler); } @Test public void testServletRedirect() throws Exception { int port = DefaultServer.getHostPort("default"); //test redirects runtest("/servletContext/redirect/foo?redirect=../bar", "null", "/bar", "http://" + DefaultServer.getHostAddress() + ":" + port + "/servletContext/bar", "/servletContext/bar" , ""); runtest("/servletContext/redirect/foo/?redirect=../../bar", "null", "/bar", "http://" + DefaultServer.getHostAddress() + ":" + port + "/servletContext/bar", "/servletContext/bar" , ""); } private void runtest(String request, String... expectedBody) throws Exception { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + request); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertArrayEquals(expectedBody, split(response)); } finally { client.getConnectionManager().shutdown(); } } /** * because String.split() is retarded */ private static String[] split(String s) { List strings = new ArrayList<>(); int pos = 0; for (int i = 0; i < s.length(); ++i) { char c = s.charAt(i); if (c == ',') { strings.add(s.substring(pos, i)); pos = i + 1; } } strings.add(s.substring(pos)); return strings.toArray(new String[strings.size()]); } } RequestHostValuesServlet.java000066400000000000000000000024041420065311100350550ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/request/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.request; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet that returns hostname/ip * @author tmiyar * */ public class RequestHostValuesServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write(req.getLocalName()); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/request/RequestPathServlet.java000066400000000000000000000035321420065311100337360ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.request; import java.io.IOException; import java.net.URLDecoder; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.junit.Assert; /** * @author Stuart Douglas */ public class RequestPathServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { StringBuilder builtUri = new StringBuilder(req.getContextPath()); builtUri.append(req.getServletPath()); builtUri.append(req.getPathInfo() == null ? "" : req.getPathInfo()); Assert.assertEquals(URLDecoder.decode(req.getRequestURI(), "UTF-8"), builtUri.toString()); resp.getWriter().write(req.getPathInfo() + ","); resp.getWriter().write(req.getServletPath() + ","); resp.getWriter().write(req.getRequestURL().toString() + ","); resp.getWriter().write(req.getRequestURI() + ","); resp.getWriter().write(req.getQueryString() == null ? "" : req.getQueryString()); } } RequestPathTestCase.java000066400000000000000000000217401420065311100337470ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/request/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.request; import java.util.ArrayList; import java.util.List; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.util.SetHeaderFilter; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class RequestPathTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler pathHandler = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlets( new ServletInfo("request", RequestPathServlet.class) .addMapping("/req/*"), new ServletInfo("DefaultServlet", RequestPathServlet.class) .addMapping("/"), new ServletInfo("ExactServlet", RequestPathServlet.class) .addMapping("/exact"), new ServletInfo("ExactTxtServlet", RequestPathServlet.class) .addMapping("/exact.txt"), new ServletInfo("HtmlServlet", RequestPathServlet.class) .addMapping("*.html") ) .addFilters( new FilterInfo("header", SetHeaderFilter.class) .addInitParam("header", "Filter").addInitParam("value", "true"), new FilterInfo("all", SetHeaderFilter.class) .addInitParam("header", "all").addInitParam("value", "true")) .addFilterUrlMapping("header", "*.txt", DispatcherType.REQUEST) .addFilterUrlMapping("all", "/*", DispatcherType.REQUEST); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); try { pathHandler.addPrefixPath(builder.getContextPath(), manager.start()); } catch (ServletException e) { throw new RuntimeException(e); } DefaultServer.setRootHandler(pathHandler); } @Test public void testRequestPaths() throws Exception { int port = DefaultServer.getHostPort("default"); final String hostAddress = DefaultServer.getHostAddress(); //test default servlet mappings runtest("/servletContext/somePath", false, "null", "/somePath", "http://"+ hostAddress + ":" + port + "/servletContext/somePath", "/servletContext/somePath", ""); runtest("/servletContext/somePath?foo=bar", false, "null", "/somePath", "http://" + hostAddress + ":" + port + "/servletContext/somePath", "/servletContext/somePath", "foo=bar"); runtest("/servletContext/somePath?foo=b+a+r", false, "null", "/somePath", "http://" + hostAddress + ":" + port + "/servletContext/somePath", "/servletContext/somePath", "foo=b+a+r"); runtest("/servletContext/some%20path?foo=b+a+r", false, "null", "/some path", "http://" + hostAddress + ":" + port + "/servletContext/some%20path", "/servletContext/some%20path", "foo=b+a+r"); runtest("/servletContext/somePath.txt", true, "null", "/somePath.txt", "http://" + hostAddress + ":" + port + "/servletContext/somePath.txt", "/servletContext/somePath.txt", ""); runtest("/servletContext/somePath.txt?foo=bar", true, "null", "/somePath.txt", "http://" + hostAddress + ":" + port + "/servletContext/somePath.txt", "/servletContext/somePath.txt", "foo=bar"); //test non-default mappings runtest("/servletContext/req/somePath", false, "/somePath", "/req", "http://" + hostAddress + ":" + port + "/servletContext/req/somePath", "/servletContext/req/somePath", ""); runtest("/servletContext/req/somePath?foo=bar", false, "/somePath", "/req", "http://" + hostAddress + ":" + port + "/servletContext/req/somePath", "/servletContext/req/somePath", "foo=bar"); runtest("/servletContext/req/somePath?foo=b+a+r", false, "/somePath", "/req", "http://" + hostAddress + ":" + port + "/servletContext/req/somePath", "/servletContext/req/somePath", "foo=b+a+r"); runtest("/servletContext/req/some%20path?foo=b+a+r", false, "/some path", "/req", "http://" + hostAddress + ":" + port + "/servletContext/req/some%20path", "/servletContext/req/some%20path", "foo=b+a+r"); runtest("/servletContext/req/somePath.txt", true, "/somePath.txt", "/req", "http://" + hostAddress + ":" + port + "/servletContext/req/somePath.txt", "/servletContext/req/somePath.txt", ""); runtest("/servletContext/req/somePath.txt?foo=bar", true, "/somePath.txt", "/req", "http://" + hostAddress + ":" + port + "/servletContext/req/somePath.txt", "/servletContext/req/somePath.txt", "foo=bar"); //test exact path mappings runtest("/servletContext/exact", false, "null", "/exact", "http://" + hostAddress + ":" + port + "/servletContext/exact", "/servletContext/exact", ""); runtest("/servletContext/exact?foo=bar", false, "null", "/exact", "http://" + hostAddress + ":" + port + "/servletContext/exact", "/servletContext/exact", "foo=bar"); //test exact path mappings with a filer runtest("/servletContext/exact.txt", true, "null", "/exact.txt", "http://" + hostAddress + ":" + port + "/servletContext/exact.txt", "/servletContext/exact.txt", ""); runtest("/servletContext/exact.txt?foo=bar", true, "null", "/exact.txt", "http://" + hostAddress + ":" + port + "/servletContext/exact.txt", "/servletContext/exact.txt", "foo=bar"); //test servlet extension matches runtest("/servletContext/file.html", false, "null", "/file.html", "http://" + hostAddress + ":" + port + "/servletContext/file.html", "/servletContext/file.html", ""); runtest("/servletContext/file.html?foo=bar", false, "null", "/file.html", "http://" + hostAddress + ":" + port + "/servletContext/file.html", "/servletContext/file.html", "foo=bar"); } private void runtest(String request, boolean filterHeader, String... expectedBody) throws Exception { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + request); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertArrayEquals(expectedBody, split(response)); Assert.assertEquals("true", result.getHeaders("all")[0].getValue()); if (filterHeader) { Assert.assertEquals("true", result.getHeaders("Filter")[0].getValue()); } else { Assert.assertEquals(0, result.getHeaders("Filter").length); } } finally { client.getConnectionManager().shutdown(); } } /** * because String.split() is retarded */ private static String[] split(String s) { List strings = new ArrayList<>(); int pos = 0; for (int i = 0; i < s.length(); ++i) { char c = s.charAt(i); if (c == ',') { strings.add(s.substring(pos, i)); pos = i + 1; } } strings.add(s.substring(pos)); return strings.toArray(new String[strings.size()]); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/response/000077500000000000000000000000001420065311100274245ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/response/contenttype/000077500000000000000000000000001420065311100320005ustar00rootroot00000000000000ContentTypeCharsetTestCase.java000066400000000000000000000074571420065311100400230ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/response/contenttype/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.response.contenttype; import java.net.URLEncoder; import javax.servlet.ServletException; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ContentTypeCharsetTestCase { @BeforeClass public static void setup() throws ServletException { DeploymentUtils.setupServlet(new ServletInfo("charset", ContentTypeServlet.class) .addMapping("/*")); } @Test public void testCharsetAndContentType() throws Exception { runtest("text/html", "UTF8", "text/html;charset=UTF8", "text/html;charset=UTF8\nUTF8"); runtest("text/html", "", "text/html;charset=ISO-8859-1", "text/html;charset=ISO-8859-1\nISO-8859-1"); runtest("text/html; charset=UTF8", "", "text/html;charset=UTF8", "text/html;charset=UTF8\nUTF8"); runtest("text/html; charset=\"UTF8\"", "", "text/html;charset=UTF8", "text/html;charset=UTF8\nUTF8"); runtest("text/html; charset=UTF8; boundary=someString;", "", "text/html; boundary=someString;charset=UTF8", "text/html; boundary=someString;charset=UTF8\nUTF8"); runtest("text/html; charset=UTF8; boundary=someString; ", "", "text/html; boundary=someString;charset=UTF8", "text/html; boundary=someString;charset=UTF8\nUTF8"); runtest("multipart/related; type=\"text/xml\"; boundary=\"uuid:ce7d652a-d035-42fa-962c-5b8315084e32\"; start=\"\"; start-info=\"text/xml\"", "", "multipart/related; type=\"text/xml\"; boundary=\"uuid:ce7d652a-d035-42fa-962c-5b8315084e32\"; start=\"\"; start-info=\"text/xml\";charset=ISO-8859-1", "multipart/related; type=\"text/xml\"; boundary=\"uuid:ce7d652a-d035-42fa-962c-5b8315084e32\"; start=\"\"; start-info=\"text/xml\";charset=ISO-8859-1\nISO-8859-1"); } private void runtest(String contentType, String charset, String expectedContentType, String expectedBody) throws Exception { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/test?contentType=" + URLEncoder.encode(contentType) + "&charset=" + URLEncoder.encode(charset)); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(expectedContentType, result.getHeaders("Content-Type")[0].getValue()); Assert.assertEquals(expectedBody, response); } finally { client.getConnectionManager().shutdown(); } } } ContentTypeFilesTestCase.java000066400000000000000000000062051420065311100374620ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/response/contenttype/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.response.contenttype; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DefaultServletConfig; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.MimeMapping; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestResourceLoader; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Tomaz Cerar */ @RunWith(DefaultServer.class) public class ContentTypeFilesTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setClassLoader(ContentTypeFilesTestCase.class.getClassLoader()) .setContextPath("/app") .setDeploymentName("servletContext.war") .setResourceManager(new TestResourceLoader(ContentTypeServlet.class)) .setDefaultServletConfig(new DefaultServletConfig(true)) .addMimeMapping(new MimeMapping("jnlp", "application/x-java-jnlp-file")); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testFileContentType() throws Exception { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/app/webstart.jnlp"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("application/x-java-jnlp-file", result.getEntity().getContentType().getValue()); } finally { client.getConnectionManager().shutdown(); } } } ContentTypeServlet.java000066400000000000000000000031531420065311100364070ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/response/contenttype/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.response.contenttype; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class ContentTypeServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { String contentType = req.getParameter("contentType"); String charset = req.getParameter("charset"); if(contentType != null && !contentType.isEmpty()) { resp.setContentType(contentType); } if(charset != null && !charset.isEmpty()) { resp.setCharacterEncoding(charset); } resp.getWriter().print(resp.getContentType() + "\n" + resp.getCharacterEncoding()); } } webstart.jnlp000066400000000000000000000004061420065311100344410ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/response/contenttype Launch applet with Web Start Foo Bar Inc. undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/response/cookies/000077500000000000000000000000001420065311100310605ustar00rootroot00000000000000AddCookiesServlet.java000066400000000000000000000030521420065311100352160ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/response/cookies/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.response.cookies; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Simple servlet that adds cookies to response. * * @author Flavia Rainone */ public class AddCookiesServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { Cookie cookie1 = new Cookie("test1", "test1"); cookie1.setPath("/test"); Cookie cookie2 = new Cookie("test2", "test2"); resp.addCookie(cookie1); resp.addCookie(cookie2); resp.getWriter().append("Served at: ").append(req.getContextPath()); } } DuplicateCookiesServlet.java000066400000000000000000000044101420065311100364370ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/response/cookies/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.response.cookies; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet that adds duplicate cookies (i.e., with the same name) to response. * * @author Flavia Rainone */ public class DuplicateCookiesServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { Cookie cookie1 = new Cookie("test1", "test1"); cookie1.setPath("/test1_1"); Cookie cookie2 = new Cookie("test1", "test1"); cookie2.setPath("/test1_2"); Cookie cookie3 = new Cookie("test2", "test2"); cookie3.setPath("/test2"); Cookie cookie4 = new Cookie("test2", "test2"); cookie4.setPath("/test2"); cookie4.setDomain("www.domain2.com"); Cookie cookie5 = new Cookie("test3", "test3"); cookie5.setDomain("www.domain3-1.com"); Cookie cookie6 = new Cookie("test3", "test3"); cookie6.setDomain("www.domain3-2.com"); Cookie cookie7 = new Cookie("test3", "test3"); resp.addCookie(cookie1); resp.addCookie(cookie2); resp.addCookie(cookie3); resp.addCookie(cookie4); resp.addCookie(cookie5); resp.addCookie(cookie6); resp.addCookie(cookie7); resp.getWriter().append("Served at: ").append(req.getContextPath()); } } JSessionIDCookiesServlet.java000066400000000000000000000045151420065311100365050ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/response/cookies/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.response.cookies; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet that emulates a buggy behavior where JSessionID cookie is added several times with * wrong path, and a few of them with max age limits for cookie expiration. * * @author Flavia Rainone */ public class JSessionIDCookiesServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { javax.servlet.http.Cookie cookie1 = new javax.servlet.http.Cookie("JSESSIONID", "_bug_fix"); cookie1.setPath("/path1"); cookie1.setMaxAge(0); javax.servlet.http.Cookie cookie2 = new javax.servlet.http.Cookie("JSESSIONID", "_bug_fix"); cookie2.setPath("/path2"); cookie2.setMaxAge(0); javax.servlet.http.Cookie cookie3 = new javax.servlet.http.Cookie("JSESSIONID", "_bug_fix"); cookie1.setPath("/path3"); cookie1.setMaxAge(500); javax.servlet.http.Cookie cookie4 = new javax.servlet.http.Cookie("JSESSIONID", "_bug_fix"); cookie2.setPath("/path4"); cookie2.setMaxAge(1000); resp.addCookie(cookie1); resp.addCookie(cookie2); resp.addCookie(cookie3); resp.addCookie(cookie4); // creating session -> additional set-cookie req.getSession().setAttribute("CleanSessions", true); resp.getWriter().append("Served at: ").append(req.getContextPath()); } } OverwriteCookiesServlet.java000066400000000000000000000060151420065311100365160ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/response/cookies/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.response.cookies; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet that adds multiple cookies with same name and a few of which sharing * the same path, to test cookies with same path and name being correctly * overriden. * * @author Flavia Rainone */ public class OverwriteCookiesServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { Cookie cookie1 = new javax.servlet.http.Cookie("test", "test1"); cookie1.setPath("/test"); Cookie cookie2 = new javax.servlet.http.Cookie("test", "test2"); cookie2.setPath("/test"); Cookie cookie3 = new javax.servlet.http.Cookie("test", "test3"); Cookie cookie4 = new javax.servlet.http.Cookie("test", "test4"); Cookie cookie5 = new javax.servlet.http.Cookie("test", "test5"); Cookie cookie6 = new javax.servlet.http.Cookie("test", "test6"); cookie6.setPath("/test"); cookie6.setDomain("www.domain.com"); Cookie cookie7 = new javax.servlet.http.Cookie("test", "test7"); cookie7.setPath("/test"); cookie7.setDomain("www.domain.com"); Cookie cookie8 = new javax.servlet.http.Cookie("test", "test8"); cookie8.setPath("/test"); cookie8.setDomain("www.domain.com"); Cookie cookie9 = new javax.servlet.http.Cookie("test", "test9"); cookie9.setDomain("www.domain.com"); Cookie cookie10 = new javax.servlet.http.Cookie("test", "test10"); cookie10.setDomain("www.domain.com"); resp.addCookie(cookie1); resp.addCookie(cookie2); resp.addCookie(cookie3); resp.addCookie(cookie4); resp.addCookie(cookie5); resp.addCookie(cookie6); resp.addCookie(cookie7); resp.addCookie(cookie8); resp.addCookie(cookie9); resp.addCookie(cookie10); // creating session -> additional jsessionid set-cookie req.getSession().setAttribute("CleanSessions", true); resp.getWriter().append("Served at: ").append(req.getContextPath()); } } ResponseCookiesTestCase.java000066400000000000000000000202611420065311100364140ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/response/cookies/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.response.cookies; import java.util.Arrays; import java.util.Comparator; import javax.servlet.ServletException; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Test for response.addCookie * * @author Flavia Rainone */ @RunWith(DefaultServer.class) public class ResponseCookiesTestCase { @BeforeClass public static void setup() throws ServletException { DeploymentUtils.setupServlet( new ServletInfo("add-cookies", AddCookiesServlet.class) .addMapping("/add-cookies"), new ServletInfo("duplicate-cookies", DuplicateCookiesServlet.class) .addMapping("/duplicate-cookies"), new ServletInfo("overwrite-cookies", OverwriteCookiesServlet.class) .addMapping("/overwrite-cookies"), new ServletInfo("jsessionid-cookies", JSessionIDCookiesServlet.class) .addMapping("/jsessionid-cookies")); } @Test public void addCookies() throws Exception { final TestHttpClient client = new TestHttpClient(); try { final HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/add-cookies"); final HttpResponse result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); assertEquals("Served at: /servletContext", response); final Header[] setCookieHeaders = result.getHeaders("Set-Cookie"); assertEquals(2, setCookieHeaders.length); assertTrue(setCookieHeadersContainsValue("test1=test1; path=/test", setCookieHeaders)); assertTrue(setCookieHeadersContainsValue("test2=test2", setCookieHeaders)); } finally { client.getConnectionManager().shutdown(); } } @Test public void duplicateCookies() throws Exception { final TestHttpClient client = new TestHttpClient(); try { final HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/duplicate-cookies"); final HttpResponse result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); assertEquals("Served at: /servletContext", response); final Header[] setCookieHeaders = result.getHeaders("Set-Cookie"); assertEquals(7, setCookieHeaders.length); Arrays.sort(setCookieHeaders, Comparator.comparing(Object::toString)); assertTrue(setCookieHeadersContainsValue("test1=test1; path=/test1_1", setCookieHeaders)); assertTrue(setCookieHeadersContainsValue("test1=test1; path=/test1_1", setCookieHeaders)); assertTrue(setCookieHeadersContainsValue("test1=test1; path=/test1_2", setCookieHeaders)); assertTrue(setCookieHeadersContainsValue("test2=test2; path=/test2", setCookieHeaders)); assertTrue(setCookieHeadersContainsValue("test2=test2; path=/test2; domain=www.domain2.com", setCookieHeaders)); assertTrue(setCookieHeadersContainsValue("test3=test3", setCookieHeaders)); assertTrue(setCookieHeadersContainsValue("test3=test3; domain=www.domain3-1.com", setCookieHeaders)); assertTrue(setCookieHeadersContainsValue("test3=test3; domain=www.domain3-2.com", setCookieHeaders)); } finally { client.getConnectionManager().shutdown(); } } @Test public void overwriteCookies() throws Exception { final TestHttpClient client = new TestHttpClient(); try { final HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/overwrite-cookies"); final HttpResponse result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); assertEquals("Served at: /servletContext", response); final Header[] setCookieHeaders = result.getHeaders("Set-Cookie"); assertEquals(5, setCookieHeaders.length); Arrays.sort(setCookieHeaders, Comparator.comparing(Object::toString)); assertTrue(setCookieHeadersMatchesValue("JSESSIONID=.*; path=/servletContext", setCookieHeaders)); assertTrue(setCookieHeadersContainsValue("test=test10; domain=www.domain.com", setCookieHeaders)); assertTrue(setCookieHeadersContainsValue("test=test2; path=/test", setCookieHeaders)); assertTrue(setCookieHeadersContainsValue("test=test5", setCookieHeaders)); assertTrue(setCookieHeadersContainsValue("test=test8; path=/test; domain=www.domain.com", setCookieHeaders)); } finally { client.getConnectionManager().shutdown(); } } @Test public void jsessionIdCookies() throws Exception { final TestHttpClient client = new TestHttpClient(); try { final HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/jsessionid-cookies"); final HttpResponse result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); assertEquals("Served at: /servletContext", response); final Header[] setCookieHeaders = result.getHeaders("Set-Cookie"); assertEquals(4, setCookieHeaders.length); assertTrue(setCookieHeadersContainsValueStartingWithPrefix("JSESSIONID=_bug_fix; path=/path3; Max-Age=500; Expires=", setCookieHeaders)); assertTrue(setCookieHeadersContainsValueStartingWithPrefix("JSESSIONID=_bug_fix; path=/path4; Max-Age=1000; Expires=", setCookieHeaders)); assertTrue(setCookieHeadersMatchesValue("JSESSIONID=.*; path=/servletContext", setCookieHeaders)); } finally { client.getConnectionManager().shutdown(); } } private static boolean setCookieHeadersContainsValue(final String value, final Header[] setCookieHeaders) { if (setCookieHeaders == null) return false; for (Header h : setCookieHeaders) { if (value.equals(h.getValue())) return true; } return false; } private static boolean setCookieHeadersContainsValueStartingWithPrefix(final String prefix, final Header[] setCookieHeaders) { if (setCookieHeaders == null) return false; for (Header h : setCookieHeaders) { if (h.getValue().startsWith(prefix)) return true; } return false; } private static boolean setCookieHeadersMatchesValue(final String regexp, final Header[] setCookieHeaders) { if (setCookieHeaders == null) return false; for (Header h : setCookieHeaders) { if (h.getValue().matches(regexp)) return true; } return false; } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/response/writer/000077500000000000000000000000001420065311100307405ustar00rootroot00000000000000LargeResponseWriterServlet.java000066400000000000000000000030231420065311100370350ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/response/writer/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.response.writer; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class LargeResponseWriterServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { String msg = getMessage(); resp.setContentLength(msg.length()); resp.getWriter().write(msg); } public static String getMessage() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10000; ++i) { sb.append("asdfasdjgabckaslfjdsakl"); } return sb.toString(); } } ResponseWriterServlet.java000066400000000000000000000040361420065311100360670ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/response/writer/* * JBoss, Home of Professional Open Source. * Copyright 2012 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.response.writer; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * @author Stuart Douglas */ public class ResponseWriterServlet extends HttpServlet { public static final String CONTENT_LENGTH_FLUSH = "content-length-flush"; @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { String test = req.getParameter("test"); if (test.equals(CONTENT_LENGTH_FLUSH)) { contentLengthFlush(req, resp); } else { throw new IllegalArgumentException("not a test " + test); } } private void contentLengthFlush(HttpServletRequest req, HttpServletResponse resp) throws IOException { int size = 10; PrintWriter pw = resp.getWriter(); StringBuffer tmp = new StringBuffer(2 * size); int i = 0; pw.write("first-"); resp.setContentLength(size); //write more data than the content length while (i < 20) { tmp = tmp.append("a"); i = i + 1; } pw.println(tmp); resp.addHeader("not-header", "not"); } } ResponseWriterTestCase.java000066400000000000000000000075361420065311100361660ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/response/writer/* * JBoss, Home of Professional Open Source. * Copyright 2012 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.response.writer; import javax.servlet.ServletException; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.Servlets; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.TestHttpClient; import io.undertow.util.FileUtils; /** * @author Tomaz Cerar */ @RunWith(DefaultServer.class) public class ResponseWriterTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setClassLoader(ResponseWriterTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setDeploymentName("servletContext.war") .addServlet(Servlets.servlet("resp", ResponseWriterServlet.class) .addMapping("/resp")) .addServlet(Servlets.servlet("respLArget", LargeResponseWriterServlet.class) .addMapping("/large")); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testContentLengthBasedFlush() throws Exception { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/resp?test=" + ResponseWriterServlet.CONTENT_LENGTH_FLUSH); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String data = FileUtils.readFile(result.getEntity().getContent()); Assert.assertEquals("first-aaaa", data); Assert.assertEquals(0, result.getHeaders("not-header").length); } finally { client.getConnectionManager().shutdown(); } } @Test public void testWriterLargeResponse() throws Exception { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/large"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String data = FileUtils.readFile(result.getEntity().getContent()); Assert.assertEquals(LargeResponseWriterServlet.getMessage(), data); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/000077500000000000000000000000001420065311100274355ustar00rootroot00000000000000MultipartAcceptingServlet.java000066400000000000000000000033341420065311100353700ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Collection; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; /** * A servlet that expects to receive a multipart post request with 2 parts. */ public class MultipartAcceptingServlet extends HttpServlet { @Override protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { Collection parts = req.getParts(); if (parts.size() != 2) { resp.setStatus(418); } for (Part part: parts) { BufferedReader reader = new BufferedReader(new InputStreamReader(part.getInputStream())); if (!reader.readLine().startsWith("0123")) { resp.setStatus(418); } } } } SendAuthTypeServlet.java000066400000000000000000000027221420065311100341460ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security; import java.io.IOException; import java.io.OutputStream; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * A servlet to just return the authentication mechanism when called. * * @author Darran Lofthouse */ public class SendAuthTypeServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { OutputStream stream = resp.getOutputStream(); String authType = req.getAuthType(); stream.write(authType.getBytes()); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/SendSchemeServlet.java000066400000000000000000000032161420065311100336650ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Simple test servlet to return the transport for the request back to the calling client. * * @author Darran Lofthouse */ public class SendSchemeServlet extends HttpServlet { private static final long serialVersionUID = -4804724108087346230L; private static final Charset UTF_8 = StandardCharsets.UTF_8; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { OutputStream stream = resp.getOutputStream(); stream.write(req.getScheme().getBytes(UTF_8)); } } SendUsernameServlet.java000066400000000000000000000026461420065311100341670ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security; import java.io.IOException; import java.io.OutputStream; import java.security.Principal; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class SendUsernameServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { OutputStream stream = resp.getOutputStream(); Principal principal = req.getUserPrincipal(); String name = principal.getName(); stream.write(name.getBytes()); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/basic/000077500000000000000000000000001420065311100305165ustar00rootroot00000000000000ServletBasicAuthTestCase.java000066400000000000000000000164501420065311100361540ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/basic/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.basic; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.AuthMethodConfig; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.SecurityInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.WebResourceCollection; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.security.SendAuthTypeServlet; import io.undertow.servlet.test.security.SendUsernameServlet; import io.undertow.servlet.test.security.constraint.ServletIdentityManager; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.FlexBase64; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import static io.undertow.util.Headers.AUTHORIZATION; import static io.undertow.util.Headers.BASIC; import static io.undertow.util.Headers.WWW_AUTHENTICATE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ServletBasicAuthTestCase { private static final String REALM_NAME = "Servlet_Realm"; @BeforeClass public static void setup() throws ServletException { final PathHandler path = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo usernameServlet = new ServletInfo("Username Servlet", SendUsernameServlet.class) .addMapping("/secured/username"); ServletInfo authTypeServlet = new ServletInfo("Auth Type Servlet", SendAuthTypeServlet.class) .addMapping("/secured/authType"); ServletIdentityManager identityManager = new ServletIdentityManager(); identityManager.addUser("user1", "password1", "role1"); identityManager.addUser("charsetUser", "password-ü", "role1"); LoginConfig loginConfig = new LoginConfig(REALM_NAME); Map props = new HashMap<>(); props.put("charset", "ISO_8859_1"); props.put("user-agent-charsets", "Chrome,UTF-8,OPR,UTF-8"); loginConfig.addFirstAuthMethod(new AuthMethodConfig("BASIC", props)); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setIdentityManager(identityManager) .setLoginConfig(loginConfig) .addServlets(usernameServlet, authTypeServlet); builder.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection() .addUrlPattern("/secured/*")) .addRoleAllowed("role1") .setEmptyRoleSemantic(SecurityInfo.EmptyRoleSemantic.DENY)); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); path.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(path); } @Test public void testChallengeSent() throws Exception { TestHttpClient client = new TestHttpClient(); String url = DefaultServer.getDefaultServerURL() + "/servletContext/secured/username"; HttpGet get = new HttpGet(url); HttpResponse result = client.execute(get); HttpClientUtils.readResponse(result); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); assertEquals(1, values.length); String value = values[0].getValue(); assertTrue(value.startsWith("Basic")); } @Test public void testUserName() throws Exception { testCall("username", "user1", StandardCharsets.UTF_8, "Chrome", "user1", "password1", 200); } @Test public void testAuthType() throws Exception { testCall("authType", "BASIC", StandardCharsets.UTF_8, "Chrome", "user1", "password1", 200); } @Test public void testBasicAuthNonAscii() throws Exception { testCall("authType", "BASIC", StandardCharsets.UTF_8, "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", "charsetUser", "password-ü", 200); testCall("authType", "BASIC", StandardCharsets.ISO_8859_1, "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", "charsetUser", "password-ü", 401); testCall("authType", "BASIC", StandardCharsets.ISO_8859_1, "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1", "charsetUser", "password-ü", 200); testCall("authType", "BASIC", StandardCharsets.UTF_8, "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1", "charsetUser", "password-ü", 401); } public void testCall(final String path, final String expectedResponse, Charset charset, String userAgent, String user, String password, int expect) throws Exception { TestHttpClient client = new TestHttpClient(); try { String url = DefaultServer.getDefaultServerURL() + "/servletContext/secured/" + path; HttpGet get = new HttpGet(url); get = new HttpGet(url); get.addHeader(Headers.USER_AGENT_STRING, userAgent); get.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString((user + ":" + password).getBytes(charset), false)); HttpResponse result = client.execute(get); assertEquals(expect, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); if(expect == 200) { assertEquals(expectedResponse, response); } } finally { client.getConnectionManager().shutdown(); } } } ServletCertAndDigestAuthTestCase.java000066400000000000000000000137741420065311100376210ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/basic/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.basic; import static io.undertow.util.Headers.AUTHORIZATION; import static io.undertow.util.Headers.BASIC; import static org.junit.Assert.assertEquals; import static org.xnio.Options.SSL_CLIENT_AUTH_MODE; import static org.xnio.SslClientAuthMode.NOT_REQUESTED; import java.nio.charset.StandardCharsets; import javax.net.ssl.SSLContext; import javax.servlet.MultipartConfigElement; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.AuthMethodConfig; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.SecurityInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.WebResourceCollection; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.security.MultipartAcceptingServlet; import io.undertow.servlet.test.security.constraint.ServletIdentityManager; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.TestHttpClient; import io.undertow.util.FlexBase64; import io.undertow.util.StatusCodes; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.entity.mime.content.ByteArrayBody; import org.apache.http.entity.mime.content.StringBody; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.OptionMap; /** * @author Tomas Hofman */ @RunWith(DefaultServer.class) public class ServletCertAndDigestAuthTestCase { private static final String REALM_NAME = "Servlet_Realm"; private static final String BASE_PATH = "/servletContext/secured/"; private static SSLContext clientSSLContext; @BeforeClass public static void startSSL() throws Exception { DefaultServer.startSSLServer(OptionMap.create(SSL_CLIENT_AUTH_MODE, NOT_REQUESTED)); clientSSLContext = DefaultServer.getClientSSLContext(); final PathHandler path = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo multipartServlet = new ServletInfo("Multipart Accepting Servlet", MultipartAcceptingServlet.class) .addMapping("/secured/multipart") .setMultipartConfig(new MultipartConfigElement("")); ServletIdentityManager identityManager = new ServletIdentityManager(); identityManager.addUser("user1", "password1", "role1"); identityManager.addUser("charsetUser", "password-ü", "role1"); LoginConfig loginConfig = new LoginConfig(REALM_NAME); loginConfig.addFirstAuthMethod(new AuthMethodConfig("BASIC")); loginConfig.addFirstAuthMethod(new AuthMethodConfig("CLIENT_CERT")); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setIdentityManager(identityManager) .setLoginConfig(loginConfig) .addServlets(multipartServlet); builder.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection() .addUrlPattern("/secured/*")) .addRoleAllowed("role1") .setEmptyRoleSemantic(SecurityInfo.EmptyRoleSemantic.DENY)); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); path.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(path); } @AfterClass public static void stopSSL() throws Exception { clientSSLContext = null; DefaultServer.stopSSLServer(); } @Test public void testMultipartRequest() throws Exception { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 2000; i++) { sb.append("0123456789"); } try (TestHttpClient client = new TestHttpClient()) { // create POST request MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.addPart("part1", new ByteArrayBody(sb.toString().getBytes(), "file.txt")); builder.addPart("part2", new StringBody("0123456789", ContentType.TEXT_HTML)); HttpEntity entity = builder.build(); client.setSSLContext(clientSSLContext); String url = DefaultServer.getDefaultServerSSLAddress() + BASE_PATH + "multipart"; HttpPost post = new HttpPost(url); post.setEntity(entity); post.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString(("user1" + ":" + "password1").getBytes(StandardCharsets.UTF_8), false)); HttpResponse result = client.execute(post); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); } } } ServletClientCertAuthTestCase.java000066400000000000000000000155331420065311100371700ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/basic/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.basic; import static org.junit.Assert.assertEquals; import java.io.IOException; import java.security.Principal; import java.util.Collections; import java.util.HashSet; import java.util.Set; import javax.net.ssl.SSLContext; import javax.servlet.ServletException; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.security.idm.Account; import io.undertow.security.idm.Credential; import io.undertow.security.idm.IdentityManager; import io.undertow.security.idm.X509CertificateCredential; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.AuthMethodConfig; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.SecurityInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.WebResourceCollection; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.security.SendAuthTypeServlet; import io.undertow.servlet.test.security.SendUsernameServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ServletClientCertAuthTestCase { private static final String REALM_NAME = "Servlet_Realm"; protected static final IdentityManager identityManager; private static SSLContext clientSSLContext; static { final Set certUsers = new HashSet<>(); certUsers.add("CN=Test Client,OU=OU,O=Org,L=City,ST=State,C=GB"); identityManager = new IdentityManager() { @Override public Account verify(Account account) { // An existing account so for testing assume still valid. return account; } @Override public Account verify(String id, Credential credential) { return null; } @Override public Account verify(Credential credential) { if (credential instanceof X509CertificateCredential) { final Principal p = ((X509CertificateCredential) credential).getCertificate().getSubjectX500Principal(); if (certUsers.contains(p.getName())) { return new Account() { @Override public Principal getPrincipal() { return p; } @Override public Set getRoles() { return Collections.singleton("role1"); } }; } } return null; } }; } @BeforeClass public static void startSSL() throws Exception { } @AfterClass public static void stopSSL() throws Exception { clientSSLContext = null; DefaultServer.stopSSLServer(); } @BeforeClass public static void setup() throws ServletException, IOException { DefaultServer.startSSLServer(); clientSSLContext = DefaultServer.getClientSSLContext(); final PathHandler path = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo usernameServlet = new ServletInfo("Username Servlet", SendUsernameServlet.class) .addMapping("/secured/username"); ServletInfo authTypeServlet = new ServletInfo("Auth Type Servlet", SendAuthTypeServlet.class) .addMapping("/secured/authType"); LoginConfig loginConfig = new LoginConfig(REALM_NAME); loginConfig.addFirstAuthMethod(new AuthMethodConfig("CLIENT_CERT")); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setIdentityManager(identityManager) .setLoginConfig(loginConfig) .addServlets(usernameServlet, authTypeServlet); builder.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection() .addUrlPattern("/secured/*")) .addRoleAllowed("role1") .setEmptyRoleSemantic(SecurityInfo.EmptyRoleSemantic.DENY)); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); path.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(path); } @Test public void testUserName() throws Exception { testCall("username", "CN=Test Client,OU=OU,O=Org,L=City,ST=State,C=GB", 200); } @Test public void testAuthType() throws Exception { testCall("authType", "CLIENT_CERT", 200); } public void testCall(final String path, final String expectedResponse, int expect) throws Exception { TestHttpClient client = new TestHttpClient(); client.setSSLContext(clientSSLContext); try { String url = DefaultServer.getDefaultServerSSLAddress() + "/servletContext/secured/" + path; HttpGet get = new HttpGet(url); HttpResponse result = client.execute(get); assertEquals(expect, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); if (expect == 200) { assertEquals(expectedResponse, response); } } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/constraint/000077500000000000000000000000001420065311100316215ustar00rootroot00000000000000AuthenticationMessageServlet.java000066400000000000000000000065521420065311100402460ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/constraint/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.constraint; import io.undertow.servlet.test.util.MessageServlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * An extension to the MessageServlet that can also perform additional checks related to the authenticated principal. * * @author Darran Lofthouse */ public class AuthenticationMessageServlet extends MessageServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { checkExpectedMechanism(req); checkExpectedUser(req); super.doGet(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } private void checkExpectedMechanism(HttpServletRequest req) { String expectedMechanism = req.getHeader("ExpectedMechanism"); if (expectedMechanism == null) { throw new IllegalStateException("No ExpectedMechanism received."); } if (expectedMechanism.equals("None")) { if (req.getAuthType() != null) { throw new IllegalStateException("Authentication occurred when not expected."); } } else if (expectedMechanism.equals("BASIC")) { if (req.getAuthType() != HttpServletRequest.BASIC_AUTH) { throw new IllegalStateException("Expected mechanism type not matched."); } } else { throw new IllegalStateException("ExpectedMechanism not recognised."); } } private void checkExpectedUser(HttpServletRequest req) { String expectedUser = req.getHeader("ExpectedUser"); if (expectedUser == null) { throw new IllegalStateException("No ExpectedUser received."); } if (expectedUser.equals("None")) { if (req.getRemoteUser() != null) { throw new IllegalStateException("Unexpected RemoteUser returned."); } if (req.getUserPrincipal() != null) { throw new IllegalStateException("Unexpected UserPrincipal returned."); } } else { if (req.getRemoteUser().equals(expectedUser) == false) { throw new IllegalStateException("Different RemoteUser returned."); } if (req.getUserPrincipal().getName().equals(expectedUser) == false) { throw new IllegalStateException("Different UserPrincipal returned."); } } } } EmptyRoleSemanticTestCase.java000066400000000000000000000156221420065311100374530ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/constraint/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.constraint; import static io.undertow.util.Headers.AUTHORIZATION; import static io.undertow.util.Headers.BASIC; import static io.undertow.util.Headers.WWW_AUTHENTICATE; import static org.junit.Assert.assertEquals; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.SecurityInfo.EmptyRoleSemantic; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.WebResourceCollection; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.util.MessageServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.FlexBase64; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import javax.servlet.ServletException; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * A test case for the three supported {@link EmptyRoleSemantic} values. * * @author Darran Lofthouse */ @RunWith(DefaultServer.class) public class EmptyRoleSemanticTestCase { public static final String HELLO_WORLD = "Hello World"; @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("servlet", AuthenticationMessageServlet.class) .addInitParam(MessageServlet.MESSAGE, HELLO_WORLD) .addMapping("/permit") .addMapping("/deny") .addMapping("/authenticate"); ServletIdentityManager identityManager = new ServletIdentityManager(); identityManager.addUser("user1", "password1"); // Just one role less user. DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setIdentityManager(identityManager) .setLoginConfig(new LoginConfig("BASIC", "Test Realm")) .addServlet(s); builder.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection().addUrlPattern("/permit")) .setEmptyRoleSemantic(EmptyRoleSemantic.PERMIT)); builder.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection().addUrlPattern("/deny")) .setEmptyRoleSemantic(EmptyRoleSemantic.DENY)); builder.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection().addUrlPattern("/authenticate")) .setEmptyRoleSemantic(EmptyRoleSemantic.AUTHENTICATE)); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testPermit() throws Exception { TestHttpClient client = new TestHttpClient(); final String url = DefaultServer.getDefaultServerURL() + "/servletContext/permit"; try { HttpGet initialGet = new HttpGet(url); initialGet.addHeader("ExpectedMechanism", "None"); initialGet.addHeader("ExpectedUser", "None"); HttpResponse result = client.execute(initialGet); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(HELLO_WORLD, response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testDeny() throws Exception { TestHttpClient client = new TestHttpClient(); final String url = DefaultServer.getDefaultServerURL() + "/servletContext/deny"; try { HttpGet initialGet = new HttpGet(url); initialGet.addHeader("ExpectedMechanism", "None"); initialGet.addHeader("ExpectedUser", "None"); HttpResponse result = client.execute(initialGet); assertEquals(StatusCodes.FORBIDDEN, result.getStatusLine().getStatusCode()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testAuthenticate() throws Exception { TestHttpClient client = new TestHttpClient(); final String url = DefaultServer.getDefaultServerURL() + "/servletContext/authenticate"; try { HttpGet get = new HttpGet(url); HttpResponse result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); assertEquals(1, values.length); assertEquals(BASIC + " realm=\"Test Realm\"", values[0].getValue()); HttpClientUtils.readResponse(result); get = new HttpGet(url); get.addHeader("ExpectedMechanism", "BASIC"); get.addHeader("ExpectedUser", "user1"); get.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString("user1:password1".getBytes(), false)); result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(HELLO_WORLD, response); } finally { client.getConnectionManager().shutdown(); } } } SecurityConstraintUrlMappingTestCase.java000066400000000000000000000312241420065311100417160ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/constraint/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.constraint; import java.io.IOException; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.SecurityInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.WebResourceCollection; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.util.MessageServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.FlexBase64; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import static io.undertow.util.Headers.AUTHORIZATION; import static io.undertow.util.Headers.BASIC; import static io.undertow.util.Headers.WWW_AUTHENTICATE; import static org.junit.Assert.assertEquals; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class SecurityConstraintUrlMappingTestCase { public static final String HELLO_WORLD = "Hello World"; @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("servlet", AuthenticationMessageServlet.class) .addInitParam(MessageServlet.MESSAGE, HELLO_WORLD) .addMapping("/role1") .addMapping("/role2") .addMapping("/starstar") .addMapping("/secured/role2/*") .addMapping("/secured/1/2/*") .addMapping("/public/*") .addMapping("/extension/*"); ServletIdentityManager identityManager = new ServletIdentityManager(); identityManager.addUser("user1", "password1", "role1"); identityManager.addUser("user2", "password2", "role2", "**"); identityManager.addUser("user3", "password3", "role1", "role2"); identityManager.addUser("user4", "password4", "badRole"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setIdentityManager(identityManager) .setLoginConfig(new LoginConfig("BASIC", "Test Realm")) .addServlet(s); builder.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection() .addUrlPattern("/role1")) .addRoleAllowed("role1")); builder.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection() .addUrlPattern("/starstar")) .addRoleAllowed("**")); builder.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection() .addUrlPattern("/secured/*")) .addRoleAllowed("role2")); builder.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection() .addUrlPattern("/secured/*")) .addRoleAllowed("role2")); builder.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection() .addUrlPattern("/secured/1/*")) .addRoleAllowed("role1")); builder.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection() .addUrlPattern("/secured/1/2/*")) .addRoleAllowed("role2")); builder.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection() .addUrlPattern("*.html")) .addRoleAllowed("role2")); builder.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection() .addUrlPattern("/public/*")).setEmptyRoleSemantic(SecurityInfo.EmptyRoleSemantic.PERMIT)); builder.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection() .addUrlPattern("/public/postSecured/*") .addHttpMethod("POST")) .addRoleAllowed("role1")); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/star") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setIdentityManager(identityManager) .setLoginConfig(new LoginConfig("BASIC", "Test Realm")) .addSecurityRole("**") .addServlet(s); builder.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection() .addUrlPattern("/starstar")) .addRoleAllowed("**")); manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testExactMatch() throws IOException { runSimpleUrlTest(DefaultServer.getDefaultServerURL() + "/servletContext/role1", "user2:password2", "user1:password1"); } @Test public void testPatternMatch() throws IOException { runSimpleUrlTest(DefaultServer.getDefaultServerURL() + "/servletContext/secured/role2/aa", "user1:password1", "user2:password2"); } @Test public void testStartStar() throws IOException { runSimpleUrlTest(DefaultServer.getDefaultServerURL() + "/servletContext/starstar", null, "user2:password2"); } @Test public void testStartStar2() throws IOException { runSimpleUrlTest(DefaultServer.getDefaultServerURL() + "/star/starstar", "user1:password1", "user2:password2"); } @Test public void testExtensionMatch() throws IOException { runSimpleUrlTest(DefaultServer.getDefaultServerURL() + "/servletContext/extension/a.html", "user1:password1", "user2:password2"); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/public/a.html"); get.addHeader("ExpectedMechanism", "None"); get.addHeader("ExpectedUser", "None"); HttpResponse result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } @Test public void testAggregatedRoles() throws IOException { runSimpleUrlTest(DefaultServer.getDefaultServerURL() + "/servletContext/secured/1/2/aa", "user4:password4", "user3:password3"); runSimpleUrlTest(DefaultServer.getDefaultServerURL() + "/servletContext/secured/1/2/aa", "user1:password1", "user2:password2"); } @Test public void testHttpMethod() throws IOException { TestHttpClient client = new TestHttpClient(); final String url = DefaultServer.getDefaultServerURL() + "/servletContext/public/postSecured/a"; try { HttpGet initialGet = new HttpGet(url); initialGet.addHeader("ExpectedMechanism", "None"); initialGet.addHeader("ExpectedUser", "None"); HttpResponse result = client.execute(initialGet); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); HttpPost post = new HttpPost(url); result = client.execute(post); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); assertEquals(1, values.length); assertEquals(BASIC + " realm=\"Test Realm\"", values[0].getValue()); HttpClientUtils.readResponse(result); post = new HttpPost(url); post.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString("user2:password2".getBytes(), false)); result = client.execute(post); assertEquals(StatusCodes.FORBIDDEN, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); post = new HttpPost(url); post.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString("user1:password1".getBytes(), false)); post.addHeader("ExpectedMechanism", "BASIC"); post.addHeader("ExpectedUser", "user1"); result = client.execute(post); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(HELLO_WORLD, response); } finally { client.getConnectionManager().shutdown(); } } public void runSimpleUrlTest(final String url, final String badUser, final String goodUser) throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(url); HttpResponse result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); assertEquals(1, values.length); assertEquals(BASIC + " realm=\"Test Realm\"", values[0].getValue()); HttpClientUtils.readResponse(result); if(badUser != null) { get = new HttpGet(url); get.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString(badUser.getBytes(), false)); result = client.execute(get); assertEquals(StatusCodes.FORBIDDEN, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } get = new HttpGet(url); get.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString(goodUser.getBytes(), false)); get.addHeader("ExpectedMechanism", "BASIC"); get.addHeader("ExpectedUser", goodUser.substring(0, goodUser.indexOf(':'))); result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); //make sure that caching is disabled Assert.assertEquals("0", result.getHeaders("Expires")[0].getValue()); Assert.assertEquals("no-cache", result.getHeaders("Pragma")[0].getValue()); Assert.assertEquals("no-cache, no-store, must-revalidate", result.getHeaders("Cache-Control")[0].getValue()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(HELLO_WORLD, response); } finally { client.getConnectionManager().shutdown(); } } } ServletIdentityManager.java000066400000000000000000000112161420065311100370370ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/constraint/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.constraint; import io.undertow.security.idm.Account; import io.undertow.security.idm.Credential; import io.undertow.security.idm.DigestCredential; import io.undertow.security.idm.IdentityManager; import io.undertow.security.idm.PasswordCredential; import io.undertow.util.HexConverter; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * A mock {@link IdentityManager} implementation for servlet security testing. * * @author Darran Lofthouse */ public class ServletIdentityManager implements IdentityManager { private static final Charset UTF_8 = StandardCharsets.UTF_8; private final Map users = new HashMap<>(); public void addUser(final String name, final String password, final String... roles) { UserAccount user = new UserAccount(); user.name = name; user.password = password.toCharArray(); user.roles = new HashSet<>(Arrays.asList(roles)); users.put(name, user); } @Override public Account verify(Account account) { // Just re-use the existing account. return account; } @Override public Account verify(String id, Credential credential) { Account account = users.get(id); if (account != null && verifyCredential(account, credential)) { return account; } return null; } @Override public Account verify(Credential credential) { return null; } private boolean verifyCredential(Account account, Credential credential) { // This approach should never be copied in a realm IdentityManager. if (account instanceof UserAccount) { if (credential instanceof PasswordCredential) { char[] expectedPassword = ((UserAccount) account).password; char[] suppliedPassword = ((PasswordCredential) credential).getPassword(); return Arrays.equals(expectedPassword, suppliedPassword); } else if (credential instanceof DigestCredential) { DigestCredential digCred = (DigestCredential) credential; MessageDigest digest = null; try { digest = digCred.getAlgorithm().getMessageDigest(); digest.update(account.getPrincipal().getName().getBytes(UTF_8)); digest.update((byte) ':'); digest.update(digCred.getRealm().getBytes(UTF_8)); digest.update((byte) ':'); char[] expectedPassword = ((UserAccount) account).password; digest.update(new String(expectedPassword).getBytes(UTF_8)); return digCred.verifyHA1(HexConverter.convertToHexBytes(digest.digest())); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Unsupported Algorithm", e); } finally { digest.reset(); } } } return false; } private static class UserAccount implements Account { // In no way whatsoever should a class like this be considered a good idea for a real IdentityManager implementation, // this is for testing only. String name; char[] password; Set roles; private final Principal principal = new Principal() { @Override public String getName() { return name; } }; @Override public Principal getPrincipal() { return principal; } @Override public Set getRoles() { return roles; } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/custom/000077500000000000000000000000001420065311100307475ustar00rootroot00000000000000CustomAuthenticationMechanism.java000066400000000000000000000056331420065311100375410ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/custom/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.custom; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.AuthenticationMechanismFactory; import io.undertow.security.api.SecurityContext; import io.undertow.security.idm.IdentityManager; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.form.FormParserFactory; import io.undertow.servlet.handlers.security.ServletFormAuthenticationMechanism; import io.undertow.util.Methods; import java.util.Map; /** *

    * Custom Authentication Mechanism has a slight change from the {@link io.undertow.security.impl.FormAuthenticationMechanism} that the posting of * username/password happens to a resource ending with custom_security_check rather than j_security_check in the form * authentication. *

    *

    * This allows to test the injection of an {@link AuthenticationMechanism} to the {@link io.undertow.servlet.core.DeploymentManagerImpl} API *

    * * @author anil saldhana * @since May 13, 2013 */ public class CustomAuthenticationMechanism extends ServletFormAuthenticationMechanism { public static final String POST_LOCATION = "custom_security_check"; public static final Factory FACTORY = new Factory(); public CustomAuthenticationMechanism(String name, String loginPage, String errorPage) { super(FormParserFactory.builder().build(), name, loginPage, errorPage); } @Override public AuthenticationMechanismOutcome authenticate(final HttpServerExchange exchange, final SecurityContext securityContext) { if (exchange.getRequestURI().endsWith(POST_LOCATION) && exchange.getRequestMethod().equals(Methods.POST)) { return runFormAuth(exchange, securityContext); } else { return AuthenticationMechanismOutcome.NOT_ATTEMPTED; } } public static final class Factory implements AuthenticationMechanismFactory { @Override public AuthenticationMechanism create(String mechanismName, IdentityManager identityManager, FormParserFactory formParserFactory, Map properties) { return new CustomAuthenticationMechanism(mechanismName, properties.get(LOGIN_PAGE), properties.get(ERROR_PAGE)); } } } CustomEncodingAuthenticationMechanism.java000066400000000000000000000064471420065311100412140ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/custom/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.custom; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.AuthenticationMechanismFactory; import io.undertow.security.idm.IdentityManager; import io.undertow.server.handlers.form.FormEncodedDataDefinition; import io.undertow.server.handlers.form.FormParserFactory; import io.undertow.server.handlers.form.FormParserFactory.ParserDefinition; import io.undertow.servlet.handlers.security.ServletFormAuthenticationMechanism; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Map; /** *

    * Custom Authentication Mechanism that will share the encoding set by DeploymentManagementImpl */ public class CustomEncodingAuthenticationMechanism extends ServletFormAuthenticationMechanism { public static final Factory FACTORY = new Factory(); public String charset = null; public CustomEncodingAuthenticationMechanism(FormParserFactory formParserFactory, String name, String loginPage, String errorPage) { super(formParserFactory, name, loginPage, errorPage); this.charset = getcharset(formParserFactory); } public String getcharset(FormParserFactory formParserFactory) { ParserDefinition parserDefinition = null; ParserDefinition[] parserDefinitions = (ParserDefinition[]) getPrivateField(formParserFactory, "parserDefinitions"); if(parserDefinitions != null) { parserDefinition = parserDefinitions[0]; if(parserDefinition instanceof FormEncodedDataDefinition) { FormEncodedDataDefinition formEncodedDataDefinition = (FormEncodedDataDefinition) parserDefinition; return (String) getPrivateField(formEncodedDataDefinition, "defaultEncoding"); } } return null; } private Object getPrivateField(Object object, String fieldName) { try { Field field = object.getClass().getDeclaredField(fieldName); if (Modifier.isPrivate(field.getModifiers())) { field.setAccessible(true); } return field.get(object); } catch (Exception e) { return null; } } public static final class Factory implements AuthenticationMechanismFactory { @Override public AuthenticationMechanism create(String mechanismName, IdentityManager identityManager, FormParserFactory formParserFactory, Map properties) { return new CustomEncodingAuthenticationMechanism(formParserFactory, mechanismName, properties.get(LOGIN_PAGE), properties.get(ERROR_PAGE)); } } } ServletCustomAuthFormEncodingTestCase.java000066400000000000000000000136551420065311100411350ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/custom/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.custom; import static org.junit.Assert.assertEquals; import java.util.List; import javax.servlet.ServletException; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.ServletSecurityInfo; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.security.SendUsernameServlet; import io.undertow.servlet.test.security.constraint.ServletIdentityManager; import io.undertow.servlet.test.security.form.FormLoginServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; /** * Test case that validates the use of the DeploymentManagerImpl authMechanism override * @author Stuart Douglas * @author Anil Saldhana */ @RunWith(DefaultServer.class) public class ServletCustomAuthFormEncodingTestCase { @Test public void testAuthFormEncoding() throws ServletException { final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("servlet", SendUsernameServlet.class) .setServletSecurityInfo(new ServletSecurityInfo() .addRoleAllowed("role1")) .addMapping("/secured/*"); ServletInfo s1 = new ServletInfo("loginPage", FormLoginServlet.class) .setServletSecurityInfo(new ServletSecurityInfo() .addRoleAllowed("group1")) .addMapping("/FormLoginServlet"); ServletIdentityManager identityManager = new ServletIdentityManager(); identityManager.addUser("user1", "password1", "role1"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setIdentityManager(identityManager) .setLoginConfig(new LoginConfig("FORM", "Test Realm", "/FormLoginServlet", "/error.html")) .addServlets(s, s1) .addAuthenticationMechanism("FORM", CustomEncodingAuthenticationMechanism.FACTORY); DeploymentManager manager = container.addDeployment(builder); CustomEncodingAuthenticationMechanism authenticationMechanism; try { manager.deploy(); authenticationMechanism = getCustomeAuth(manager); assertEquals("ISO-8859-1", authenticationMechanism.charset); } finally { manager.undeploy(); } builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext2.war") .setIdentityManager(identityManager) .setLoginConfig(new LoginConfig("FORM", "Test Realm", "/FormLoginServlet", "/error.html")) .addServlets(s, s1) .setDefaultRequestEncoding("UTF-8") .addAuthenticationMechanism("FORM", CustomEncodingAuthenticationMechanism.FACTORY); manager = container.addDeployment(builder); try { manager.deploy(); authenticationMechanism = getCustomeAuth(manager); assertEquals("UTF-8", authenticationMechanism.charset); } finally { manager.undeploy(); } builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext3.war") .setIdentityManager(identityManager) .setLoginConfig(new LoginConfig("FORM", "Test Realm", "/FormLoginServlet", "/error.html")) .addServlets(s, s1) .setDefaultEncoding("UTF-8") .addAuthenticationMechanism("FORM", CustomEncodingAuthenticationMechanism.FACTORY); manager = container.addDeployment(builder); try { manager.deploy(); authenticationMechanism = getCustomeAuth(manager); assertEquals("UTF-8", authenticationMechanism.charset); } finally { manager.undeploy(); } } private CustomEncodingAuthenticationMechanism getCustomeAuth(DeploymentManager manager) { List authenticationMechanismList = manager.getDeployment().getAuthenticationMechanisms(); for (AuthenticationMechanism authenticationMechanism : authenticationMechanismList) { if (authenticationMechanism instanceof CustomEncodingAuthenticationMechanism) { return (CustomEncodingAuthenticationMechanism) authenticationMechanism; } } return null; } } ServletCustomAuthTestCase.java000066400000000000000000000140021420065311100366250ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/custom/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.custom; import static org.junit.Assert.assertEquals; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.ServletSecurityInfo; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.security.SendUsernameServlet; import io.undertow.servlet.test.security.constraint.ServletIdentityManager; import io.undertow.servlet.test.security.form.FormLoginServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.servlet.ServletException; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.ProtocolException; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultRedirectStrategy; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HttpContext; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * Test case that validates the use of the DeploymentManagerImpl authMechanism override * @author Stuart Douglas * @author Anil Saldhana */ @RunWith(DefaultServer.class) public class ServletCustomAuthTestCase { public static final String HELLO_WORLD = "Hello World"; @BeforeClass public static void setup() throws ServletException { final PathHandler path = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("servlet", SendUsernameServlet.class) .setServletSecurityInfo(new ServletSecurityInfo() .addRoleAllowed("role1")) .addMapping("/secured/*"); ServletInfo s1 = new ServletInfo("loginPage", FormLoginServlet.class) .setServletSecurityInfo(new ServletSecurityInfo() .addRoleAllowed("group1")) .addMapping("/FormLoginServlet"); ServletIdentityManager identityManager = new ServletIdentityManager(); identityManager.addUser("user1", "password1", "role1"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setIdentityManager(identityManager) .setLoginConfig(new LoginConfig("FORM", "Test Realm", "/FormLoginServlet", "/error.html")) .addServlets(s, s1) .addAuthenticationMechanism("FORM", CustomAuthenticationMechanism.FACTORY); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); path.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(path); } @Test public void testServletCustomFormAuth() throws IOException { TestHttpClient client = new TestHttpClient(); client.setRedirectStrategy(new DefaultRedirectStrategy() { @Override public boolean isRedirected(final HttpRequest request, final HttpResponse response, final HttpContext context) throws ProtocolException { if (response.getStatusLine().getStatusCode() == StatusCodes.FOUND) { return true; } return super.isRedirected(request, response, context); } }); try { final String uri = DefaultServer.getDefaultServerURL() + "/servletContext/secured/test"; HttpGet get = new HttpGet(uri); HttpResponse result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("j_security_check")); BasicNameValuePair[] pairs = new BasicNameValuePair[]{new BasicNameValuePair("j_username", "user1"), new BasicNameValuePair("j_password", "password1")}; final List data = new ArrayList<>(); data.addAll(Arrays.asList(pairs)); HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext/" + CustomAuthenticationMechanism.POST_LOCATION ); post.setEntity(new UrlEncodedFormEntity(data)); result = client.execute(post); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("user1", response); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/digest/000077500000000000000000000000001420065311100307145ustar00rootroot00000000000000DigestAuthTestCase.java000066400000000000000000000177331420065311100352100ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/digest/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.digest; import io.undertow.security.idm.DigestAlgorithm; import io.undertow.security.impl.DigestAuthorizationToken; import io.undertow.security.impl.DigestWWWAuthenticateToken; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.SecurityInfo.EmptyRoleSemantic; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.WebResourceCollection; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.security.SendAuthTypeServlet; import io.undertow.servlet.test.security.SendUsernameServlet; import io.undertow.servlet.test.security.constraint.ServletIdentityManager; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.HexConverter; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.Map; import javax.servlet.ServletException; import static io.undertow.util.Headers.AUTHORIZATION; import static io.undertow.util.Headers.DIGEST; import static io.undertow.util.Headers.WWW_AUTHENTICATE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Test case to test authentication using HTTP Digest. * * @author Darran Lofthouse */ @RunWith(DefaultServer.class) public class DigestAuthTestCase { private static final String REALM_NAME = "Servlet_Realm"; private static final Charset UTF_8 = StandardCharsets.UTF_8; @BeforeClass public static void setup() throws ServletException { final PathHandler path = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo usernameServlet = new ServletInfo("Username Servlet", SendUsernameServlet.class) .addMapping("/secured/username"); ServletInfo authTypeServlet = new ServletInfo("Auth Type Servlet", SendAuthTypeServlet.class) .addMapping("/secured/authType"); ServletIdentityManager identityManager = new ServletIdentityManager(); identityManager.addUser("user1", "password1", "role1"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setIdentityManager(identityManager) .setLoginConfig(new LoginConfig("DIGEST", REALM_NAME)) .addServlets(usernameServlet, authTypeServlet); builder.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection() .addUrlPattern("/secured/*")) .addRoleAllowed("role1") .setEmptyRoleSemantic(EmptyRoleSemantic.DENY)); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); path.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(path); } @Test public void testUserName() throws Exception { testCall("username", "user1"); } @Test public void testAuthType() throws Exception { testCall("authType", "DIGEST"); } public void testCall(final String path, final String expectedResponse) throws Exception { TestHttpClient client = new TestHttpClient(); String servletPath = "/servletContext/secured/" + path; String url = DefaultServer.getDefaultServerURL() + servletPath; HttpGet get = new HttpGet(url); HttpResponse result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); assertEquals(1, values.length); String value = values[0].getValue(); assertTrue(value.startsWith(DIGEST.toString())); Map parsedHeader = DigestWWWAuthenticateToken.parseHeader(value.substring(7)); assertEquals(REALM_NAME, parsedHeader.get(DigestWWWAuthenticateToken.REALM)); assertEquals(DigestAlgorithm.MD5.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.ALGORITHM)); assertTrue(parsedHeader.containsKey(DigestWWWAuthenticateToken.MESSAGE_QOP)); String nonce = parsedHeader.get(DigestWWWAuthenticateToken.NONCE); String clientResponse = createResponse("user1", REALM_NAME, "password1", "GET", servletPath, nonce); client = new TestHttpClient(); get = new HttpGet(url); StringBuilder sb = new StringBuilder(DIGEST.toString()); sb.append(" "); sb.append(DigestAuthorizationToken.USERNAME.getName()).append("=").append("\"user1\"").append(","); sb.append(DigestAuthorizationToken.REALM.getName()).append("=\"").append(REALM_NAME).append("\","); sb.append(DigestAuthorizationToken.NONCE.getName()).append("=\"").append(nonce).append("\","); sb.append(DigestAuthorizationToken.DIGEST_URI.getName()).append("=\"" + servletPath + "\","); sb.append(DigestAuthorizationToken.RESPONSE.getName()).append("=\"").append(clientResponse).append("\""); get.addHeader(AUTHORIZATION.toString(), sb.toString()); result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); assertEquals(expectedResponse, response); } /** * Creates a response value from the supplied parameters. * * @return The generated Hex encoded MD5 digest based response. */ private String createResponse(final String userName, final String realm, final String password, final String method, final String uri, final String nonce) throws Exception { MessageDigest digest = MessageDigest.getInstance("MD5"); digest.update(userName.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(realm.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(password.getBytes(UTF_8)); byte[] ha1 = HexConverter.convertToHexBytes(digest.digest()); digest.update(method.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(uri.getBytes(UTF_8)); byte[] ha2 = HexConverter.convertToHexBytes(digest.digest()); digest.update(ha1); digest.update((byte) ':'); digest.update(nonce.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(ha2); return HexConverter.convertToHexString(digest.digest()); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/form/000077500000000000000000000000001420065311100304005ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/form/EchoServlet.java000066400000000000000000000025221420065311100334670ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.form; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author Stuart Douglas */ public class EchoServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { byte[] buf = new byte[100]; int res; while ((res = req.getInputStream().read(buf)) > 0) { resp.getOutputStream().write(buf, 0, res); } } } FormAuthenticationRootContextRedirectTestCase.java000066400000000000000000000120741420065311100423220ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/form/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.form; import javax.servlet.ServletException; import java.io.IOException; import java.net.URI; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.ServletSecurityInfo; import io.undertow.servlet.api.WebResourceCollection; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.security.constraint.ServletIdentityManager; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestResourceLoader; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.protocol.HttpClientContext; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.assertEquals; @RunWith(DefaultServer.class) public class FormAuthenticationRootContextRedirectTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler path = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo securedIndexRequestDumper = new ServletInfo("SecuredIndexRequestDumperServlet", SaveOriginalPostRequestTestCase.RequestDumper.class) .setServletSecurityInfo(new ServletSecurityInfo() .addRoleAllowed("role1")) .addMapping("/index.html"); ServletInfo loginFormServlet = new ServletInfo("loginPage", FormLoginServlet.class) .setServletSecurityInfo(new ServletSecurityInfo() .addRoleAllowed("group1")) .addMapping("/FormLoginServlet"); ServletIdentityManager identityManager = new ServletIdentityManager(); identityManager.addUser("user1", "password1", "role1"); SecurityConstraint securityConstraint = new SecurityConstraint(); WebResourceCollection webResourceCollection = new WebResourceCollection(); webResourceCollection.addUrlPattern("/*"); securityConstraint.addWebResourceCollection(webResourceCollection); securityConstraint.addRoleAllowed("role1"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setIdentityManager(identityManager) .addWelcomePage("index.html") .setResourceManager(new TestResourceLoader(SaveOriginalPostRequestTestCase.class)) .addSecurityConstraint(securityConstraint) .setLoginConfig(new LoginConfig("FORM", "Test Realm", "/FormLoginServlet", "/error.html")) .addServlets(loginFormServlet, securedIndexRequestDumper); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); path.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(path); } @Test public void test2() throws IOException { TestHttpClient client = new TestHttpClient(); HttpClientContext context = HttpClientContext.create(); String uri = DefaultServer.getDefaultServerURL() + "/servletContext"; HttpGet request = new HttpGet(uri); HttpResponse result = client.execute(request, context); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); assertEquals(DefaultServer.getDefaultServerURL() + "/servletContext/", requestedUri(context, uri)); Assert.assertTrue(HttpClientUtils.readResponse(result).startsWith("j_security_check")); } private String requestedUri(HttpClientContext context, String original) { if (context.getRedirectLocations() == null) { return original; } URI uri = context.getRedirectLocations().get(context.getRedirectLocations().size() - 1); return (uri == null)?original:uri.toString(); } } FormLoginServlet.java000066400000000000000000000026501420065311100344300ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/form/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.form; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class FormLoginServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write(resp.encodeRedirectURL("j_security_check")); } @Override protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } } RequestParamEchoServlet.java000066400000000000000000000027751420065311100357540ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/form/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.form; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.OutputStream; /** * Servlet that echoes back a request param called "param" * @author Anil Saldhana * @since December 18, 2013 */ public class RequestParamEchoServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String paramValue = req.getParameter("param"); if(paramValue == null){ paramValue = "Not Found"; } OutputStream os = resp.getOutputStream(); os.write(paramValue.getBytes()); } } SaveOriginalPostRequestTestCase.java000066400000000000000000000244411420065311100374270ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/form/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.form; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.ServletSecurityInfo; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.security.constraint.ServletIdentityManager; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestResourceLoader; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.ProtocolException; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultRedirectStrategy; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HttpContext; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** *

    Tests if a request made to a secured resource is saved before the client is redirect to the login form. Once the authentication is * done, the server should restore the original/saved request.

    * * @author Pedro Igor */ @RunWith(DefaultServer.class) public class SaveOriginalPostRequestTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler path = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo securedRequestDumper = new ServletInfo("SecuredRequestDumperServlet", RequestDumper.class) .setServletSecurityInfo(new ServletSecurityInfo() .addRoleAllowed("role1")) .addMapping("/secured/dumpRequest"); ServletInfo securedIndexRequestDumper = new ServletInfo("SecuredIndexRequestDumperServlet", RequestDumper.class) .setServletSecurityInfo(new ServletSecurityInfo() .addRoleAllowed("role1")) .addMapping("/index.html"); ServletInfo unsecuredRequestDumper = new ServletInfo("UnsecuredRequestDumperServlet", RequestDumper.class) .addMapping("/dumpRequest"); ServletInfo loginFormServlet = new ServletInfo("loginPage", FormLoginServlet.class) .setServletSecurityInfo(new ServletSecurityInfo() .addRoleAllowed("group1")) .addMapping("/FormLoginServlet"); ServletIdentityManager identityManager = new ServletIdentityManager(); identityManager.addUser("user1", "password1", "role1"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setIdentityManager(identityManager) .addWelcomePage("index.html") .setResourceManager(new TestResourceLoader(SaveOriginalPostRequestTestCase.class)) .setLoginConfig(new LoginConfig("FORM", "Test Realm", "/FormLoginServlet", "/error.html")) .addServlets(securedRequestDumper, unsecuredRequestDumper, loginFormServlet, securedIndexRequestDumper); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); path.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(path); } @Test public void testParametersFromOriginalPostRequest() throws IOException { TestHttpClient client = createHttpClient(); // let's test if a usual POST request have its parameters dumped in the response HttpResponse result = executePostRequest(client, "/servletContext/dumpRequest", new BasicNameValuePair("param1", "param1Value"), new BasicNameValuePair("param2", "param2Value")); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); assertTrue(response.contains("param1=param1Value")); assertTrue(response.contains("param2=param2Value")); // this request should be saved and the client redirect to the login form. result = executePostRequest(client, "/servletContext/secured/dumpRequest", new BasicNameValuePair("securedParam1", "securedParam1Value"), new BasicNameValuePair("securedParam2", "securedParam2Value")); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertTrue(HttpClientUtils.readResponse(result).startsWith("j_security_check")); // let's perform a successful authentication and get the request restored result = executePostRequest(client, "/servletContext/j_security_check", new BasicNameValuePair("j_username", "user1"), new BasicNameValuePair("j_password", "password1")); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); // let's check if the original request was saved, including its parameters. assertTrue(response.contains("securedParam1=securedParam1Value")); assertTrue(response.contains("securedParam2=securedParam2Value")); } @Test public void testSavedRequestWithWelcomeFile() throws IOException { TestHttpClient client = createHttpClient(); // this request should be saved and the client redirect to the login form. HttpResponse result = executePostRequest(client, "/servletContext/", new BasicNameValuePair("securedParam1", "securedParam1Value"), new BasicNameValuePair("securedParam2", "securedParam2Value")); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertTrue(HttpClientUtils.readResponse(result).startsWith("j_security_check")); // let's perform a successful authentication and get the request restored result = executePostRequest(client, "/servletContext/j_security_check", new BasicNameValuePair("j_username", "user1"), new BasicNameValuePair("j_password", "password1")); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); // let's check if the original request was saved, including its parameters. assertTrue(response.contains("securedParam1=securedParam1Value")); assertTrue(response.contains("securedParam2=securedParam2Value")); } private TestHttpClient createHttpClient() { TestHttpClient client = new TestHttpClient(); client.setRedirectStrategy(new DefaultRedirectStrategy() { @Override public boolean isRedirected(final HttpRequest request, final HttpResponse response, final HttpContext context) throws ProtocolException { if (response.getStatusLine().getStatusCode() == StatusCodes.FOUND) { return true; } return super.isRedirected(request, response, context); } }); return client; } private HttpResponse executePostRequest(TestHttpClient client, String uri, BasicNameValuePair... parameters) throws IOException { HttpPost request = new HttpPost(DefaultServer.getDefaultServerURL() + uri); request.setEntity(new UrlEncodedFormEntity(new ArrayList(Arrays.asList(parameters)))); return client.execute(request); } static class RequestDumper extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { dumpRequest(req, resp); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { dumpRequest(req, resp); } private void dumpRequest(HttpServletRequest req, HttpServletResponse resp) throws IOException { StringBuilder buffer = new StringBuilder(); PrintWriter writer = resp.getWriter(); buffer.append("Method: " + req.getMethod() + "\n"); Enumeration parameterNames = req.getParameterNames(); buffer.append("Parameters: "); while (parameterNames.hasMoreElements()) { String parameterName = parameterNames.nextElement(); buffer.append(parameterName + "=" + req.getParameter(parameterName)); buffer.append("/"); } writer.write(buffer.toString()); } } } ServletFormAuthDefaultPageTestCase.java000066400000000000000000000260311420065311100400160ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/form/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.form; import static org.junit.Assert.assertEquals; import io.undertow.security.api.AuthenticationMode; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.AuthMethodConfig; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.ServletSecurityInfo; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.security.SendUsernameServlet; import io.undertow.servlet.test.security.constraint.ServletIdentityManager; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletException; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.ProtocolException; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultRedirectStrategy; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HttpContext; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Mark Banierink */ @RunWith(DefaultServer.class) public class ServletFormAuthDefaultPageTestCase { public static final String HELLO_WORLD = "Hello World"; private static final String DEFAULT_PAGE = "/main.html"; @BeforeClass public static void setup() throws ServletException { final PathHandler path = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("servlet", SendUsernameServlet.class) .setServletSecurityInfo(new ServletSecurityInfo() .addRoleAllowed("role1")) .addMapping("/secured/*"); ServletInfo echo = new ServletInfo("echo", EchoServlet.class) .setServletSecurityInfo(new ServletSecurityInfo() .addRoleAllowed("role1")) .addMapping("/secured/echo"); ServletInfo echoParam = new ServletInfo("echoParam", RequestParamEchoServlet.class) .setServletSecurityInfo(new ServletSecurityInfo() .addRoleAllowed("role1")) .addMapping("/secured/echoParam"); ServletInfo s1 = new ServletInfo("loginPage", FormLoginServlet.class) .setServletSecurityInfo(new ServletSecurityInfo() .addRoleAllowed("group1")) .addMapping("/FormLoginServlet"); ServletIdentityManager identityManager = new ServletIdentityManager(); identityManager.addUser("user1", "password1", "role1"); Map props = new HashMap<>(); props.put("default_page", "/main.html"); props.put("override_initial", "true"); AuthMethodConfig authMethodConfig = new AuthMethodConfig("FORM", props); LoginConfig loginConfig = new LoginConfig("Test Realm", "/FormLoginServlet", "/error.html").addFirstAuthMethod(authMethodConfig); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setAuthenticationMode(AuthenticationMode.CONSTRAINT_DRIVEN) .setIdentityManager(identityManager) .setLoginConfig(loginConfig) .addServlets(s, s1, echo,echoParam); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); path.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(path); } @Test public void testServletFormAuth() throws IOException { TestHttpClient client = new TestHttpClient(); client.setRedirectStrategy(new DefaultRedirectStrategy() { @Override public boolean isRedirected(final HttpRequest request, final HttpResponse response, final HttpContext context) throws ProtocolException { if (response.getStatusLine().getStatusCode() == StatusCodes.FOUND) { return true; } if (request.getRequestLine().getUri().equals(DEFAULT_PAGE)) { response.setStatusCode(StatusCodes.OK); // Skip redirecting, because the resource isn't available in this test return false; } return super.isRedirected(request, response, context); } }); try { final String uri = DefaultServer.getDefaultServerURL() + "/servletContext/secured/test"; HttpGet get = new HttpGet(uri); HttpResponse result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("j_security_check")); BasicNameValuePair[] pairs = new BasicNameValuePair[]{new BasicNameValuePair("j_username", "user1"), new BasicNameValuePair("j_password", "password1")}; final List data = new ArrayList<>(); data.addAll(Arrays.asList(pairs)); HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext/j_security_check;jsessionid=dsjahfklsahdfjklsa"); post.setEntity(new UrlEncodedFormEntity(data)); result = client.execute(post); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testServletFormAuthWithSavedPostBody() throws IOException { TestHttpClient client = new TestHttpClient(); client.setRedirectStrategy(new DefaultRedirectStrategy() { @Override public boolean isRedirected(final HttpRequest request, final HttpResponse response, final HttpContext context) throws ProtocolException { if (response.getStatusLine().getStatusCode() == StatusCodes.FOUND) { return true; } if (request.getRequestLine().getUri().equals(DEFAULT_PAGE)) { response.setStatusCode(StatusCodes.OK); // Skip redirecting, because the resource isn't available in this test return false; } return super.isRedirected(request, response, context); } }); try { final String uri = DefaultServer.getDefaultServerURL() + "/servletContext/secured/echo"; HttpPost post = new HttpPost(uri); post.setEntity(new StringEntity("String Entity")); HttpResponse result = client.execute(post); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("j_security_check")); BasicNameValuePair[] pairs = new BasicNameValuePair[]{new BasicNameValuePair("j_username", "user1"), new BasicNameValuePair("j_password", "password1")}; final List data = new ArrayList<>(); data.addAll(Arrays.asList(pairs)); post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext/j_security_check"); post.setEntity(new UrlEncodedFormEntity(data)); result = client.execute(post); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testServletFormAuthWithoutSavedPostBody() throws IOException { TestHttpClient client = new TestHttpClient(); client.setRedirectStrategy(new DefaultRedirectStrategy() { @Override public boolean isRedirected(final HttpRequest request, final HttpResponse response, final HttpContext context) throws ProtocolException { if (response.getStatusLine().getStatusCode() == StatusCodes.FOUND) { return true; } if (request.getRequestLine().getUri().equals(DEFAULT_PAGE)) { response.setStatusCode(StatusCodes.OK); // Skip redirecting, because the resource isn't available in this test return false; } // force the test to fail response.setStatusCode(StatusCodes.EXPECTATION_FAILED); return super.isRedirected(request, response, context); } }); try { BasicNameValuePair[] pairs = new BasicNameValuePair[]{new BasicNameValuePair("j_username", "user1"), new BasicNameValuePair("j_password", "password1")}; final List data = new ArrayList<>(); data.addAll(Arrays.asList(pairs)); HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext/j_security_check"); post.setEntity(new UrlEncodedFormEntity(data)); HttpResponse result = client.execute(post); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("", response); } finally { client.getConnectionManager().shutdown(); } } } ServletFormAuthTestCase.java000066400000000000000000000304771420065311100357250ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/form/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.form; import io.undertow.servlet.api.AuthMethodConfig; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletException; import io.undertow.security.api.AuthenticationMode; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.ServletSecurityInfo; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.security.SendUsernameServlet; import io.undertow.servlet.test.security.constraint.ServletIdentityManager; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.ProtocolException; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultRedirectStrategy; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HttpContext; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.assertEquals; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ServletFormAuthTestCase { public static final String HELLO_WORLD = "Hello World"; private static final String DEFAULT_PAGE = "/main.html"; @BeforeClass public static void setup() throws ServletException { final PathHandler path = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("servlet", SendUsernameServlet.class) .setServletSecurityInfo(new ServletSecurityInfo() .addRoleAllowed("role1")) .addMapping("/secured/*"); ServletInfo echo = new ServletInfo("echo", EchoServlet.class) .setServletSecurityInfo(new ServletSecurityInfo() .addRoleAllowed("role1")) .addMapping("/secured/echo"); ServletInfo echoParam = new ServletInfo("echoParam", RequestParamEchoServlet.class) .setServletSecurityInfo(new ServletSecurityInfo() .addRoleAllowed("role1")) .addMapping("/secured/echoParam"); ServletInfo s1 = new ServletInfo("loginPage", FormLoginServlet.class) .setServletSecurityInfo(new ServletSecurityInfo() .addRoleAllowed("group1")) .addMapping("/FormLoginServlet"); ServletIdentityManager identityManager = new ServletIdentityManager(); identityManager.addUser("user1", "password1", "role1"); Map props = new HashMap<>(); props.put("default_page", DEFAULT_PAGE); AuthMethodConfig authMethodConfig = new AuthMethodConfig("FORM", props); LoginConfig loginConfig = new LoginConfig("Test Realm", "/FormLoginServlet", "/error.html").addFirstAuthMethod(authMethodConfig); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setAuthenticationMode(AuthenticationMode.CONSTRAINT_DRIVEN) .setIdentityManager(identityManager) .setLoginConfig(loginConfig) .addServlets(s, s1, echo,echoParam); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); path.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(path); } @Test public void testServletFormAuth() throws IOException { TestHttpClient client = new TestHttpClient(); client.setRedirectStrategy(new DefaultRedirectStrategy() { @Override public boolean isRedirected(final HttpRequest request, final HttpResponse response, final HttpContext context) throws ProtocolException { if (response.getStatusLine().getStatusCode() == StatusCodes.FOUND) { return true; } return super.isRedirected(request, response, context); } }); try { final String uri = DefaultServer.getDefaultServerURL() + "/servletContext/secured/test"; HttpGet get = new HttpGet(uri); HttpResponse result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("j_security_check")); BasicNameValuePair[] pairs = new BasicNameValuePair[]{new BasicNameValuePair("j_username", "user1"), new BasicNameValuePair("j_password", "password1")}; final List data = new ArrayList<>(); data.addAll(Arrays.asList(pairs)); HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext/j_security_check;jsessionid=dsjahfklsahdfjklsa"); post.setEntity(new UrlEncodedFormEntity(data)); result = client.execute(post); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("user1", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testServletFormAuthWithSavedPostBody() throws IOException { TestHttpClient client = new TestHttpClient(); client.setRedirectStrategy(new DefaultRedirectStrategy() { @Override public boolean isRedirected(final HttpRequest request, final HttpResponse response, final HttpContext context) throws ProtocolException { if (response.getStatusLine().getStatusCode() == StatusCodes.FOUND) { return true; } return super.isRedirected(request, response, context); } }); try { final String uri = DefaultServer.getDefaultServerURL() + "/servletContext/secured/echo"; HttpPost post = new HttpPost(uri); post.setEntity(new StringEntity("String Entity")); HttpResponse result = client.execute(post); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("j_security_check")); BasicNameValuePair[] pairs = new BasicNameValuePair[]{new BasicNameValuePair("j_username", "user1"), new BasicNameValuePair("j_password", "password1")}; final List data = new ArrayList<>(); data.addAll(Arrays.asList(pairs)); post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext/j_security_check"); post.setEntity(new UrlEncodedFormEntity(data)); result = client.execute(post); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("String Entity", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testServletFormAuthWithoutSavedPostBody() throws IOException { TestHttpClient client = new TestHttpClient(); client.setRedirectStrategy(new DefaultRedirectStrategy() { @Override public boolean isRedirected(final HttpRequest request, final HttpResponse response, final HttpContext context) throws ProtocolException { if (response.getStatusLine().getStatusCode() == StatusCodes.FOUND) { return true; } if (request.getRequestLine().getUri().equals(DEFAULT_PAGE)) { response.setStatusCode(StatusCodes.OK); // Skip redirecting, because the resource isn't available in this test return false; } // force the test to fail response.setStatusCode(StatusCodes.EXPECTATION_FAILED); return super.isRedirected(request, response, context); } }); try { BasicNameValuePair[] pairs = new BasicNameValuePair[]{new BasicNameValuePair("j_username", "user1"), new BasicNameValuePair("j_password", "password1")}; final List data = new ArrayList<>(); data.addAll(Arrays.asList(pairs)); HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext/j_security_check"); post.setEntity(new UrlEncodedFormEntity(data)); HttpResponse result = client.execute(post); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testServletFormAuthWithOriginalRequestParams() throws IOException { TestHttpClient client = new TestHttpClient(); client.setRedirectStrategy(new DefaultRedirectStrategy() { @Override public boolean isRedirected(final HttpRequest request, final HttpResponse response, final HttpContext context) throws ProtocolException { if (response.getStatusLine().getStatusCode() == StatusCodes.FOUND) { return true; } return super.isRedirected(request, response, context); } }); try { final String uri = DefaultServer.getDefaultServerURL() + "/servletContext/secured/echoParam?param=developer"; HttpPost post = new HttpPost(uri); post.setEntity(new StringEntity("String Entity")); HttpResponse result = client.execute(post); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("j_security_check")); BasicNameValuePair[] pairs = new BasicNameValuePair[]{new BasicNameValuePair("j_username", "user1"), new BasicNameValuePair("j_password", "password1")}; final List data = new ArrayList<>(); data.addAll(Arrays.asList(pairs)); post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext/j_security_check"); post.setEntity(new UrlEncodedFormEntity(data)); result = client.execute(post); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); assertEquals("developer", response); } finally { client.getConnectionManager().shutdown(); } } } ServletFormAuthURLRewriteTestCase.java000066400000000000000000000245441420065311100376500ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/form/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.form; import static org.junit.Assert.assertEquals; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import javax.servlet.ServletException; import javax.servlet.SessionTrackingMode; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.ProtocolException; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultRedirectStrategy; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HttpContext; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.security.api.AuthenticationMode; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.ServletSecurityInfo; import io.undertow.servlet.api.ServletSessionConfig; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.security.SendUsernameServlet; import io.undertow.servlet.test.security.constraint.ServletIdentityManager; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ServletFormAuthURLRewriteTestCase { public static final String HELLO_WORLD = "Hello World"; @BeforeClass public static void setup() throws ServletException { final PathHandler path = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("servlet", SendUsernameServlet.class) .setServletSecurityInfo(new ServletSecurityInfo() .addRoleAllowed("role1")) .addMapping("/secured/*"); ServletInfo echo = new ServletInfo("echo", EchoServlet.class) .setServletSecurityInfo(new ServletSecurityInfo() .addRoleAllowed("role1")) .addMapping("/secured/echo"); ServletInfo echoParam = new ServletInfo("echoParam", RequestParamEchoServlet.class) .setServletSecurityInfo(new ServletSecurityInfo() .addRoleAllowed("role1")) .addMapping("/secured/echoParam"); ServletInfo s1 = new ServletInfo("loginPage", FormLoginServlet.class) .setServletSecurityInfo(new ServletSecurityInfo() .addRoleAllowed("group1")) .addMapping("/FormLoginServlet"); ServletIdentityManager identityManager = new ServletIdentityManager(); identityManager.addUser("user1", "password1", "role1"); DeploymentInfo builder = new DeploymentInfo() .setServletSessionConfig(new ServletSessionConfig().setSessionTrackingModes(Collections.singleton(SessionTrackingMode.URL))) .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setAuthenticationMode(AuthenticationMode.CONSTRAINT_DRIVEN) .setIdentityManager(identityManager) .setLoginConfig(new LoginConfig("FORM", "Test Realm", "/FormLoginServlet", "/error.html")) .addServlets(s, s1, echo,echoParam); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); path.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(path); } @Test public void testServletFormAuth() throws IOException { TestHttpClient client = new TestHttpClient(); client.setRedirectStrategy(new DefaultRedirectStrategy() { @Override public boolean isRedirected(final HttpRequest request, final HttpResponse response, final HttpContext context) throws ProtocolException { if (response.getStatusLine().getStatusCode() == StatusCodes.FOUND) { return true; } return super.isRedirected(request, response, context); } }); try { final String uri = DefaultServer.getDefaultServerURL() + "/servletContext/secured/test"; HttpGet get = new HttpGet(uri); HttpResponse result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("j_security_check")); BasicNameValuePair[] pairs = new BasicNameValuePair[]{new BasicNameValuePair("j_username", "user1"), new BasicNameValuePair("j_password", "password1")}; final List data = new ArrayList<>(); data.addAll(Arrays.asList(pairs)); HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext/" + response); post.setEntity(new UrlEncodedFormEntity(data)); result = client.execute(post); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("user1", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testServletFormAuthWithSavedPostBody() throws IOException { TestHttpClient client = new TestHttpClient(); client.setRedirectStrategy(new DefaultRedirectStrategy() { @Override public boolean isRedirected(final HttpRequest request, final HttpResponse response, final HttpContext context) throws ProtocolException { if (response.getStatusLine().getStatusCode() == StatusCodes.FOUND) { return true; } return super.isRedirected(request, response, context); } }); try { final String uri = DefaultServer.getDefaultServerURL() + "/servletContext/secured/echo"; HttpPost post = new HttpPost(uri); post.setEntity(new StringEntity("String Entity")); HttpResponse result = client.execute(post); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("j_security_check")); BasicNameValuePair[] pairs = new BasicNameValuePair[]{new BasicNameValuePair("j_username", "user1"), new BasicNameValuePair("j_password", "password1")}; final List data = new ArrayList<>(); data.addAll(Arrays.asList(pairs)); post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext/" + response); post.setEntity(new UrlEncodedFormEntity(data)); result = client.execute(post); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("String Entity", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testServletFormAuthWithOriginalRequestParams() throws IOException { TestHttpClient client = new TestHttpClient(); client.setRedirectStrategy(new DefaultRedirectStrategy() { @Override public boolean isRedirected(final HttpRequest request, final HttpResponse response, final HttpContext context) throws ProtocolException { if (response.getStatusLine().getStatusCode() == StatusCodes.FOUND) { return true; } return super.isRedirected(request, response, context); } }); try { final String uri = DefaultServer.getDefaultServerURL() + "/servletContext/secured/echoParam?param=developer"; HttpPost post = new HttpPost(uri); post.setEntity(new StringEntity("String Entity")); HttpResponse result = client.execute(post); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("j_security_check")); BasicNameValuePair[] pairs = new BasicNameValuePair[]{new BasicNameValuePair("j_username", "user1"), new BasicNameValuePair("j_password", "password1")}; final List data = new ArrayList<>(); data.addAll(Arrays.asList(pairs)); post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext/" + response); post.setEntity(new UrlEncodedFormEntity(data)); result = client.execute(post); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); assertEquals("developer", response); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/form/error.html000066400000000000000000000000511420065311100324130ustar00rootroot00000000000000 error page undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/login/000077500000000000000000000000001420065311100305455ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/login/LoginFilter.java000066400000000000000000000040021420065311100336220ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.login; import io.undertow.util.StatusCodes; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class LoginFilter implements Filter { @Override public void init(final FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest)request; String username = req.getHeader("username"); String password = req.getHeader("password"); if(username == null) { chain.doFilter(request, response); return; } try { req.login(username, password); chain.doFilter(request, response); } catch (ServletException e) { ((HttpServletResponse)response).setStatus(StatusCodes.UNAUTHORIZED); } } @Override public void destroy() { } } ServletLoginTestCase.java000066400000000000000000000111641420065311100354050ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/login/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.login; import java.io.IOException; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.security.SendUsernameServlet; import io.undertow.servlet.test.security.constraint.ServletIdentityManager; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.assertEquals; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ServletLoginTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler path = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("servlet", SendUsernameServlet.class) .addMapping("/*"); ServletIdentityManager identityManager = new ServletIdentityManager(); identityManager.addUser("user1", "password1", "role1"); identityManager.addUser("user2", "password2", "role2"); identityManager.addUser("user3", "password3", "role3"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setIdentityManager(identityManager) .setLoginConfig(new LoginConfig("BASIC", "Test Realm")) .addServlet(s) .addFilter(new FilterInfo("LoginFilter", LoginFilter.class)) .addFilterServletNameMapping("LoginFilter", "servlet", DispatcherType.REQUEST); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); path.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(path); } @Test public void testHttpMethod() throws IOException { TestHttpClient client = new TestHttpClient(); final String url = DefaultServer.getDefaultServerURL() + "/servletContext/login"; try { HttpGet get = new HttpGet(url); get.addHeader("username", "bob"); get.addHeader("password", "bogus"); HttpResponse result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); get = new HttpGet(url); get.addHeader("username", "user1"); get.addHeader("password", "password1"); result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("user1", response); get = new HttpGet(url); result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("user1", response); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/ssl/000077500000000000000000000000001420065311100302365ustar00rootroot00000000000000ConfidentialityConstraintUrlMappingTestCase.java000066400000000000000000000117201420065311100416500ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/ssl/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.ssl; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.SecurityInfo.EmptyRoleSemantic; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.TransportGuaranteeType; import io.undertow.servlet.api.WebResourceCollection; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.security.SendSchemeServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestConfidentialPortManager; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import static org.junit.Assert.assertEquals; /** * Test case to test transport-guarantee enforcement. * * @author Darran Lofthouse */ @RunWith(DefaultServer.class) public class ConfidentialityConstraintUrlMappingTestCase { @BeforeClass public static void setup() throws Exception { DefaultServer.startSSLServer(); final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("servlet", SendSchemeServlet.class) .addMapping("/clear") .addMapping("/integral") .addMapping("/confidential"); DeploymentInfo info = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setConfidentialPortManager(TestConfidentialPortManager.INSTANCE) .addServlet(s); info.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection() .addUrlPattern("/integral")) .setTransportGuaranteeType(TransportGuaranteeType.INTEGRAL) .setEmptyRoleSemantic(EmptyRoleSemantic.PERMIT)); info.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection() .addUrlPattern("/confidential")) .setTransportGuaranteeType(TransportGuaranteeType.CONFIDENTIAL) .setEmptyRoleSemantic(EmptyRoleSemantic.PERMIT)); DeploymentManager manager = container.addDeployment(info); manager.deploy(); root.addPrefixPath(info.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @AfterClass public static void cleanUp() throws Exception { DefaultServer.stopSSLServer(); } @Test public void testClear() throws IOException { internalTest("/clear", "http"); } @Test public void testIntegral() throws IOException { internalTest("/integral", "https"); } @Test public void testConfidential() throws IOException { internalTest("/confidential", "https"); } private void internalTest(final String path, final String expectedScheme) throws IOException { TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); final String url = DefaultServer.getDefaultServerURL() + "/servletContext" + path; try { HttpGet get = new HttpGet(url); HttpResponse result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals(expectedScheme, response); } finally { client.getConnectionManager().shutdown(); } } } SSLAttributesServlet.java000066400000000000000000000050361420065311100351030ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/ssl/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.ssl; import java.io.IOException; import java.io.PrintWriter; import java.security.cert.X509Certificate; import javax.servlet.ServletException; import javax.servlet.annotation.HttpConstraint; import javax.servlet.annotation.ServletSecurity; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Tomaz Cerar (c) 2013 Red Hat Inc. */ @ServletSecurity(value = @HttpConstraint( )) public class SSLAttributesServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { PrintWriter pw = resp.getWriter(); if (req.getServletPath().equals("/id")) { pw.write(req.getAttribute("javax.servlet.request.ssl_session_id").toString()); } else if (req.getServletPath().equals("/key-size")) { pw.write(req.getAttribute("javax.servlet.request.key_size").toString()); } else if (req.getServletPath().equals("/cipher-suite")) { pw.write(req.getAttribute("javax.servlet.request.cipher_suite").toString()); } else if (req.getServletPath().equals("/cert")) { final X509Certificate[] attribute = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate"); if (attribute!=null){ pw.write(attribute[0].getSerialNumber().toString()); } } else if (req.getServletPath().equals("/cert-dn")) { final X509Certificate[] attribute = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate"); pw.write(attribute != null && attribute.length > 0? attribute[0].getSubjectDN().toString() : "null"); } pw.close(); } } SSLMetaDataProxyTestCase.java000066400000000000000000000107731420065311100355720ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/ssl/* * JBoss, Home of Professional Open Source. * Copyright 2020 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.ssl; import io.undertow.server.handlers.PathHandler; import io.undertow.server.handlers.SSLHeaderHandler; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.ProxyIgnore; import io.undertow.util.Headers; import java.io.InputStream; import java.security.KeyStore; import java.security.cert.X509Certificate; import java.util.Base64; import org.apache.http.Header; import org.apache.http.message.BasicHeader; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * Test SSLMetaData but emulating the proxy headers. This way some extra * specific tests sending crafted headers can be performed. Only needed in * normal execution, not needed in proxy profiles. * * @author rmartinc */ @ProxyIgnore @RunWith(DefaultServer.class) public class SSLMetaDataProxyTestCase extends SSLMetaDataTestCase { private static final String DUMMY_KEYSTORE = "dummy.keystore"; private static final String DUMMY_PASSWORD = "password"; private static X509Certificate dummyCertificate = null; @BeforeClass public static void setup() throws Exception { final PathHandler root = setupPathHandler(); // force the setup of the SSLHeaderHandler to add the SSL headers SSLHeaderHandler sslHeaders = new SSLHeaderHandler(root); DefaultServer.setRootHandler(sslHeaders); final InputStream stream = SSLMetaDataTestCase.class.getClassLoader().getResourceAsStream(DUMMY_KEYSTORE); KeyStore dummyKeystore = KeyStore.getInstance("JKS"); dummyKeystore.load(stream, DUMMY_PASSWORD.toCharArray()); dummyCertificate = (X509Certificate) dummyKeystore.getCertificate("dummy"); } @Test public void testWithHeaders() throws Exception { Base64.Encoder encoder = Base64.getMimeEncoder(); String cert = "-----BEGIN CERTIFICATE----- " + encoder.encodeToString(dummyCertificate.getEncoded()) + " -----END CERTIFICATE-----"; cert = cert.replace("\r\n", " "); String id = "1633d36df6f28e1325912b46f7d214f97370c39a6b3fc24ee374a76b3f9b0fba"; String cipher = "ECDHE-RSA-AES128-GCM-SHA256"; String keySize = "128"; Header[] headers = { new BasicHeader(Headers.SSL_SESSION_ID_STRING, id), new BasicHeader(Headers.SSL_CLIENT_CERT_STRING, cert), new BasicHeader(Headers.SSL_CIPHER_STRING, cipher), new BasicHeader(Headers.SSL_CIPHER_USEKEYSIZE_STRING, keySize) }; String response = internalTest("/cert-dn", headers); Assert.assertEquals(dummyCertificate.getSubjectDN().toString(), response); response = internalTest("/id", headers); Assert.assertEquals(id, response); response = internalTest("/cipher-suite", headers); Assert.assertEquals(cipher, response); response = internalTest("/key-size", headers); Assert.assertEquals(keySize, response); } @Test public void testNoCertWithHeaders() throws Exception { String cert = "(null)"; String id = "1633d36df6f28e1325912b46f7d214f97370c39a6b3fc24ee374a76b3f9b0fba"; String cipher = "ECDHE-RSA-AES128-GCM-SHA256"; Header[] headers = { new BasicHeader(Headers.SSL_SESSION_ID_STRING, id), new BasicHeader(Headers.SSL_CLIENT_CERT_STRING, cert), new BasicHeader(Headers.SSL_CIPHER_STRING, cipher) }; String response = internalTest("/cert-dn", headers); Assert.assertEquals("null", response); response = internalTest("/id", headers); Assert.assertEquals(id, response); response = internalTest("/cipher-suite", headers); Assert.assertEquals(cipher, response); response = internalTest("/key-size", headers); Assert.assertEquals("0", response); } } SSLMetaDataTestCase.java000066400000000000000000000114651420065311100345270ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/ssl/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.ssl; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import org.apache.http.Header; import static org.junit.Assert.assertEquals; /** * Test case to test transport-guarantee enforcement. * * @author Darran Lofthouse */ @RunWith(DefaultServer.class) public class SSLMetaDataTestCase { protected static PathHandler setupPathHandler() throws Exception { DefaultServer.startSSLServer(); final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("servlet", SSLAttributesServlet.class) .addMapping("/id") .addMapping("/cert") .addMapping("/cert-dn") .addMapping("/key-size") .addMapping("/cipher-suite"); DeploymentInfo info = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlet(s); DeploymentManager manager = container.addDeployment(info); manager.deploy(); root.addPrefixPath(info.getContextPath(), manager.start()); return root; } @BeforeClass public static void setup() throws Exception { PathHandler root = setupPathHandler(); DefaultServer.setRootHandler(root); } @AfterClass public static void cleanUp() throws Exception { DefaultServer.stopSSLServer(); } @Test public void testSessionId() throws IOException { String sessionId = internalTest("/id"); Assert.assertTrue("The session-id is an HEX byte array", sessionId.length() % 2 == 0); for (int i = 0; i < sessionId.length(); i++) { Character c = sessionId.charAt(i); Assert.assertTrue("The session-id is an HEX byte array", (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')); } } @Test public void testCipherSuite() throws IOException { internalTest("/cipher-suite"); } @Test public void testKeySize() throws IOException { internalTest("/key-size"); } @Test public void testCert() throws IOException { internalTest("/cert"); } @Test public void testCertDn() throws IOException { String response = internalTest("/cert-dn"); Assert.assertEquals("CN=Test Client, OU=OU, O=Org, L=City, ST=State, C=GB", response); } protected String internalTest(final String path, Header... headers) throws IOException { TestHttpClient client = new TestHttpClient(); client.setSSLContext(DefaultServer.getClientSSLContext()); final String url = DefaultServer.getDefaultServerSSLAddress() + "/servletContext" + path; try { HttpGet get = new HttpGet(url); get.setHeaders(headers); HttpResponse result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.length() > 0); return response; } finally { client.getConnectionManager().shutdown(); } } } SSLMetaDataWithExpandedBufferTestCase.java000066400000000000000000000037751420065311100401730ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/security/ssl/* * JBoss, Home of Professional Open Source. * Copyright 2019 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.ssl; import java.nio.ByteBuffer; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import io.undertow.testutils.DefaultServer; import org.junit.BeforeClass; import org.junit.runner.RunWith; /** * Runs {@link SSLMetaDataTestCase} with an expanded buffer SSL Engine, * and verifies if {@link javax.net.ssl.SSLEngineResult.Status#BUFFER_OVERFLOW} * is handled appropriately. * * @author Flavia Rainone */ @RunWith(DefaultServer.class) public class SSLMetaDataWithExpandedBufferTestCase extends SSLMetaDataTestCase { @BeforeClass public static void setup() throws Exception { final SSLContext context = SSLContext.getDefault(); final SSLEngine firstEngine = context.createSSLEngine(); firstEngine.setUseClientMode(false); final SSLEngine anotherEngine = context.createSSLEngine(); anotherEngine.setUseClientMode(false); final ByteBuffer expandBufferHandshake = ByteBuffer .wrap(new byte[] { 0x16, 0x3, 0x3, 0x71, 0x41 }); final ByteBuffer unwrapDest = ByteBuffer.allocate(64 * 1024); // enable large fragment buffers in all engines in the JVM firstEngine.unwrap(expandBufferHandshake, unwrapDest); unwrapDest.clear(); SSLMetaDataTestCase.setup(); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/servletcontext/000077500000000000000000000000001420065311100306575ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/servletcontext/1#2.txt000066400000000000000000000000061420065311100317010ustar00rootroot00000000000000Hello!GetResourceTestCase.java000066400000000000000000000111441420065311100353270ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/servletcontext/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.servletcontext; import io.undertow.server.handlers.PathHandler; import io.undertow.server.handlers.resource.PathResourceManager; import io.undertow.server.handlers.resource.Resource; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestResourceLoader; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.FileUtils; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import java.io.IOException; import java.io.InputStream; import java.net.URLEncoder; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class GetResourceTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setClassLoader(GetResourceTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setDeploymentName("servletContext.war") .setResourceManager(new TestResourceLoader(GetResourceTestCase.class)); builder.addServlet(new ServletInfo("ReadFileServlet", ReadFileServlet.class) .addMapping("/file")); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testGetResource() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/file?file=/file.txt"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("File Contents", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testGetResourceSpecialCharacterInFileName() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/file?file=/" + URLEncoder.encode("1#2.txt", "UTF-8")); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("Hello!", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testSpecialCharacterInFileURL() throws IOException { String tmp = System.getProperty("java.io.tmpdir"); PathResourceManager pathResourceManager = new PathResourceManager(Paths.get(tmp), 1); Path file = Paths.get(tmp, "1#2.txt"); Files.write(file, "Hi".getBytes()); Resource res = pathResourceManager.getResource("1#2.txt"); try(InputStream in = res.getUrl().openStream()) { Assert.assertEquals("Hi", FileUtils.readFile(in)); } } } ReadFileServlet.java000066400000000000000000000032411420065311100344630ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/servletcontext/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.servletcontext; import org.xnio.IoUtils; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.net.URL; /** * @author Stuart Douglas */ public class ReadFileServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String name = req.getParameter("file"); URL resource = req.getServletContext().getResource(name); InputStream stream = resource.openStream(); try { byte[] buff = new byte[100]; int res; while ((res = stream.read(buff)) > 0) { resp.getOutputStream().write(buff, 0, res); } } finally { IoUtils.safeClose(stream); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/servletcontext/file.txt000066400000000000000000000000151420065311100323330ustar00rootroot00000000000000File Contentsundertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/session/000077500000000000000000000000001420065311100272515ustar00rootroot00000000000000ChangeSessionIdListener.java000066400000000000000000000023261420065311100345540ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/session/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.session; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionIdListener; /** * @author Stuart Douglas */ public class ChangeSessionIdListener implements HttpSessionIdListener { public static volatile String oldId; public static volatile String newId; @Override public void sessionIdChanged(final HttpSessionEvent event, final String oldSessionId) { this.oldId = oldSessionId; this.newId = event.getSession().getId(); } } ChangeSessionIdServlet.java000066400000000000000000000026471420065311100344210ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/session/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.session; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /** * @author Stuart Douglas */ public class ChangeSessionIdServlet extends HttpServlet{ @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(true); String old = session.getId(); req.changeSessionId(); String newId = session.getId(); resp.getWriter().write(old + " "+ newId); } } ChangeSessionIdTestCase.java000066400000000000000000000104551420065311100345040ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/session/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.session; import java.io.IOException; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ListenerInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ChangeSessionIdTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler path = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("servlet", ChangeSessionIdServlet.class) .addMapping("/aa"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addListener(new ListenerInfo(ChangeSessionIdListener.class)) .addServlet(s); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); path.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(path); } @Test public void testChangeSessionId() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/aa"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); String oldId = testResponse(response, null); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); oldId = testResponse(response, oldId); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); oldId = testResponse(response, oldId); } finally { client.getConnectionManager().shutdown(); } } private String testResponse(final String response, final String expectedOld) { final String[] parts = response.split(" "); Assert.assertEquals(2, parts.length); String oldId = parts[0]; String newId = parts[1]; if(expectedOld != null) { Assert.assertEquals(expectedOld, oldId); } Assert.assertFalse(oldId.isEmpty()); Assert.assertFalse(newId.isEmpty()); Assert.assertFalse(oldId.equals(newId)); Assert.assertEquals(oldId, ChangeSessionIdListener.oldId); Assert.assertEquals(newId, ChangeSessionIdListener.newId); return newId; } } CrossContextServletSessionTestCase.java000066400000000000000000000463551420065311100370550ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/session/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.session; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.ServletSessionConfig; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import io.undertow.testutils.TestHttpClient; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * * Test that separate servlet deployments use seperate session managers, even in the presence of forwards, * and that sessions created in a forwarded context are accessible to later direct requests * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class CrossContextServletSessionTestCase { @BeforeClass public static void setup() throws ServletException { final ServletContainer container = ServletContainer.Factory.newInstance(); final PathHandler path = new PathHandler(); DefaultServer.setRootHandler(path); createDeployment("1", container, path); createDeployment("2", container, path); } private static void createDeployment(final String name, final ServletContainer container, final PathHandler path) throws ServletException { ServletInfo s = new ServletInfo("servlet", SessionServlet.class) .addMapping("/servlet"); ServletInfo forward = new ServletInfo("forward", ForwardServlet.class) .addMapping("/forward"); ServletInfo include = new ServletInfo("include", IncludeServlet.class) .addMapping("/include"); ServletInfo includeAdd = new ServletInfo("includeadd", IncludeAddServlet.class) .addMapping("/includeadd"); ServletInfo forwardAdd = new ServletInfo("forwardadd", ForwardAddServlet.class) .addMapping("/forwardadd"); ServletInfo accessTimeServlet = new ServletInfo("accesstimeservlet", LastAccessTimeSessionServlet.class) .addMapping("/accesstimeservlet"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/" + name) .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName( name + ".war") .setServletSessionConfig(new ServletSessionConfig().setPath("/")) .addServlets(s, forward, include, forwardAdd, includeAdd, accessTimeServlet); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); path.addPrefixPath(builder.getContextPath(), manager.start()); } @Test public void testSharedSessionCookieMultipleDeployments() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet direct1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/servlet"); HttpGet direct2 = new HttpGet(DefaultServer.getDefaultServerURL() + "/2/servlet"); HttpResponse result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("1", response); result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("2", response); result = client.execute(direct2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("1", response); result = client.execute(direct2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("2", response); result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("3", response); result = client.execute(direct2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("3", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testCrossContextSessionForwardInvocation() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet direct1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/servlet"); HttpGet forward1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/forward?context=/2&path=/servlet"); HttpGet direct2 = new HttpGet(DefaultServer.getDefaultServerURL() + "/2/servlet"); HttpGet forward2 = new HttpGet(DefaultServer.getDefaultServerURL() + "/2/forward?context=/1&path=/servlet"); HttpResponse result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("1", response); result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("2", response); result = client.execute(forward2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("3", response); result = client.execute(forward2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("4", response); result = client.execute(forward1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("1", response); result = client.execute(forward1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("2", response); result = client.execute(direct2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("3", response); result = client.execute(direct2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("4", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testCrossContextSessionForwardAccessTimeInvocation() throws IOException, InterruptedException { TestHttpClient client = new TestHttpClient(); try { HttpGet direct1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/accesstimeservlet"); HttpGet forward1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/forward?context=/2&path=/accesstimeservlet"); HttpResponse result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("1 ")); result = client.execute(forward1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("1 ")); Thread.sleep(50); result = client.execute(forward1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("2 ")); Long time1 = Long.parseLong(response.substring(2)); Thread.sleep(50); result = client.execute(forward1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("3 ")); Long time2 = Long.parseLong(response.substring(2)); Assert.assertTrue(time2 > time1); // access time updated in forward app result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("2 ")); Long time3 = Long.parseLong(response.substring(2)); Assert.assertTrue(time3 > time2); // access time updated in outer app } finally { client.getConnectionManager().shutdown(); } } @Test public void testCrossContextSessionForwardInvocationWithBothServletsAdding() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet direct1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/servlet"); HttpGet forward1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/forwardadd?context=/2&path=/servlet"); HttpGet direct2 = new HttpGet(DefaultServer.getDefaultServerURL() + "/2/servlet"); HttpGet forward2 = new HttpGet(DefaultServer.getDefaultServerURL() + "/2/forwardadd?context=/1&path=/servlet"); HttpResponse result = client.execute(forward1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("1", response); result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("2", response); result = client.execute(forward2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("3", response); result = client.execute(forward2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("4", response); result = client.execute(forward1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("4", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testCrossContextSessionIncludeInvocation() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet direct1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/servlet"); HttpGet include1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/include?context=/2&path=/servlet"); HttpGet direct2 = new HttpGet(DefaultServer.getDefaultServerURL() + "/2/servlet"); HttpGet include2 = new HttpGet(DefaultServer.getDefaultServerURL() + "/2/include?context=/1&path=/servlet"); HttpResponse result = client.execute(include2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("1", response); result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("2", response); result = client.execute(include2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("3", response); result = client.execute(include2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("4", response); result = client.execute(include1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("1", response); result = client.execute(include1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("2", response); result = client.execute(direct2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("3", response); result = client.execute(direct2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("4", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testCrossContextSessionIncludeAccessTimeInvocation() throws IOException, InterruptedException { TestHttpClient client = new TestHttpClient(); try { HttpGet direct1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/accesstimeservlet"); HttpGet include1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/include?context=/2&path=/accesstimeservlet"); HttpResponse result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("1 ")); result = client.execute(include1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("1 ")); Thread.sleep(50); result = client.execute(include1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("2 ")); Long time1 = Long.parseLong(response.substring(2)); Thread.sleep(50); result = client.execute(include1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("3 ")); Long time2 = Long.parseLong(response.substring(2)); Assert.assertTrue(time2 > time1); // access time updated in include app result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("2 ")); Long time3 = Long.parseLong(response.substring(2)); Assert.assertTrue(time3 > time2); // access time updated in outer app } finally { client.getConnectionManager().shutdown(); } } public static class ForwardServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { req.getServletContext().getContext(req.getParameter("context")).getRequestDispatcher(req.getParameter("path")).forward(req, resp); } } public static class IncludeServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { req.getServletContext().getContext(req.getParameter("context")).getRequestDispatcher(req.getParameter("path")).include(req, resp); } } public static class ForwardAddServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(); Integer value = (Integer)session.getAttribute("key"); if(value == null) { value = 1; } session.setAttribute("key", value + 1); req.getServletContext().getContext(req.getParameter("context")).getRequestDispatcher(req.getParameter("path")).forward(req, resp); } } public static class IncludeAddServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(); Integer value = (Integer)session.getAttribute("key"); if(value == null) { value = 1; } session.setAttribute("key", value + 1); req.getServletContext().getContext(req.getParameter("context")).getRequestDispatcher(req.getParameter("path")).include(req, resp); } } } CrossContextServletSharedSessionTestCase.java000066400000000000000000000475351420065311100402050ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/session/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.session; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.server.handlers.PathHandler; import io.undertow.server.session.InMemorySessionManager; import io.undertow.server.session.SessionManager; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.ServletSessionConfig; import io.undertow.servlet.api.SessionManagerFactory; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; /** * * Test that separate servlet deployments use seperate session managers, even in the presence of forwards, * and that sessions created in a forwarded context are accessible to later direct requests * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class CrossContextServletSharedSessionTestCase { @BeforeClass public static void setup() throws ServletException { final ServletContainer container = ServletContainer.Factory.newInstance(); final PathHandler path = new PathHandler(); DefaultServer.setRootHandler(path); InMemorySessionManager manager = new InMemorySessionManager("test"); createDeployment("1", container, path, manager); createDeployment("2", container, path, manager); } private static void createDeployment(final String name, final ServletContainer container, final PathHandler path, InMemorySessionManager sessionManager) throws ServletException { ServletInfo s = new ServletInfo("servlet", SessionServlet.class) .addMapping("/servlet"); ServletInfo forward = new ServletInfo("forward", ForwardServlet.class) .addMapping("/forward"); ServletInfo include = new ServletInfo("include", IncludeServlet.class) .addMapping("/include"); ServletInfo includeAdd = new ServletInfo("includeadd", IncludeAddServlet.class) .addMapping("/includeadd"); ServletInfo forwardAdd = new ServletInfo("forwardadd", ForwardAddServlet.class) .addMapping("/forwardadd"); ServletInfo accessTimeServlet = new ServletInfo("accesstimeservlet", LastAccessTimeSessionServlet.class) .addMapping("/accesstimeservlet"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/" + name) .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName( name + ".war") .setSessionManagerFactory(new SessionManagerFactory() { @Override public SessionManager createSessionManager(Deployment deployment) { return sessionManager; } }) .setServletSessionConfig(new ServletSessionConfig().setPath("/")) .addServlets(s, forward, include, forwardAdd, includeAdd, accessTimeServlet); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); path.addPrefixPath(builder.getContextPath(), manager.start()); } @Test public void testSharedSessionCookieMultipleDeployments() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet direct1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/servlet"); HttpGet direct2 = new HttpGet(DefaultServer.getDefaultServerURL() + "/2/servlet"); HttpResponse result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("1", response); result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("2", response); result = client.execute(direct2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("3", response); result = client.execute(direct2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("4", response); result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("5", response); result = client.execute(direct2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("6", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testCrossContextSessionForwardInvocation() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet direct1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/servlet"); HttpGet forward1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/forward?context=/2&path=/servlet"); HttpGet direct2 = new HttpGet(DefaultServer.getDefaultServerURL() + "/2/servlet"); HttpGet forward2 = new HttpGet(DefaultServer.getDefaultServerURL() + "/2/forward?context=/1&path=/servlet"); HttpResponse result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("1", response); result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("2", response); result = client.execute(forward2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("3", response); result = client.execute(forward2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("4", response); result = client.execute(forward1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("5", response); result = client.execute(forward1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("6", response); result = client.execute(direct2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("7", response); result = client.execute(direct2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("8", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testCrossContextSessionForwardAccessTimeInvocation() throws IOException, InterruptedException { TestHttpClient client = new TestHttpClient(); try { HttpGet direct1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/accesstimeservlet"); HttpGet forward1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/forward?context=/2&path=/accesstimeservlet"); HttpResponse result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("1 ")); result = client.execute(forward1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("2 ")); Thread.sleep(50); result = client.execute(forward1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("3 ")); Long time1 = Long.parseLong(response.substring(2)); Thread.sleep(50); result = client.execute(forward1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("4 ")); Long time2 = Long.parseLong(response.substring(2)); Assert.assertTrue(time2 > time1); // access time updated in forward app result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("5 ")); Long time3 = Long.parseLong(response.substring(2)); Assert.assertTrue(time3 > time2); // access time updated in outer app } finally { client.getConnectionManager().shutdown(); } } @Test public void testCrossContextSessionForwardInvocationWithBothServletsAdding() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet direct1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/servlet"); HttpGet forward1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/forwardadd?context=/2&path=/servlet"); HttpGet direct2 = new HttpGet(DefaultServer.getDefaultServerURL() + "/2/servlet"); HttpGet forward2 = new HttpGet(DefaultServer.getDefaultServerURL() + "/2/forwardadd?context=/1&path=/servlet"); HttpResponse result = client.execute(forward1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("2", response); result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("3", response); result = client.execute(forward2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("5", response); result = client.execute(forward2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("7", response); result = client.execute(forward1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("9", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testCrossContextSessionIncludeInvocation() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet direct1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/servlet"); HttpGet include1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/include?context=/2&path=/servlet"); HttpGet direct2 = new HttpGet(DefaultServer.getDefaultServerURL() + "/2/servlet"); HttpGet include2 = new HttpGet(DefaultServer.getDefaultServerURL() + "/2/include?context=/1&path=/servlet"); HttpResponse result = client.execute(include2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("1", response); result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("2", response); result = client.execute(include2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("3", response); result = client.execute(include2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("4", response); result = client.execute(include1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("5", response); result = client.execute(include1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("6", response); result = client.execute(direct2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("7", response); result = client.execute(direct2); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("8", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testCrossContextSessionIncludeAccessTimeInvocation() throws IOException, InterruptedException { TestHttpClient client = new TestHttpClient(); try { HttpGet direct1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/accesstimeservlet"); HttpGet include1 = new HttpGet(DefaultServer.getDefaultServerURL() + "/1/include?context=/2&path=/accesstimeservlet"); HttpResponse result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("1 ")); result = client.execute(include1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("2 ")); Thread.sleep(50); result = client.execute(include1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("3 ")); Long time1 = Long.parseLong(response.substring(2)); Thread.sleep(50); result = client.execute(include1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("4 ")); Long time2 = Long.parseLong(response.substring(2)); Assert.assertTrue(time2 > time1); // access time updated in include app result = client.execute(direct1); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertTrue(response.startsWith("5 ")); Long time3 = Long.parseLong(response.substring(2)); Assert.assertTrue(time3 > time2); // access time updated in outer app } finally { client.getConnectionManager().shutdown(); } } public static class ForwardServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { req.getServletContext().getContext(req.getParameter("context")).getRequestDispatcher(req.getParameter("path")).forward(req, resp); } } public static class IncludeServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { req.getServletContext().getContext(req.getParameter("context")).getRequestDispatcher(req.getParameter("path")).include(req, resp); } } public static class ForwardAddServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(); Integer value = (Integer)session.getAttribute("key"); if(value == null) { value = 1; } session.setAttribute("key", value + 1); req.getServletContext().getContext(req.getParameter("context")).getRequestDispatcher(req.getParameter("path")).forward(req, resp); } } public static class IncludeAddServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(); Integer value = (Integer)session.getAttribute("key"); if(value == null) { value = 1; } session.setAttribute("key", value + 1); req.getServletContext().getContext(req.getParameter("context")).getRequestDispatcher(req.getParameter("path")).include(req, resp); } } } LastAccessTimeSessionServlet.java000066400000000000000000000031621420065311100356140ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/session/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.session; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /** * @author rmartinc */ public class LastAccessTimeSessionServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(); resp.addHeader("url", resp.encodeURL(req.getRequestURL().toString())); Integer value = (Integer)session.getAttribute("key"); if(value == null) { value = 1; } session.setAttribute("key", value+1); resp.getWriter().write("" + value + " " + session.getLastAccessedTime()); resp.getWriter().close(); } } RequestedSessionIdServlet.java000066400000000000000000000045041420065311100351670ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/session/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.session; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author Stuart Douglas */ public class RequestedSessionIdServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { switch (req.getParameter("action")) { case "create": req.getSession(true); resp.getWriter().write(req.getRequestedSessionId()); break; case "destroy": req.getSession().invalidate(); resp.getWriter().write(req.getRequestedSessionId()); break; case "destroycreate": req.getSession().invalidate(); req.getSession(true); resp.getWriter().write(req.getRequestedSessionId()); break; case "change": req.changeSessionId(); resp.getWriter().write(req.getRequestedSessionId()); break; case "timeout": req.getSession(true).setMaxInactiveInterval(1); resp.getWriter().write(req.getRequestedSessionId()); break; case "isvalid": resp.getWriter().write(req.isRequestedSessionIdValid() + ""); break; case "default": resp.getWriter().write(req.getRequestedSessionId()); break; } } } ServletSessionCrawlerTestCase.java000066400000000000000000000111341420065311100360010ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/session/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.session; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.CrawlerSessionManagerConfig; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ListenerInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Headers; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.CookieStore; import org.apache.http.client.methods.HttpGet; import org.apache.http.cookie.Cookie; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import java.io.IOException; import java.util.Collections; import java.util.Date; import java.util.List; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ServletSessionCrawlerTestCase { @Test public void testCrawlerSessionUsage() throws IOException, InterruptedException { final PathHandler pathHandler = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setCrawlerSessionManagerConfig(new CrawlerSessionManagerConfig()) .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addListener(new ListenerInfo(SessionCookieConfigListener.class)) .addServlets(new ServletInfo("servlet", SessionServlet.class) .addMapping("/aa/b")); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); try { pathHandler.addPrefixPath(builder.getContextPath(), manager.start()); } catch (ServletException e) { throw new RuntimeException(e); } DefaultServer.setRootHandler(pathHandler); TestHttpClient client = new TestHttpClient(); client.setCookieStore(new CookieStore() { @Override public void addCookie(Cookie cookie) { } @Override public List getCookies() { return Collections.EMPTY_LIST; } @Override public boolean clearExpired(Date date) { return false; } @Override public void clear() { } }); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/aa/b"); get.addHeader(Headers.USER_AGENT_STRING, "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("1", response); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("2", response); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("3", response); } finally { client.getConnectionManager().shutdown(); } } } ServletSessionPersistenceTestCase.java000066400000000000000000000103771420065311100366760ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/session/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.session; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.ServletSessionConfig; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.util.InMemorySessionPersistence; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import java.io.IOException; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ServletSessionPersistenceTestCase { @Test public void testSimpleSessionUsage() throws IOException, ServletException { final PathHandler pathHandler = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setSessionPersistenceManager(new InMemorySessionPersistence()) .setServletSessionConfig(new ServletSessionConfig().setPath("/servletContext/aa")) .addServlets(new ServletInfo("servlet", SessionServlet.class) .addMapping("/aa/b")); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); try { pathHandler.addPrefixPath(builder.getContextPath(), manager.start()); } catch (ServletException e) { throw new RuntimeException(e); } DefaultServer.setRootHandler(pathHandler); TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/aa/b"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("1", response); String cookieValue = result.getHeaders("Set-Cookie")[0].getValue(); Assert.assertTrue(cookieValue, cookieValue.contains("JSESSIONID")); Assert.assertTrue(cookieValue, cookieValue.contains("/servletContext/aa")); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("2", response); manager.stop(); manager.undeploy(); manager.deploy(); pathHandler.addPrefixPath(builder.getContextPath(), manager.start()); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("3", response); } finally { client.getConnectionManager().shutdown(); } } } ServletSessionTestCase.java000066400000000000000000000207611420065311100344670ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/session/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.session; import java.io.IOException; import java.util.Collections; import java.util.Date; import java.util.List; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ListenerInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.CookieStore; import org.apache.http.client.methods.HttpGet; import org.apache.http.cookie.Cookie; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ServletSessionTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler pathHandler = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addListener(new ListenerInfo(SessionCookieConfigListener.class)) .addServlets(new ServletInfo("servlet", SessionServlet.class) .addMapping("/aa/b")); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); try { pathHandler.addPrefixPath(builder.getContextPath(), manager.start()); } catch (ServletException e) { throw new RuntimeException(e); } DefaultServer.setRootHandler(pathHandler); } @Test public void testSimpleSessionUsage() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/aa/b"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("1", response); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("2", response); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("3", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testSessionCookieConfig() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/aa/b"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("1", response); String cookieValue = result.getHeaders("Set-Cookie")[0].getValue(); Assert.assertTrue(cookieValue.contains("MySessionCookie")); Assert.assertTrue(cookieValue.contains("/servletContext/aa/")); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("2", response); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("3", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testSessionConfigNoCookies() throws IOException { TestHttpClient client = new TestHttpClient(); client.setCookieStore(new CookieStore() { @Override public void addCookie(Cookie cookie) { } @Override public List getCookies() { return Collections.EMPTY_LIST; } @Override public boolean clearExpired(Date date) { return false; } @Override public void clear() { } }); try { HttpResponse result = client.execute(new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/aa/b;foo=bar")); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("1", response); String url = result.getHeaders("url")[0].getValue(); result = client.execute(new HttpGet(url)); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); url = result.getHeaders("url")[0].getValue(); Assert.assertEquals("2", response); result = client.execute(new HttpGet(url)); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("3", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testSessionConfigNoCookiesMatrixParameters() throws IOException { TestHttpClient client = new TestHttpClient(); client.setCookieStore(new CookieStore() { @Override public void addCookie(Cookie cookie) { } @Override public List getCookies() { return Collections.EMPTY_LIST; } @Override public boolean clearExpired(Date date) { return false; } @Override public void clear() { } }); try { HttpResponse result = client.execute(new HttpGet(DefaultServer.getDefaultServerURL() + ";foo=bar/servletContext/aa/b")); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("1", response); String url = result.getHeaders("url")[0].getValue(); result = client.execute(new HttpGet(url)); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); url = result.getHeaders("url")[0].getValue(); Assert.assertEquals("2", response); result = client.execute(new HttpGet(url)); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("3", response); } finally { client.getConnectionManager().shutdown(); } } } ServletURLRewritingSessionTestCase.java000066400000000000000000000250171420065311100367440ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/session/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.session; import java.io.IOException; import java.util.Collections; import java.util.Map; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.SessionTrackingMode; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.BasicCookieStore; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.servlet.ServletExtension; import io.undertow.servlet.Servlets; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.ServletSessionConfig; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; /** * basic test of in memory session functionality * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ServletURLRewritingSessionTestCase { public static final String COUNT = "count"; @BeforeClass public static void setup() { DeploymentUtils.setupServlet(new ServletExtension() { @Override public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servletContext) { deploymentInfo.setServletSessionConfig(new ServletSessionConfig().setSessionTrackingModes(Collections.singleton(SessionTrackingMode.URL))); } }, Servlets.servlet(URLRewritingServlet.class).addMapping("/foo")); } @Test public void testURLRewriting() throws IOException { TestHttpClient client = new TestHttpClient(); client.setCookieStore(new BasicCookieStore()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/foo;foo=bar"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String url = HttpClientUtils.readResponse(result); Header[] header = result.getHeaders(COUNT); Assert.assertEquals("0", header[0].getValue()); get = new HttpGet(url); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); url = HttpClientUtils.readResponse(result); header = result.getHeaders(COUNT); Assert.assertEquals("1", header[0].getValue()); get = new HttpGet(url); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); url = HttpClientUtils.readResponse(result); header = result.getHeaders(COUNT); Assert.assertEquals("2", header[0].getValue()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testURLRewritingWithQueryParameters() throws IOException { TestHttpClient client = new TestHttpClient(); client.setCookieStore(new BasicCookieStore()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/foo?a=b;c"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String url = HttpClientUtils.readResponse(result); Header[] header = result.getHeaders(COUNT); Assert.assertEquals("0", header[0].getValue()); Assert.assertEquals("b;c", result.getHeaders("a")[0].getValue()); get = new HttpGet(url); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); url = HttpClientUtils.readResponse(result); header = result.getHeaders(COUNT); Assert.assertEquals("1", header[0].getValue()); Assert.assertEquals("b;c", result.getHeaders("a")[0].getValue()); get = new HttpGet(url); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); url = HttpClientUtils.readResponse(result); header = result.getHeaders(COUNT); Assert.assertEquals("2", header[0].getValue()); Assert.assertEquals("b;c", result.getHeaders("a")[0].getValue()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testURLRewritingWithExistingOldSessionId() throws IOException { TestHttpClient client = new TestHttpClient(); client.setCookieStore(new BasicCookieStore()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/foo;jsessionid=foobar"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String url = HttpClientUtils.readResponse(result); Header[] header = result.getHeaders(COUNT); Assert.assertEquals("0", header[0].getValue()); get = new HttpGet(url); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); url = HttpClientUtils.readResponse(result); header = result.getHeaders(COUNT); Assert.assertEquals("1", header[0].getValue()); get = new HttpGet(url); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); url = HttpClientUtils.readResponse(result); header = result.getHeaders(COUNT); Assert.assertEquals("2", header[0].getValue()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testURLRewritingWithExistingOldSessionIdAndOtherPathParams() throws IOException { TestHttpClient client = new TestHttpClient(); client.setCookieStore(new BasicCookieStore()); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/foo;jsessionid=foobar&a=b"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String url = HttpClientUtils.readResponse(result); Header[] header = result.getHeaders(COUNT); Assert.assertEquals("0", header[0].getValue()); get = new HttpGet(url); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); url = HttpClientUtils.readResponse(result); header = result.getHeaders(COUNT); Assert.assertEquals("1", header[0].getValue()); get = new HttpGet(url); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); url = HttpClientUtils.readResponse(result); header = result.getHeaders(COUNT); Assert.assertEquals("2", header[0].getValue()); } finally { client.getConnectionManager().shutdown(); } } @Test public void testGetRequestedSessionId() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/foo"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/foo;jsessionid=test"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { client.close(); } } public static class URLRewritingServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String sessionIdBefore = req.getRequestedSessionId(); HttpSession session = req.getSession(true); String sessionIdAfter = req.getRequestedSessionId(); Assert.assertEquals(String.format("sessionIdBefore %s, sessionIdAfter %s", sessionIdBefore, sessionIdAfter), sessionIdBefore, sessionIdAfter); Object existing = session.getAttribute(COUNT); if (existing == null) { session.setAttribute(COUNT, 0); } else { Assert.assertTrue(req.getRequestURI().startsWith("/servletContext/foo;")); Assert.assertTrue(req.getRequestURI().contains("jsessionid=" + session.getId())); } Integer count = (Integer) session.getAttribute(COUNT); resp.addHeader(COUNT, count.toString()); session.setAttribute(COUNT, ++count); for (Map.Entry qp : req.getParameterMap().entrySet()) { resp.addHeader(qp.getKey(), qp.getValue()[0]); } if (req.getQueryString() == null) { resp.getWriter().write(resp.encodeURL(DefaultServer.getDefaultServerURL() + req.getRequestURI())); } else { resp.getWriter().write(resp.encodeURL(DefaultServer.getDefaultServerURL() + req.getRequestURI() + "?" + req.getQueryString())); } } } } SessionCookieConfigListener.java000066400000000000000000000030041420065311100354430ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/session/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.session; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.SessionTrackingMode; import java.util.Arrays; import java.util.HashSet; /** * @author Stuart Douglas */ public class SessionCookieConfigListener implements ServletContextListener { @Override public void contextInitialized(final ServletContextEvent sce) { sce.getServletContext().getSessionCookieConfig().setName("MySessionCookie"); sce.getServletContext().getSessionCookieConfig().setPath("/servletContext/aa/"); sce.getServletContext().setSessionTrackingModes(new HashSet<>(Arrays.asList(SessionTrackingMode.COOKIE, SessionTrackingMode.URL))); } @Override public void contextDestroyed(final ServletContextEvent sce) { } } SessionIdHandlingTestCase.java000066400000000000000000000161271420065311100350450ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/session/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.session; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.cookie.Cookie; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import java.io.IOException; import java.util.List; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class SessionIdHandlingTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler pathHandler = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlets(new ServletInfo("servlet", RequestedSessionIdServlet.class) .addMapping("/session")); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); try { pathHandler.addPrefixPath(builder.getContextPath(), manager.start()); } catch (ServletException e) { throw new RuntimeException(e); } DefaultServer.setRootHandler(pathHandler); } @Test public void testGetRequestedSessionId() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/session?action=create"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("null", response); String sessionId = getSession(client.getCookieStore().getCookies()); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/session?action=default"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals(sessionId, response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/session?action=change"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals(sessionId, response); String newSessionId = getSession(client.getCookieStore().getCookies()); Assert.assertNotEquals(sessionId, newSessionId); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/session?action=default"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals(newSessionId, response); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/session?action=destroycreate"); result = client.execute(get); final String createdSessionId = getSession(client.getCookieStore().getCookies()); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals(newSessionId, response); Assert.assertNotEquals(createdSessionId, newSessionId); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/session?action=destroy"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals(createdSessionId, response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testIsRequestedSessionIdValid() throws IOException, InterruptedException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/session?action=create"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("null", response); String sessionId = getSession(client.getCookieStore().getCookies()); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/session?action=timeout"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals(sessionId, response); Thread.sleep(2500); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/session?action=isvalid"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("false", response); } finally { client.getConnectionManager().shutdown(); } } private String getSession(List cookies) { for(Cookie cookie : cookies) { if(cookie.getName().equals("JSESSIONID")) { return cookie.getValue(); } } return null; } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/session/SessionServlet.java000066400000000000000000000031041420065311100331020ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.session; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /** * @author Stuart Douglas */ public class SessionServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(); resp.addHeader("url", resp.encodeURL(req.getRequestURL().toString())); Integer value = (Integer)session.getAttribute("key"); if(value == null) { value = 1; } session.setAttribute("key", value+1); resp.getWriter().write("" + value); resp.getWriter().close(); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/session/invalidate/000077500000000000000000000000001420065311100313715ustar00rootroot00000000000000ServletSessionInvalidateTestCase.java000066400000000000000000000040361420065311100406050ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/session/invalidate/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.session.invalidate; import java.io.IOException; import javax.servlet.ServletException; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Jozef Hartinger */ @RunWith(DefaultServer.class) public class ServletSessionInvalidateTestCase { @BeforeClass public static void setup() throws ServletException { DeploymentUtils.setupServlet( new ServletInfo("servlet", SessionServlet.class) .addMapping("/test")); } @Test public void testSimpleSessionUsage() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/test"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); } finally { client.getConnectionManager().shutdown(); } } } SessionServlet.java000066400000000000000000000026341420065311100351520ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/session/invalidate/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.session.invalidate; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.junit.Assert; public class SessionServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(true); session.setAttribute("FOO", "BAR"); session.invalidate(); HttpSession session2 = req.getSession(false); Assert.assertNull(session2); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/spec/000077500000000000000000000000001420065311100265205ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/spec/FilterMappingTestCase.java000066400000000000000000000070471420065311100335700ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.spec; import io.undertow.servlet.ServletExtension; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServlet; import java.io.IOException; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class FilterMappingTestCase { public static String message; public static final String HELLO_WORLD = "Hello World"; public static final String SERVLET = "aServlet"; private Filter filterMappedByServletName = new NullFilter(); private Filter filterMappedByUrlPattern = new NullFilter(); /** * A Filter that does nothing */ static class NullFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() {} } static class NullServlet extends HttpServlet {} @Test public void testRegisterFilters() throws Exception { // If the servlet can be set up without an exception, then the filters were correctly registered setupServlet(); } /** * Registers a servlet with two filters, one mapped by servlet name and one mapped by url pattern */ private void setupServlet() { DeploymentUtils.setupServlet(new ServletExtension() { @Override public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servletContext) { servletContext .addFilter("MyFilter1", filterMappedByServletName) .addMappingForServletNames(null, false, SERVLET); servletContext .addFilter("MyFilter2", filterMappedByUrlPattern) .addMappingForUrlPatterns(null, false, "/"); } }, new ServletInfo(SERVLET, NullServlet.class) .addMapping("/" + SERVLET)); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/spec/GetCookiesTestCase.java000066400000000000000000000114001420065311100330470ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2012 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.spec; import javax.servlet.ServletException; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.Headers; /** * Tests that getCookies() on a request does not fail due to invalid cookies. * * @author Gael Marziou */ @RunWith(DefaultServer.class) public class GetCookiesTestCase { @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("servlet", ValidCookieEchoServlet.class) .addMapping("/aaa"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(GetCookiesTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlet(s); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testGetCookiesWithOnlyValidCookie() throws Exception { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/aaa"); get.setHeader(Headers.COOKIE_STRING, "testcookie=works"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("Only one valid cookie", "name='testcookie'value='works'", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testGetCookiesWithOnlyInvalidCookies() throws Exception { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/aaa"); get.setHeader(Headers.COOKIE_STRING, "ctx:123=456"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("No valid cookie", "", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testGetCookiesWithInvalidCookieName() throws Exception { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/aaa"); get.setHeader(Headers.COOKIE_STRING, "testcookie=works; ctx:123=456"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals("Only one valid cookie", "name='testcookie'value='works'", response); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/spec/ParameterEchoTestCase.java000066400000000000000000000212401420065311100335350ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.spec; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.ParameterEchoServlet; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.message.BasicNameValuePair; import io.undertow.testutils.TestHttpClient; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Istvan Szabo */ @RunWith(DefaultServer.class) public class ParameterEchoTestCase { public static final String RESPONSE = "param1=\'1\'param2=\'2\'param3=\'3\'"; @BeforeClass public static void setup() throws ServletException { final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo s = new ServletInfo("servlet", ParameterEchoServlet.class) .addMapping("/aaa"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(ParameterEchoTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlet(s); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } @Test public void testPostInUrl() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext/aaa?param1=1¶m2=2¶m3=3"); final List values = new ArrayList<>(); UrlEncodedFormEntity data = new UrlEncodedFormEntity(values, "UTF-8"); post.setEntity(data); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(RESPONSE, response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testPostInStream() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext/aaa"); final List values = new ArrayList<>(); values.add(new BasicNameValuePair("param1", "1")); values.add(new BasicNameValuePair("param2", "2")); values.add(new BasicNameValuePair("param3", "3")); UrlEncodedFormEntity data = new UrlEncodedFormEntity(values, "UTF-8"); post.setEntity(data); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(RESPONSE, response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testPostBoth() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/servletContext/aaa?param1=1¶m2=2"); final List values = new ArrayList<>(); values.add(new BasicNameValuePair("param3", "3")); UrlEncodedFormEntity data = new UrlEncodedFormEntity(values, "UTF-8"); post.setEntity(data); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(RESPONSE, response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testPutBothValues() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpPut put = new HttpPut(DefaultServer.getDefaultServerURL() + "/servletContext/aaa?param1=1¶m2=2"); final List values = new ArrayList<>(); values.add(new BasicNameValuePair("param3", "3")); UrlEncodedFormEntity data = new UrlEncodedFormEntity(values, "UTF-8"); put.setEntity(data); HttpResponse result = client.execute(put); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(RESPONSE, response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testPutNames() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpPut put = new HttpPut(DefaultServer.getDefaultServerURL() + "/servletContext/aaa?type=names"); final List values = new ArrayList<>(); values.add(new BasicNameValuePair("param1", "1")); values.add(new BasicNameValuePair("param2", "2")); values.add(new BasicNameValuePair("param3", "3")); UrlEncodedFormEntity data = new UrlEncodedFormEntity(values, "UTF-8"); put.setEntity(data); HttpResponse result = client.execute(put); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); List resList = Arrays.asList(HttpClientUtils.readResponse(result).split(",")); Assert.assertEquals(4, resList.size()); Assert.assertTrue(resList.contains("type")); Assert.assertTrue(resList.contains("param1")); Assert.assertTrue(resList.contains("param2")); Assert.assertTrue(resList.contains("param3")); } finally { client.getConnectionManager().shutdown(); } } @Test public void testPutMap() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpPut put = new HttpPut(DefaultServer.getDefaultServerURL() + "/servletContext/aaa?type=map"); final List values = new ArrayList<>(); values.add(new BasicNameValuePair("param1", "1")); values.add(new BasicNameValuePair("param2", "2")); values.add(new BasicNameValuePair("param3", "3")); UrlEncodedFormEntity data = new UrlEncodedFormEntity(values, "UTF-8"); put.setEntity(data); HttpResponse result = client.execute(put); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); List resList = Arrays.asList(HttpClientUtils.readResponse(result).split(";")); Assert.assertEquals(4, resList.size()); Assert.assertTrue(resList.contains("type=map")); Assert.assertTrue(resList.contains("param1=1")); Assert.assertTrue(resList.contains("param2=2")); Assert.assertTrue(resList.contains("param3=3")); } finally { client.getConnectionManager().shutdown(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/spec/UnavailableServlet.java000066400000000000000000000021421420065311100331520ustar00rootroot00000000000000package io.undertow.servlet.test.spec; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.UnavailableException; import java.io.IOException; /** * @author Stuart Douglas */ public class UnavailableServlet implements Servlet { static final String PERMANENT = "permanent"; static boolean first = true; @Override public void init(ServletConfig config) throws ServletException { if(config.getInitParameter(PERMANENT) != null) { throw new UnavailableException("msg"); } else if(first){ first = false; throw new UnavailableException("msg", 1); } } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { } @Override public String getServletInfo() { return null; } @Override public void destroy() { } } UnavailableServletTestCase.java000066400000000000000000000046751420065311100345440ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/specpackage io.undertow.servlet.test.spec; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import java.io.IOException; import static io.undertow.servlet.Servlets.servlet; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class UnavailableServletTestCase { @BeforeClass public static void setup() throws ServletException { DeploymentUtils.setupServlet( servlet("p", UnavailableServlet.class) .addInitParam(UnavailableServlet.PERMANENT, "1") .addMapping("/p"), servlet("t", UnavailableServlet.class) .addMapping("/t")); } @Test public void testPermanentUnavailableServlet() throws IOException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/p"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.NOT_FOUND, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } @Test public void testTempUnavailableServlet() throws IOException, InterruptedException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/t"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.SERVICE_UNAVAILABLE, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); Thread.sleep(1001); get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/t"); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } } ValidCookieEchoServlet.java000066400000000000000000000016131420065311100336420ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/specpackage io.undertow.servlet.test.spec; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * A servlet that echoes name and value pairs for received valid cookies only. * * @author Gael Marziou */ public class ValidCookieEchoServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { Cookie[] cookies = req.getCookies(); PrintWriter out = resp.getWriter(); for (Cookie cookie : cookies) { out.print("name='" + cookie.getName() + "'"); out.print("value='" + cookie.getValue() + "'"); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/000077500000000000000000000000001420065311100272445ustar00rootroot00000000000000AbstractServletInputStreamTestCase.java000066400000000000000000000303261420065311100367740ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.streams; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.apache.commons.codec.binary.Hex; import org.apache.http.HttpResponse; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.InputStreamEntity; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.junit.Assert; import org.junit.Test; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; /** * @author Stuart Douglas */ public abstract class AbstractServletInputStreamTestCase { public static final String HELLO_WORLD = "Hello World"; public static final String BLOCKING_SERVLET = "blockingInput"; public static final String ASYNC_SERVLET = "asyncInput"; public static final String ASYNC_EAGER_SERVLET = "asyncEagerInput"; @Test public void testBlockingServletInputStream() { StringBuilder builder = new StringBuilder(1000 * HELLO_WORLD.length()); for (int i = 0; i < 10; ++i) { try { for (int j = 0; j < 1000; ++j) { builder.append(HELLO_WORLD); } String message = builder.toString(); runTest(message, BLOCKING_SERVLET, false, false); } catch (Throwable e) { throw new RuntimeException("test failed with i equal to " + i, e); } } } @Test public void testAsyncServletInputStream() { //for(int h = 0; h < 20 ; ++h) { StringBuilder builder = new StringBuilder(1000 * HELLO_WORLD.length()); for (int i = 0; i < 10; ++i) { try { for (int j = 0; j < 10000; ++j) { builder.append(HELLO_WORLD); } String message = builder.toString(); runTest(message, ASYNC_SERVLET, false, false); } catch (Throwable e) { throw new RuntimeException("test failed with i equal to " + i, e); } } //} } @Test public void testAsyncServletInputStreamWithPreamble() { StringBuilder builder = new StringBuilder(2000 * HELLO_WORLD.length()); for (int i = 0; i < 10; ++i) { try { for (int j = 0; j < 10000; ++j) { builder.append(HELLO_WORLD); } String message = builder.toString(); runTest(message, ASYNC_SERVLET, true, false); } catch (Throwable e) { throw new RuntimeException("test failed with i equal to " + i, e); } } } @Test public void testAsyncServletInputStreamInParallel() throws Exception { StringBuilder builder = new StringBuilder(100000 * HELLO_WORLD.length()); for (int j = 0; j < 100000; ++j) { builder.append(HELLO_WORLD); } String message = builder.toString(); runTestParallel(20, message, ASYNC_SERVLET, false, false); } @Test public void testAsyncServletInputStreamInParallelOffIoThread() throws Exception { StringBuilder builder = new StringBuilder(100000 * HELLO_WORLD.length()); for (int j = 0; j < 100000; ++j) { builder.append(HELLO_WORLD); } String message = builder.toString(); runTestParallel(20, message, ASYNC_SERVLET, false, true); } @Test public void testAsyncServletInputStreamOffIoThread() { StringBuilder builder = new StringBuilder(2000 * HELLO_WORLD.length()); for (int i = 0; i < 10; ++i) { try { for (int j = 0; j < 10000; ++j) { builder.append(HELLO_WORLD); } String message = builder.toString(); runTest(message, ASYNC_SERVLET, false, true); } catch (Throwable e) { throw new RuntimeException("test failed with i equal to " + i, e); } } } @Test public void testAsyncServletInputStreamOffIoThreadWithPreamble() { StringBuilder builder = new StringBuilder(2000 * HELLO_WORLD.length()); for (int i = 0; i < 10; ++i) { try { for (int j = 0; j < 10000; ++j) { builder.append(HELLO_WORLD); } String message = builder.toString(); runTest(message, ASYNC_SERVLET, true, true); } catch (Throwable e) { throw new RuntimeException("test failed with i equal to " + i, e); } } } @Test public void testAsyncServletInputStreamWithEmptyRequestBody() { String message = ""; try { runTest(message, ASYNC_SERVLET, false, false); } catch (Throwable e) { throw new RuntimeException("test failed", e); } } private void runTestViaJavaImpl(final String message, String url) throws IOException { HttpURLConnection urlcon = null; try { String uri = getBaseUrl() + "/servletContext/" + url; urlcon = (HttpURLConnection) new URL(uri).openConnection(); urlcon.setInstanceFollowRedirects(true); urlcon.setRequestProperty("Connection", "close"); urlcon.setRequestMethod("POST"); urlcon.setDoInput(true); urlcon.setDoOutput(true); OutputStream os = urlcon.getOutputStream(); os.write(message.getBytes()); os.close(); Assert.assertEquals(StatusCodes.OK, urlcon.getResponseCode()); InputStream is = urlcon.getInputStream(); ByteArrayOutputStream bytes = new ByteArrayOutputStream(); byte[] buf = new byte[256]; int len; while ((len = is.read(buf)) > 0) { bytes.write(buf, 0, len); } is.close(); final String response = new String(bytes.toByteArray(), 0, bytes.size()); if (!message.equals(response)) { System.out.println(String.format("response=%s", Hex.encodeHexString(response.getBytes()))); } Assert.assertEquals(message, response); } finally { if (urlcon != null) { urlcon.disconnect(); } } } protected String getBaseUrl() { return DefaultServer.getDefaultServerURL(); } @Test public void testAsyncServletInputStream3() { String message = "to_user_id=7999&msg_body=msg3"; for (int i = 0; i < 200; ++i) { try { runTestViaJavaImpl(message, ASYNC_SERVLET); } catch (Throwable e) { System.out.println("test failed with i equal to " + i); e.printStackTrace(); throw new RuntimeException("test failed with i equal to " + i, e); } } } public void runTest(final String message, String url, boolean preamble, boolean offIOThread) throws IOException { TestHttpClient client = createClient(); try { String uri = getBaseUrl() + "/servletContext/" + url; HttpPost post = new HttpPost(uri); if (preamble && !message.isEmpty()) { post.addHeader("preamble", Integer.toString(message.length() / 2)); } if (offIOThread) { post.addHeader("offIoThread", "true"); } post.setEntity(new StringEntity(message)); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(message.length(), response.length()); Assert.assertEquals(message, response); } finally { client.getConnectionManager().shutdown(); } } public void runTestParallel(int concurrency, final String message, String url, boolean preamble, boolean offIOThread) throws Exception { CloseableHttpClient client = HttpClients.custom() .setMaxConnPerRoute(1000) .setSSLContext(DefaultServer.createClientSslContext()) .build(); byte[] messageBytes = message.getBytes(); try { ExecutorService executorService = Executors.newFixedThreadPool(concurrency); Callable task = new Callable() { @Override public Void call() throws Exception { String uri = getBaseUrl() + "/servletContext/" + url; HttpPost post = new HttpPost(uri); if (preamble && !message.isEmpty()) { post.addHeader("preamble", Integer.toString(message.length() / 2)); } if (offIOThread) { post.addHeader("offIoThread", "true"); } post.setEntity(new InputStreamEntity( // Server should wait for events from the client new RateLimitedInputStream(new ByteArrayInputStream(messageBytes)))); CloseableHttpResponse result = client.execute(post); try { Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(message.length(), response.length()); Assert.assertEquals(message, response); } finally { result.close(); } return null; } }; List> results = new ArrayList<>(); for (int i = 0; i < concurrency * 5; i++) { Future future = executorService.submit(task); results.add(future); } for(Future i : results) { i.get(); } executorService.shutdown(); Assert.assertTrue(executorService.awaitTermination(70, TimeUnit.SECONDS)); } finally { client.close(); } } private static final class RateLimitedInputStream extends InputStream { private final InputStream in; private int count; RateLimitedInputStream(InputStream in) { this.in = in; } @Override public int read() throws IOException { if (count++ % 1000 == 0) { try { Thread.sleep(1); } catch (InterruptedException e) { throw new InterruptedIOException(); } } return in.read(); } @Override public void close() throws IOException { in.close(); } } protected TestHttpClient createClient() { return new TestHttpClient(); } } AsyncInputStreamServlet.java000066400000000000000000000124071420065311100346520ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.streams; import java.io.ByteArrayOutputStream; import java.io.IOException; import javax.servlet.AsyncContext; import javax.servlet.ReadListener; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class AsyncInputStreamServlet extends HttpServlet { @Override protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { final int preamble = Math.max(0, req.getIntHeader("preamble")); final boolean offIoThread = req.getHeader("offIoThread") != null; final AsyncContext context = req.startAsync(); final ServletOutputStream outputStream = resp.getOutputStream(); ServletInputStream inputStream = req.getInputStream(); ByteArrayOutputStream data = new ByteArrayOutputStream(); for (int i = 0; i < preamble; i++) { int value = inputStream.read(); assert value >= 0 : "Stream is finished"; data.write(value); } final MyListener listener = new MyListener(outputStream, inputStream, data, context, offIoThread); inputStream.setReadListener(listener); if(!offIoThread) { outputStream.setWriteListener(listener); } } private class MyListener implements WriteListener, ReadListener { private final ServletOutputStream outputStream; private final ServletInputStream inputStream; private final ByteArrayOutputStream dataToWrite; private final AsyncContext context; private final boolean offIoThread; boolean done = false; int written = 0; MyListener( final ServletOutputStream outputStream, final ServletInputStream inputStream, ByteArrayOutputStream dataToWrite, final AsyncContext context, final boolean offIoThread) { this.outputStream = outputStream; this.inputStream = inputStream; this.dataToWrite = dataToWrite; this.context = context; this.offIoThread = offIoThread; } @Override public void onWritePossible() throws IOException { //we don't use async writes for the off IO thread case //as we can't make it thread safe if (offIoThread || outputStream.isReady()) { dataToWrite.writeTo(outputStream); written += dataToWrite.size(); dataToWrite.reset(); if (done) { context.complete(); } } } @Override public void onDataAvailable() throws IOException { if (offIoThread) { context.start(new Runnable() { @Override public void run() { doOnDataAvailable(); } }); } else { doOnDataAvailable(); } } private void doOnDataAvailable() { int read; try { while (inputStream.isReady()) { read = inputStream.read(); if (read == 0) { System.out.println("onDataAvailable> read 0x00"); } if (read != -1) { dataToWrite.write(read); } else { onWritePossible(); } } } catch (IOException e) { context.complete(); throw new RuntimeException(e); } } @Override public synchronized void onAllDataRead() throws IOException { done = true; if(offIoThread) { context.start(new Runnable() { @Override public void run() { try { onWritePossible(); } catch (IOException e) { e.printStackTrace(); } } }); } else { onWritePossible(); } } @Override public synchronized void onError(final Throwable t) { t.printStackTrace(); } } } AsyncOutputStreamServlet.java000066400000000000000000000067151420065311100350600ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.streams; import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.AsyncContext; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class AsyncOutputStreamServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { final boolean flush = req.getParameter("flush") != null; final boolean close = req.getParameter("close") != null; final boolean preable = req.getParameter("preamble") != null; final boolean offIoThread = req.getParameter("offIoThread") != null; final int reps = Integer.parseInt(req.getParameter("reps")); final AtomicInteger count = new AtomicInteger(); final AsyncContext context = req.startAsync(); final ServletOutputStream outputStream = resp.getOutputStream(); if(preable) { for(int i = 0; i < reps; ++i) { outputStream.write(ServletOutputStreamTestCase.message.getBytes()); } } WriteListener listener = new WriteListener() { @Override public synchronized void onWritePossible() throws IOException { while (outputStream.isReady() && count.get() < reps) { count.incrementAndGet(); outputStream.write(ServletOutputStreamTestCase.message.getBytes()); } if (count.get() == reps) { if (flush) { outputStream.flush(); } if (close) { outputStream.close(); } context.complete(); } } @Override public void onError(final Throwable t) { } }; outputStream.setWriteListener(offIoThread ? new WriteListener() { @Override public void onWritePossible() throws IOException { context.start(new Runnable() { @Override public void run() { try { listener.onWritePossible(); } catch (IOException e) { throw new RuntimeException(e); } } }); } @Override public void onError(Throwable throwable) { } } : listener); } } BlockingInputStreamServlet.java000066400000000000000000000031451420065311100353240ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.streams; import java.io.ByteArrayOutputStream; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class BlockingInputStreamServlet extends HttpServlet { @Override protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { final ByteArrayOutputStream out = new ByteArrayOutputStream(); final ServletInputStream inputStream = req.getInputStream(); byte[] buf = new byte[1024]; int read; while ((read = inputStream.read(buf)) != -1) { out.write(buf, 0, read); } resp.getOutputStream().write(out.toByteArray()); } } BlockingOutputStreamServlet.java000066400000000000000000000035021420065311100355220ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.streams; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class BlockingOutputStreamServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { boolean flush = req.getParameter("flush") != null; boolean close = req.getParameter("close") != null; boolean initialFlush = req.getParameter("initialFlush") != null; int reps = Integer.parseInt(req.getParameter("reps")); ServletOutputStream out = resp.getOutputStream(); if(initialFlush) { resp.flushBuffer(); } for(int i = 0; i < reps; ++i) { out.write(ServletOutputStreamTestCase.message.getBytes()); } if(flush) { out.flush(); } if(close) { out.close(); } } } ConnectionTerminationServlet.java000066400000000000000000000032501420065311100357060ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.streams; import io.undertow.servlet.handlers.ServletRequestContext; import javax.servlet.ServletException; import javax.servlet.WriteListener; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author Stuart Douglas */ public class ConnectionTerminationServlet extends HttpServlet{ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.startAsync(); resp.getOutputStream().print("hi"); resp.getOutputStream().setWriteListener(new WriteListener() { @Override public void onWritePossible() throws IOException { } @Override public void onError(Throwable t) { } }); ServletRequestContext.current().getExchange().getConnection().close(); } } ContentLengthCloseFlushServlet.java000066400000000000000000000033321420065311100361420ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.streams; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class ContentLengthCloseFlushServlet extends HttpServlet { private boolean completed = false; @Override protected synchronized void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { if (completed) { completed = false; resp.getWriter().write("OK"); } else { resp.setContentLength(1); ServletOutputStream stream = resp.getOutputStream(); stream.write('a'); //the stream should automatically close here, because it is the content length, but flush should still work stream.flush(); stream.close(); completed = true; } } } EagerAsyncInputStreamServlet.java000066400000000000000000000076751420065311100356310ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.streams; import java.io.ByteArrayOutputStream; import java.io.IOException; import javax.servlet.AsyncContext; import javax.servlet.ReadListener; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class EagerAsyncInputStreamServlet extends HttpServlet { @Override protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { req.startAsync(); MyListener listener = new MyListener(resp.getOutputStream(), req.getInputStream(), new ByteArrayOutputStream(), req.getAsyncContext()); resp.getOutputStream().setWriteListener(listener); req.getInputStream().setReadListener(listener); req.getInputStream().isReady(); } private class MyListener implements WriteListener, ReadListener { private final ServletOutputStream outputStream; private final ServletInputStream inputStream; private final ByteArrayOutputStream dataToWrite; private final AsyncContext context; boolean done = false; int written = 0; MyListener( final ServletOutputStream outputStream, final ServletInputStream inputStream, ByteArrayOutputStream dataToWrite, final AsyncContext context) { this.outputStream = outputStream; this.inputStream = inputStream; this.dataToWrite = dataToWrite; this.context = context; } @Override public void onWritePossible() throws IOException { //we don't use async writes for the off IO thread case //as we can't make it thread safe if (outputStream.isReady()) { dataToWrite.writeTo(outputStream); written += dataToWrite.size(); dataToWrite.reset(); if (done) { context.complete(); } } } @Override public void onDataAvailable() throws IOException { doOnDataAvailable(); } private void doOnDataAvailable() { int read; try { while (inputStream.isReady()) { read = inputStream.read(); if (read == 0) { System.out.println("onDataAvailable> read 0x00"); } if (read != -1) { dataToWrite.write(read); } else { onWritePossible(); } } } catch (IOException e) { context.complete(); throw new RuntimeException(e); } } @Override public synchronized void onAllDataRead() throws IOException { done = true; onWritePossible(); } @Override public synchronized void onError(final Throwable t) { t.printStackTrace(); } } } EarlyCloseClientServlet.java000066400000000000000000000050261420065311100346010ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.streams; import io.undertow.testutils.DefaultServer; import org.junit.runner.RunWith; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.concurrent.CountDownLatch; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class EarlyCloseClientServlet extends HttpServlet { private static volatile boolean exceptionThrown; private static volatile boolean completedNormally; private static volatile CountDownLatch latch = new CountDownLatch(1); @Override protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { try { final ByteArrayOutputStream out = new ByteArrayOutputStream(); final ServletInputStream inputStream = req.getInputStream(); byte[] buf = new byte[1024]; int read; while ((read = inputStream.read(buf)) != -1) { out.write(buf, 0, read); } resp.getOutputStream().write(out.toByteArray()); completedNormally = true; } catch (IOException e) { exceptionThrown = true; } finally { latch.countDown(); } } public static void reset() { latch = new CountDownLatch(1); completedNormally = false; exceptionThrown = false; } public static boolean isExceptionThrown() { return exceptionThrown; } public static boolean isCompletedNormally() { return completedNormally; } public static CountDownLatch getLatch() { return latch; } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/EarlyCloseServlet.java000066400000000000000000000036441420065311100335250ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.streams; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import io.undertow.server.ServerConnection; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.servlet.spec.HttpServletRequestImpl; import io.undertow.testutils.DefaultServer; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class EarlyCloseServlet extends HttpServlet { private volatile ServerConnection connection; @Override protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { req.getInputStream().close(); HttpServletRequestImpl request = ServletRequestContext.requireCurrent().getOriginalRequest(); if(connection == null) { connection = request.getExchange().getConnection(); } else if(!DefaultServer.isAjp() && !DefaultServer.isProxy() && connection != request.getExchange().getConnection()) { throw new RuntimeException("Connection not persistent"); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/ForceDrainServlet.java000066400000000000000000000025761420065311100335020ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.streams; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.junit.runner.RunWith; import io.undertow.testutils.DefaultServer; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ForceDrainServlet extends HttpServlet { @Override protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { resp.getOutputStream().write("close".getBytes("UTF-8")); resp.getOutputStream().close(); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/ResetBufferServlet.java000066400000000000000000000030201420065311100336630ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.streams; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author Stuart Douglas */ public class ResetBufferServlet extends HttpServlet { public static final String MESSAGE = "hello world"; @Override protected synchronized void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { resp.setContentLength(MESSAGE.length()); ServletOutputStream outputStream = resp.getOutputStream(); outputStream.print("foo"); resp.resetBuffer(); outputStream.print(MESSAGE); outputStream.close(); } } ServletInputStreamConnectionTerminationTestCase.java000066400000000000000000000054761420065311100415520ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.streams; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.HttpOneOnly; import io.undertow.testutils.ProxyIgnore; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import java.io.IOException; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) @HttpOneOnly @ProxyIgnore public class ServletInputStreamConnectionTerminationTestCase { public static final String HELLO_WORLD = "Hello World"; @BeforeClass public static void setup() throws ServletException { DeploymentUtils.setupServlet( new ServletInfo("term", ConnectionTerminationServlet.class) .setAsyncSupported(true) .addMapping("/term")); } @Test public void testConnectionTermination() throws IOException { StringBuilder builder = new StringBuilder(1000 * HELLO_WORLD.length()); for (int j = 0; j < 1000; ++j) { builder.append(HELLO_WORLD); } String message = builder.toString(); TestHttpClient client = new TestHttpClient(); try { String uri = DefaultServer.getDefaultServerURL() + "/servletContext/term"; HttpPost post = new HttpPost(uri); post.setEntity(new StringEntity(message)); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.fail(); } catch (IOException expected) { //expected } finally { client.getConnectionManager().shutdown(); } } } ServletInputStreamDrainTestCase.java000066400000000000000000000064601420065311100362700ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.streams; import javax.servlet.ServletException; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; /** * Tests calling close on the input stream before all data has been read. * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ServletInputStreamDrainTestCase { public static final String SERVLET = "servlet"; private static final String HELLO_WORLD = "Hello World"; @BeforeClass public static void setup() throws ServletException { DeploymentUtils.setupServlet( new ServletInfo(SERVLET, ForceDrainServlet.class) .addMapping("/" + SERVLET)); } @Test public void testServletInputStreamEarlyClose() throws Exception { StringBuilder builder = new StringBuilder(1000 * HELLO_WORLD.length()); for (int i = 0; i < 10; ++i) { try { for (int j = 0; j < 1000; ++j) { builder.append(HELLO_WORLD); } } catch (Throwable e) { throw new RuntimeException("test failed with i equal to " + i, e); } } String message = builder.toString(); TestHttpClient client = new TestHttpClient(); try { String uri = DefaultServer.getDefaultServerURL() + "/servletContext/" + SERVLET; HttpPost post = new HttpPost(uri); post.setEntity(new StringEntity(message)); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("close",HttpClientUtils.readResponse(result)); result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("close",HttpClientUtils.readResponse(result)); result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); Assert.assertEquals("close",HttpClientUtils.readResponse(result)); } finally { client.getConnectionManager().shutdown(); } } } ServletInputStreamEarlyCloseClientSideTestCase.java000066400000000000000000000062011420065311100412320ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.streams; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.testutils.TestHttpClient; import org.junit.Assert; import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import java.io.OutputStream; import java.net.Socket; import java.util.concurrent.TimeUnit; /** * Tests the behaviour of the input stream when the connection is closed on the client side *

    * https://issues.jboss.org/browse/WFLY-4827 * * @author Stuart Douglas */ @RunWith(DefaultServer.class) @HttpOneOnly public class ServletInputStreamEarlyCloseClientSideTestCase { public static final String SERVLET = "servlet"; @BeforeClass public static void setup() throws ServletException { DeploymentUtils.setupServlet( new ServletInfo(SERVLET, EarlyCloseClientServlet.class) .addMapping("/" + SERVLET)); } @Test public void testServletInputStreamEarlyClose() throws Exception { Assume.assumeFalse(DefaultServer.isH2()); TestHttpClient client = new TestHttpClient(); EarlyCloseClientServlet.reset(); try (Socket socket = new Socket()) { socket.connect(DefaultServer.getDefaultServerAddress()); try { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10000; ++i) { sb.append("hello world\r\n"); } //send a large request that is too small, then kill the socket String request = "POST /servletContext/" + SERVLET + " HTTP/1.1\r\nHost:localhost\r\nContent-Length:" + sb.length() + 100 + "\r\n\r\n" + sb.toString(); OutputStream outputStream = socket.getOutputStream(); outputStream.write(request.getBytes("US-ASCII")); outputStream.flush(); socket.close(); Assert.assertTrue(EarlyCloseClientServlet.getLatch().await(10, TimeUnit.SECONDS)); Assert.assertFalse(EarlyCloseClientServlet.isCompletedNormally()); Assert.assertTrue(EarlyCloseClientServlet.isExceptionThrown()); } finally { client.getConnectionManager().shutdown(); } } } } ServletInputStreamEarlyCloseTestCase.java000066400000000000000000000051431420065311100372720ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.streams; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; /** * Tests calling close on the input stream before all data has been read. * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ServletInputStreamEarlyCloseTestCase { public static final String SERVLET = "servlet"; @Test public void testServletInputStreamEarlyClose() throws Exception { DeploymentUtils.setupServlet( new ServletInfo(SERVLET, EarlyCloseServlet.class) .addMapping("/" + SERVLET)); TestHttpClient client = new TestHttpClient(); try { String uri = DefaultServer.getDefaultServerURL() + "/servletContext/" + SERVLET; HttpPost post = new HttpPost(uri); post.setEntity(new StringEntity("A non-empty request body")); HttpResponse result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); result = client.execute(post); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } } ServletInputStreamRequestBufferingTestCase.java000066400000000000000000000051201420065311100405030ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.streams; import javax.servlet.ServletContext; import io.undertow.server.handlers.RequestBufferingHandler; import io.undertow.servlet.ServletExtension; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Carter Kozak */ @RunWith(DefaultServer.class) public class ServletInputStreamRequestBufferingTestCase extends AbstractServletInputStreamTestCase { @BeforeClass public static void setup() { DeploymentUtils.setupServlet( new ServletExtension() { @Override public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servletContext) { deploymentInfo.addInitialHandlerChainWrapper(new RequestBufferingHandler.Wrapper(1)); } }, new ServletInfo(BLOCKING_SERVLET, BlockingInputStreamServlet.class) .addMapping("/" + BLOCKING_SERVLET), new ServletInfo(ASYNC_SERVLET, AsyncInputStreamServlet.class) .addMapping("/" + ASYNC_SERVLET) .setAsyncSupported(true)); } @Test public void testAsyncServletInputStreamInParallel() throws Exception { Assume.assumeFalse(DefaultServer.isH2upgrade()); // FIXME UNDERTOW-1818 bytes out of order super.testAsyncServletInputStreamInParallel(); } @Test public void testAsyncServletInputStreamInParallelOffIoThread() throws Exception { Assume.assumeFalse(DefaultServer.isH2upgrade()); // FIXME UNDERTOW-1818 bytes out of order super.testAsyncServletInputStreamInParallelOffIoThread(); } } ServletInputStreamSSLTestCase.java000066400000000000000000000033311420065311100356660ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/* * JBoss, Home of Professional Open Source. * Copyright 2018 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * 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. */ package io.undertow.servlet.test.streams; import java.io.IOException; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.TestHttpClient; @RunWith(DefaultServer.class) public class ServletInputStreamSSLTestCase extends ServletInputStreamTestCase { @BeforeClass public static void ssl() throws Exception { DefaultServer.startSSLServer(); } @AfterClass public static void stopssl() throws IOException { DefaultServer.stopSSLServer(); } @Override protected TestHttpClient createClient() { TestHttpClient client = super.createClient(); client.setSSLContext(DefaultServer.createClientSslContext()); return client; } @Test @Ignore public void testAsyncServletInputStream3() { } @Override protected String getBaseUrl() { return DefaultServer.getDefaultServerSSLAddress(); } } ServletInputStreamTestCase.java000066400000000000000000000053441420065311100353120ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.streams; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ServletInputStreamTestCase extends AbstractServletInputStreamTestCase { @BeforeClass public static void setup() { DeploymentUtils.setupServlet( new ServletInfo(BLOCKING_SERVLET, BlockingInputStreamServlet.class) .addMapping("/" + BLOCKING_SERVLET), new ServletInfo(ASYNC_SERVLET, AsyncInputStreamServlet.class) .addMapping("/" + ASYNC_SERVLET) .setAsyncSupported(true), new ServletInfo(ASYNC_EAGER_SERVLET, EagerAsyncInputStreamServlet.class) .addMapping("/" + ASYNC_EAGER_SERVLET) .setAsyncSupported(true)); } @Test public void testAsyncServletInputStreamEagerIsReady() { //for(int h = 0; h < 20 ; ++h) { StringBuilder builder = new StringBuilder(1000 * HELLO_WORLD.length()); for (int i = 0; i < 10; ++i) { try { for (int j = 0; j < 10000; ++j) { builder.append(HELLO_WORLD); } String message = builder.toString(); runTest(message, ASYNC_EAGER_SERVLET, false, false); } catch (Throwable e) { throw new RuntimeException("test failed with i equal to " + i, e); } } //} } @Override @Test @Ignore ("UNDERTOW-1927 503 result received sporadically") // FIXME public void testAsyncServletInputStreamInParallelOffIoThread() { } @Override @Test @Ignore ("UNDERTOW-1927 503 result received sporadically UNDERTOW-1818 bytes out of order") // FIXME public void testAsyncServletInputStreamInParallel() { } } ServletOutputStreamSSLTestCase.java000066400000000000000000000045051420065311100360730ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/* * JBoss, Home of Professional Open Source. * Copyright 2018 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * 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. */ package io.undertow.servlet.test.streams; import java.io.IOException; import org.junit.AfterClass; import org.junit.Assume; import org.junit.BeforeClass; import org.junit.runner.RunWith; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.TestHttpClient; @RunWith(DefaultServer.class) public class ServletOutputStreamSSLTestCase extends ServletOutputStreamTestCase { @BeforeClass public static void ssl() throws Exception { DefaultServer.startSSLServer(); } @AfterClass public static void stopssl() throws IOException { DefaultServer.stopSSLServer(); } @Override protected TestHttpClient createClient() { TestHttpClient client = super.createClient(); client.setSSLContext(DefaultServer.createClientSslContext()); return client; } @Override protected String getBaseUrl() { return DefaultServer.getDefaultServerSSLAddress(); } @Override public void testAsyncServletOutputStreamOffIOThread() { // FIXME UNDERTOW-1948 temporarily ignore the test (throws SocketTimeoutException) Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows") && DefaultServer.isProxy() && DefaultServer.isAjp()); super.testAsyncServletOutputStreamOffIOThread(); } @Override public void testAsyncServletOutputStreamWithPreable() { // FIXME UNDERTOW-1948 temporarily ignore the exception (throws SocketTimeoutException) Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows") && DefaultServer.isProxy() && DefaultServer.isAjp()); super.testAsyncServletOutputStreamWithPreable(); } } ServletOutputStreamTestCase.java000066400000000000000000000300641420065311100355100ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/streams/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.streams; import java.io.IOException; import javax.servlet.ServletContext; import javax.servlet.ServletException; import io.undertow.servlet.ServletExtension; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.ConnectionClosedException; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class ServletOutputStreamTestCase { public static String message; public static final String HELLO_WORLD = "Hello World"; public static final String BLOCKING_SERVLET = "blockingOutput"; public static final String ASYNC_SERVLET = "asyncOutput"; public static final String CONTENT_LENGTH_SERVLET = "contentLength"; public static final String RESET = "reset"; public static final String START = "START"; public static final String END = "END"; @BeforeClass public static void setup() throws ServletException { DeploymentUtils.setupServlet(new ServletExtension() { @Override public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servletContext) { deploymentInfo.setIgnoreFlush(false); } }, new ServletInfo(BLOCKING_SERVLET, BlockingOutputStreamServlet.class) .addMapping("/" + BLOCKING_SERVLET), new ServletInfo(ASYNC_SERVLET, AsyncOutputStreamServlet.class) .addMapping("/" + ASYNC_SERVLET) .setAsyncSupported(true), new ServletInfo(CONTENT_LENGTH_SERVLET, ContentLengthCloseFlushServlet.class) .addMapping("/" + CONTENT_LENGTH_SERVLET), new ServletInfo(RESET, ResetBufferServlet.class).addMapping("/" + RESET)); } @Test public void testFlushAndCloseWithContentLength() throws Exception { TestHttpClient client = createClient(); try { String uri = getBaseUrl() + "/servletContext/" + CONTENT_LENGTH_SERVLET; HttpGet get = new HttpGet(uri); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("a", response); get = new HttpGet(uri); result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); response = HttpClientUtils.readResponse(result); Assert.assertEquals("OK", response); } finally { client.getConnectionManager().shutdown(); } } protected TestHttpClient createClient() { return new TestHttpClient(); } @Test public void testResetBuffer() throws Exception { TestHttpClient client = createClient(); try { String uri = getBaseUrl() + "/servletContext/" + RESET; HttpGet get = new HttpGet(uri); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); String response = HttpClientUtils.readResponse(result); Assert.assertEquals("hello world", response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testBlockingServletOutputStream() throws IOException { Assume.assumeFalse(DefaultServer.isH2upgrade()); // FIXME UNDERTOW-1937 returns 503 instead of 200 message = START + HELLO_WORLD + END; runTest(message, BLOCKING_SERVLET, false, true, 1, true, false, false); StringBuilder builder = new StringBuilder(1000 * HELLO_WORLD.length()); builder.append(START); for (int i = 0; i < 10; ++i) { try { for (int j = 0; j < 1000; ++j) { builder.append(HELLO_WORLD); } String message = builder.toString() + END; runTest(message, BLOCKING_SERVLET, false, false, 1, false, false, false); runTest(message, BLOCKING_SERVLET, true, false, 10, false, false, false); runTest(message, BLOCKING_SERVLET, false, true, 3, false, false, false); runTest(message, BLOCKING_SERVLET, true, true, 7, false, false, false); } catch (Throwable e) { throw new RuntimeException("test failed with i equal to " + i, e); } } } @Test public void testChunkedResponseWithInitialFlush() throws IOException { message = START + HELLO_WORLD + END; runTest(message, BLOCKING_SERVLET, false, true, 1, true, false, false); } @Test public void testAsyncServletOutputStream() { Assume.assumeFalse(DefaultServer.isH2upgrade()); // FIXME UNDERTOW-1937 returns 503 instead of 200 StringBuilder builder = new StringBuilder(1000 * HELLO_WORLD.length()); builder.append(START); for (int i = 0; i < 10; ++i) { try { for (int j = 0; j < 10000; ++j) { builder.append(HELLO_WORLD); } String message = builder.toString() + END; runTest(message, ASYNC_SERVLET, false, false, 1, false, false, false); runTest(message, ASYNC_SERVLET, true, false, 10, false, false, false); runTest(message, ASYNC_SERVLET, false, true, 3, false, false, false); runTest(message, ASYNC_SERVLET, true, true, 7, false, false, false); } catch (Exception e) { throw new RuntimeException("test failed with i equal to " + i, e); } } } @Test public void testAsyncServletOutputStreamOffIOThread() { Assume.assumeFalse(DefaultServer.isH2upgrade()); // FIXME UNDERTOW-1937 returns 503 instead of 200 StringBuilder builder = new StringBuilder(1000 * HELLO_WORLD.length()); builder.append(START); for (int i = 0; i < 10; ++i) { try { for (int j = 0; j < 10000; ++j) { builder.append(HELLO_WORLD); } String message = builder.toString() + END; runTest(message, ASYNC_SERVLET, false, false, 1, false, false, true); runTest(message, ASYNC_SERVLET, true, false, 10, false, false, true); runTest(message, ASYNC_SERVLET, false, true, 3, false, false, true); runTest(message, ASYNC_SERVLET, true, true, 7, false, false, true); } catch (Exception e) { throw new RuntimeException("test failed with i equal to " + i, e); } } } @Test public void testAsyncServletOutputStreamWithPreableOffIOThread() { Assume.assumeFalse(DefaultServer.isH2upgrade()); // FIXME UNDERTOW-1937 returns 503 instead of 200 StringBuilder builder = new StringBuilder(1000 * HELLO_WORLD.length()); builder.append(START); for (int i = 0; i < 10; ++i) { try { for (int j = 0; j < 10000; ++j) { builder.append(HELLO_WORLD); } String message = builder.toString() + END; runTest(message, ASYNC_SERVLET, false, false, 1, false, true, true); runTest(message, ASYNC_SERVLET, true, false, 10, false, true, true); runTest(message, ASYNC_SERVLET, false, true, 3, false, true, true); runTest(message, ASYNC_SERVLET, true, true, 7, false, true, true); } catch (Exception e) { throw new RuntimeException("test failed with i equal to " + i, e); } } } @Test public void testAsyncServletOutputStreamWithPreable() { Assume.assumeFalse(DefaultServer.isH2upgrade()); // FIXME UNDERTOW-1937 returns 503 instead of 200 StringBuilder builder = new StringBuilder(1000 * HELLO_WORLD.length()); builder.append(START); for (int i = 0; i < 10; ++i) { try { for (int j = 0; j < 10000; ++j) { builder.append(HELLO_WORLD); } String message = builder.toString() + END; runTest(message, ASYNC_SERVLET, false, false, 1, false, true, false); runTest(message, ASYNC_SERVLET, true, false, 10, false, true, false); runTest(message, ASYNC_SERVLET, false, true, 3, false, true, false); runTest(message, ASYNC_SERVLET, true, true, 7, false, true, false); } catch (Exception e) { throw new RuntimeException("test failed with i equal to " + i, e); } } } public void runTest(final String message, String url, final boolean flush, final boolean close, int reps, boolean initialFlush, boolean writePreable, boolean offIoThread) throws IOException { TestHttpClient client = createClient(); try { ServletOutputStreamTestCase.message = message; String uri = getBaseUrl() + "/servletContext/" + url + "?reps=" + reps + "&"; if (flush) { uri = uri + "flush=true&"; } if (close) { uri = uri + "close=true&"; } if(initialFlush) { uri = uri + "initialFlush=true&"; } if(writePreable) { uri = uri + "preamble=true&"; } if(offIoThread) { uri += "offIoThread=true&"; } HttpGet get = new HttpGet(uri); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); StringBuilder builder = new StringBuilder(reps * message.length()); for (int j = 0; j < reps; ++j) { builder.append(message); } if(writePreable) { builder.append(builder.toString()); //content gets written twice in this case } final String response; try { response = HttpClientUtils.readResponse(result); } catch (ConnectionClosedException prematureEndOfChunkException) { Assert.assertEquals("Premature end of chunk coded message body: closing chunk expected", prematureEndOfChunkException.getMessage()); return; // FIXME UNDERTOW-1945 temporarily ignore the exception } String expected = builder.toString(); Assert.assertTrue("Must start with START", response.startsWith(START)); Assert.assertTrue("Must end with END", response.endsWith(END)); Assert.assertEquals(expected.length(), response.length()); Assert.assertEquals(expected, response); } finally { client.getConnectionManager().shutdown(); } } protected String getBaseUrl() { return DefaultServer.getDefaultServerURL(); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/upgrade/000077500000000000000000000000001420065311100272155ustar00rootroot00000000000000AsyncUpgradeServlet.java000066400000000000000000000075731420065311100337470ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/upgrade/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.upgrade; import java.io.IOException; import javax.servlet.ReadListener; import javax.servlet.ServletException; import javax.servlet.WriteListener; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpUpgradeHandler; import javax.servlet.http.WebConnection; import io.undertow.UndertowLogger; /** * Simple upgrade servlet. Because the apache http client does not handle upgrades we keep faking http * after we upgrade * * @author Stuart Douglas */ public class AsyncUpgradeServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { req.upgrade(Handler.class); } public static class Handler implements HttpUpgradeHandler { @Override public void init(final WebConnection wc) { Listener listener = new Listener(wc); try { //we have to set the write listener before the read listener //otherwise the output stream could be written to before it is //in async mode wc.getOutputStream().setWriteListener(listener); wc.getInputStream().setReadListener(listener); } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); } } @Override public void destroy() { } } private static class Listener implements WriteListener, ReadListener { private final WebConnection connection; StringBuilder builder = new StringBuilder(); boolean reading = true; private Listener(final WebConnection connection) { this.connection = connection; } @Override public synchronized void onDataAvailable() throws IOException { byte[] data = new byte[100]; while (connection.getInputStream().isReady()) { int read; if ((read = connection.getInputStream().read(data)) != -1) { builder.append(new String(data, 0, read)); } if (builder.toString().endsWith("\r\n\r\n")) { reading = false; onWritePossible(); } } } @Override public void onAllDataRead() throws IOException { } @Override public synchronized void onWritePossible() throws IOException { if(builder.toString().equals("exit\r\n\r\n")) { try { connection.close(); } catch (Exception e) { throw new RuntimeException(e); } } if (reading) { return; } if (connection.getOutputStream().isReady()) { connection.getOutputStream().write(builder.toString().getBytes()); builder = new StringBuilder(); reading = true; } } @Override public void onError(final Throwable t) { } } } SimpleUpgradeTestCase.java000066400000000000000000000062671420065311100342110ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/upgrade/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.upgrade; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import javax.servlet.ServletException; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @HttpOneOnly @RunWith(DefaultServer.class) public class SimpleUpgradeTestCase { @BeforeClass public static void setup() throws ServletException { DeploymentUtils.setupServlet( new ServletInfo("upgradeServlet", UpgradeServlet.class) .addMapping("/upgrade"), new ServletInfo("upgradeAsyncServlet", AsyncUpgradeServlet.class) .addMapping("/asyncupgrade")); } @Test public void testBlockingUpgrade() throws IOException { runTest("/servletContext/upgrade"); } @Test public void testAsyncUpgrade() throws IOException { runTest("/servletContext/asyncupgrade"); } public void runTest(final String url) throws IOException { final Socket socket = new Socket(DefaultServer.getHostAddress("default"), DefaultServer.getHostPort("default")); InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); out.write(("GET " + url + " HTTP/1.1\r\nHost:default\r\nConnection: upgrade\r\nUpgrade: servlet\r\n\r\n").getBytes()); out.flush(); Assert.assertTrue(readBytes(in).startsWith("HTTP/1.1 101 Switching Protocols\r\n")); out.write("Echo Messages\r\n\r\n".getBytes()); out.flush(); Assert.assertEquals("Echo Messages\r\n\r\n", readBytes(in)); out.write("Echo Messages2\r\n\r\n".getBytes()); out.flush(); Assert.assertEquals("Echo Messages2\r\n\r\n", readBytes(in)); out.write("exit\r\n\r\n".getBytes()); out.flush(); out.close(); } private String readBytes(final InputStream in) throws IOException { final StringBuilder builder = new StringBuilder(); byte[] buf = new byte[100]; int read; while (!builder.toString().contains("\r\n\r\n") && (read = in.read(buf)) != -1) { //awesome hack builder.append(new String(buf, 0, read)); } return builder.toString(); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/upgrade/SslUpgradeTestCase.java000066400000000000000000000075031420065311100335720ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.upgrade; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import javax.servlet.ServletException; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.testutils.TestHttpClient; /** * @author Stuart Douglas */ @HttpOneOnly @RunWith(DefaultServer.class) public class SslUpgradeTestCase { @BeforeClass public static void setup() throws ServletException, IOException { DefaultServer.startSSLServer(); DeploymentUtils.setupServlet( new ServletInfo("upgradeServlet", UpgradeServlet.class) .addMapping("/upgrade"), new ServletInfo("upgradeAsyncServlet", AsyncUpgradeServlet.class) .addMapping("/asyncupgrade")); } @AfterClass public static void stop() throws IOException { DefaultServer.stopSSLServer(); } @Test public void testBlockingUpgrade() throws IOException { runTest("/servletContext/upgrade"); } @Test public void testAsyncUpgrade() throws IOException { runTest("/servletContext/asyncupgrade"); } public void runTest(final String url) throws IOException { TestHttpClient client = new TestHttpClient(); try { final Socket socket = DefaultServer.getClientSSLContext().getSocketFactory().createSocket(new Socket(DefaultServer.getHostAddress("default"), DefaultServer.getHostSSLPort("default")), DefaultServer.getHostAddress("default"), DefaultServer.getHostSSLPort("default"), true); InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); out.write(("GET " + url + " HTTP/1.1\r\nHost: default\r\nConnection: upgrade\r\nUpgrade:servlet\r\n\r\n").getBytes()); out.flush(); String bytes = readBytes(in); Assert.assertTrue(bytes, bytes.startsWith("HTTP/1.1 101 Switching Protocols\r\n")); out.write("Echo Messages\r\n\r\n".getBytes()); out.flush(); Assert.assertEquals("Echo Messages\r\n\r\n", readBytes(in)); out.write("Echo Messages2\r\n\r\n".getBytes()); out.flush(); Assert.assertEquals("Echo Messages2\r\n\r\n", readBytes(in)); out.write("exit\r\n\r\n".getBytes()); out.flush(); socket.close(); } finally { client.getConnectionManager().shutdown(); } } private String readBytes(final InputStream in) throws IOException { final StringBuilder builder = new StringBuilder(); byte[] buf = new byte[100]; int read; while (!builder.toString().endsWith("\r\n\r\n") && (read = in.read(buf)) != -1) { //awesome hack builder.append(new String(buf, 0, read)); } return builder.toString(); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/upgrade/UpgradeServlet.java000066400000000000000000000053701420065311100330210ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.upgrade; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpUpgradeHandler; import javax.servlet.http.WebConnection; import io.undertow.UndertowLogger; /** * Simple upgrade servlet. Because the apache http client does not handle upgrades we keep faking http * after we upgrade * * @author Stuart Douglas */ public class UpgradeServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { req.upgrade(Handler.class); } public static class Handler implements HttpUpgradeHandler { @Override public void init(final WebConnection wc) { try { String message = ""; do { //an incredibly poxy implementation of an echo server, that uses /r/n/r/n to delineate messages final StringBuilder builder = new StringBuilder(); byte[] data = new byte[100]; int read; while (!builder.toString().endsWith("\r\n\r\n") && (read = wc.getInputStream().read(data)) != -1) { builder.append(new String(data, 0, read)); } wc.getOutputStream().print(builder.toString()); wc.getOutputStream().flush(); message = builder.toString(); } while (!"exit\r\n\r\n".equals(message)); } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); } finally { try { wc.close(); } catch (Exception e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(new IOException(e)); } } } @Override public void destroy() { } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/util/000077500000000000000000000000001420065311100265435ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/util/DeploymentUtils.java000066400000000000000000000055641420065311100325610ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.util; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.ServletExtension; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.testutils.DefaultServer; /** * @author Stuart Douglas */ public class DeploymentUtils { /** * Sets up a simple servlet deployment with the provided servlets. * * This is just a convenience method for simple deployments * * @param servlets The servlets to add */ public static Deployment setupServlet(final ServletInfo... servlets) { return setupServlet(null, servlets); } /** * Sets up a simple servlet deployment with the provided servlets. * * This is just a convenience method for simple deployments * * @param servlets The servlets to add */ public static Deployment setupServlet(final ServletExtension servletExtension, final ServletInfo... servlets) { final PathHandler pathHandler = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServlets(servlets); if(servletExtension != null) { builder.addServletExtension(servletExtension); } DeploymentManager manager = container.addDeployment(builder); manager.deploy(); try { pathHandler.addPrefixPath(builder.getContextPath(), manager.start()); } catch (ServletException e) { throw new RuntimeException(e); } DefaultServer.setRootHandler(pathHandler); return manager.getDeployment(); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/util/EmptyServlet.java000066400000000000000000000027311420065311100320540ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.util; import java.io.IOException; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; /** * @author Stuart Douglas */ public class EmptyServlet implements Servlet { @Override public void init(final ServletConfig config) throws ServletException { } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(final ServletRequest req, final ServletResponse res) throws ServletException, IOException { } @Override public String getServletInfo() { return null; } @Override public void destroy() { } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/util/MessageFilter.java000066400000000000000000000031351420065311100321420ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.util; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; /** * @author Stuart Douglas */ public class MessageFilter implements Filter { public static final String MESSAGE = "message"; private String message; @Override public void init(final FilterConfig filterConfig) throws ServletException { message = filterConfig.getInitParameter(MESSAGE); } @Override public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { response.getWriter().write(message); chain.doFilter(request,response); } @Override public void destroy() { } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/util/MessageServlet.java000066400000000000000000000033711420065311100323430ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.util; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class MessageServlet extends HttpServlet { public static final String MESSAGE = "message"; private String message; @Override public void init(final ServletConfig config) throws ServletException { super.init(config); message = config.getInitParameter(MESSAGE); } @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { PrintWriter writer = resp.getWriter(); writer.write(message); writer.close(); } @Override protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/util/ParameterEchoServlet.java000066400000000000000000000105231420065311100334730ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.util; import java.io.IOException; import java.io.PrintWriter; import java.util.Enumeration; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Istvan Szabo */ public class ParameterEchoServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { String echoType = req.getParameter("type"); if (echoType == null) echoType = "values"; StringBuilder sb = new StringBuilder(); if (echoType.equals("values")) { sb = echoParameterValues(req); } else if (echoType.equals("names")) { sb = echoParameterNames(req); } else if (echoType.equals("map")) { sb = echoParameterMap(req); } else { resp.sendError(400); return; } PrintWriter writer = resp.getWriter(); writer.write(sb.toString()); writer.close(); } private StringBuilder echoParameterMap(HttpServletRequest req) { StringBuilder sb = new StringBuilder(); Map map = req.getParameterMap(); for (Map.Entry entry: map.entrySet()) { sb.append(entry.getKey()).append("="); for (int i = 0; i < entry.getValue().length; i++) { if (i > 0) { sb.append(','); } sb.append(entry.getValue()[i]); } sb.append(";"); } return sb; } private StringBuilder echoParameterNames(HttpServletRequest req) { StringBuilder sb = new StringBuilder(); Enumeration names = req.getParameterNames(); while (names.hasMoreElements()) { sb.append(names.nextElement()); if (names.hasMoreElements()) sb.append(","); } return sb; } private StringBuilder echoParameterValues(HttpServletRequest req) { StringBuilder sb = new StringBuilder(); String[] param1Values = req.getParameterValues("param1"); String[] param2Values = req.getParameterValues("param2"); String[] param3Values = req.getParameterValues("param3"); if (param1Values != null) { sb.append("param1=\'"); for (int i = 0; i < param1Values.length; i++) { if (i > 0) { sb.append(','); } sb.append(param1Values[i]); } sb.append('\''); } if (param2Values != null) { sb.append("param2=\'"); for (int i = 0; i < param2Values.length; i++) { if (i > 0) { sb.append(','); } sb.append(param2Values[i]); } sb.append('\''); } if (param3Values != null) { sb.append("param3=\'"); for (int i = 0; i < param3Values.length; i++) { if (i > 0) { sb.append(','); } sb.append(param3Values[i]); } sb.append('\''); } return sb; } @Override protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } @Override protected void doPut(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/util/PathTestServlet.java000066400000000000000000000027171420065311100325160ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.util; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class PathTestServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { PrintWriter out = resp.getWriter(); out.print("pathInfo:" + req.getPathInfo()); out.print(" queryString:" + req.getQueryString()); out.print(" servletPath:" + req.getServletPath()); out.print(" requestUri:" + req.getRequestURI()); } } ProxyPeerXForwardedHandlerServlet.java000066400000000000000000000053021420065311100361150ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/util/* * JBoss, Home of Professional Open Source. * Copyright 2021 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.util; import io.undertow.servlet.test.constant.GenericServletConstants; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; /** * @author Moulali Shikalwadi */ public class ProxyPeerXForwardedHandlerServlet extends HttpServlet { @Override public void init(final ServletConfig config) throws ServletException { super.init(config); } @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { Map resMap = new HashMap(); resMap.put(GenericServletConstants.SERVER_NAME, req.getServerName()); resMap.put(GenericServletConstants.SERVER_PORT, String.valueOf(req.getServerPort())); resMap.put(GenericServletConstants.LOCAL_NAME, req.getLocalName()); resMap.put(GenericServletConstants.LOCAL_ADDR, req.getLocalAddr()); resMap.put(GenericServletConstants.LOCAL_PORT, String.valueOf(req.getLocalPort())); resMap.put(GenericServletConstants.REMOTE_ADDR, req.getRemoteAddr()); resMap.put(GenericServletConstants.REMOTE_PORT, String.valueOf(req.getRemotePort())); PrintWriter writer = resp.getWriter(); writer.write(convertWithStream(resMap)); writer.close(); } @Override protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } public String convertWithStream(Map map) { String mapAsString = map.keySet().stream() .map(key -> key + "=" + map.get(key)) .collect(Collectors.joining(", ", "{", "}")); return mapAsString; } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/util/SetHeaderFilter.java000066400000000000000000000033001420065311100324140ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.util; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class SetHeaderFilter implements Filter { private String header; private String value; @Override public void init(final FilterConfig filterConfig) throws ServletException { header = filterConfig.getInitParameter("header"); value = filterConfig.getInitParameter("value"); } @Override public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { ((HttpServletResponse) response).setHeader(header, value); chain.doFilter(request, response); } @Override public void destroy() { } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/util/TXServlet.java000066400000000000000000000040041420065311100313040ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.util; import java.io.IOException; import java.net.URISyntaxException; import java.nio.channels.FileChannel; import java.nio.file.Paths; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import io.undertow.io.BufferWritableOutputStream; /** * @author Jason T. Greene */ public class TXServlet extends HttpServlet { @Override public void init(final ServletConfig config) throws ServletException { super.init(config); } @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { FileChannel file = null; try { file = FileChannel.open(Paths.get(TXServlet.class.getResource(TXServlet.class.getSimpleName() + ".class").toURI())); } catch (URISyntaxException e) { } BufferWritableOutputStream stream = (BufferWritableOutputStream) resp.getOutputStream(); stream.transferFrom(file); file.close(); } @Override protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/util/TestClassIntrospector.java000066400000000000000000000024401420065311100337270ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.util; import io.undertow.servlet.api.ClassIntrospecter; import io.undertow.servlet.api.InstanceFactory; import io.undertow.servlet.util.ConstructorInstanceFactory; /** * @author Stuart Douglas */ public class TestClassIntrospector implements ClassIntrospecter { public static final TestClassIntrospector INSTANCE = new TestClassIntrospector(); @Override public InstanceFactory createInstanceFactory(final Class clazz) throws NoSuchMethodException { return new ConstructorInstanceFactory<>(clazz.getDeclaredConstructor()); } } TestConfidentialPortManager.java000066400000000000000000000026431420065311100347330ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/util/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.util; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.api.ConfidentialPortManager; import io.undertow.testutils.DefaultServer; /** * Implementation of {@see ConfidentialPortManager} for use within the test suite. * * @author Darran Lofthouse */ public class TestConfidentialPortManager implements ConfidentialPortManager { public static final TestConfidentialPortManager INSTANCE = new TestConfidentialPortManager(); private TestConfidentialPortManager() { } @Override public int getConfidentialPort(HttpServerExchange exchange) { return DefaultServer.getHostSSLPort("default"); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/util/TestListener.java000066400000000000000000000040771420065311100320430ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.util; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; /** * @author Stuart Douglas */ public class TestListener implements ServletRequestListener { private static final List RESULTS = Collections.synchronizedList(new ArrayList()); private static volatile CountDownLatch latch; public static void addMessage(String message) { RESULTS.add(message); latch.countDown(); } public static void init(int count) { RESULTS.clear(); latch = new CountDownLatch(count); } @Override public void requestDestroyed(final ServletRequestEvent sre) { RESULTS.add("destroyed " + sre.getServletRequest().getDispatcherType()); latch.countDown(); } @Override public void requestInitialized(final ServletRequestEvent sre) { RESULTS.add("created " + sre.getServletRequest().getDispatcherType()); latch.countDown(); } public static List results() { try { latch.await(10, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); } return RESULTS; } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/util/TestResourceLoader.java000066400000000000000000000104121420065311100331620ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.util; import io.undertow.io.IoCallback; import io.undertow.io.Sender; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.resource.ClassPathResourceManager; import io.undertow.server.handlers.resource.RangeAwareResource; import io.undertow.server.handlers.resource.Resource; import io.undertow.util.ETag; import io.undertow.util.MimeMappings; import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.file.Path; import java.util.Date; import java.util.List; /** * @author Stuart Douglas */ public class TestResourceLoader extends ClassPathResourceManager { public TestResourceLoader(final Class testClass) { super(testClass.getClassLoader(), testClass.getPackage().getName().replace(".", "/")); } @Override public Resource getResource(String path) throws IOException { final Resource delegate = super.getResource(path); if(delegate == null) { return delegate; } return new TestResource(delegate); } private static class TestResource implements RangeAwareResource { private final Resource delegate; TestResource(Resource delegate) { this.delegate = delegate; } @Override public String getPath() { return delegate.getPath(); } @Override public Date getLastModified() { return delegate.getLastModified(); } @Override public String getLastModifiedString() { return delegate.getLastModifiedString(); } @Override public ETag getETag() { return delegate.getETag(); } @Override public String getName() { return delegate.getName(); } @Override public boolean isDirectory() { return delegate.isDirectory(); } @Override public List list() { return delegate.list(); } @Override public String getContentType(MimeMappings mimeMappings) { return delegate.getContentType(mimeMappings); } @Override public void serve(Sender sender, HttpServerExchange exchange, IoCallback completionCallback) { delegate.serve(sender, exchange, completionCallback); } @Override public Long getContentLength() { return delegate.getContentLength(); } @Override public String getCacheKey() { return delegate.getCacheKey(); } @Override public File getFile() { return delegate.getFile(); } @Override public Path getFilePath() { return delegate.getFilePath(); } @Override public File getResourceManagerRoot() { return delegate.getResourceManagerRoot(); } @Override public Path getResourceManagerRootPath() { return delegate.getResourceManagerRootPath(); } @Override public URL getUrl() { return delegate.getUrl(); } @Override public void serveRange(Sender sender, HttpServerExchange exchange, long start, long end, IoCallback completionCallback) { ((RangeAwareResource)delegate).serveRange(sender, exchange, start, end, completionCallback); } @Override public boolean isRangeSupported() { return delegate instanceof RangeAwareResource && ((RangeAwareResource) delegate).isRangeSupported(); } } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/util/Tracker.java000066400000000000000000000024001420065311100307750ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.util; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Utility class for tracking invocation order. * * @author Jozef Hartinger * */ public class Tracker { private static final List actions = Collections.synchronizedList(new ArrayList()); public static void addAction(String action) { actions.add(action); } public static List getActions() { return actions; } public static void reset() { actions.clear(); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/websocket/000077500000000000000000000000001420065311100275545ustar00rootroot00000000000000WebSocketServletTest.java000066400000000000000000000101261420065311100344330ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/websocket/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.websocket; import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.DeploymentUtils; import io.undertow.servlet.util.ImmediateInstanceFactory; import io.undertow.servlet.websockets.WebSocketServlet; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.util.NetworkUtils; import io.undertow.websockets.WebSocketConnectionCallback; import io.undertow.websockets.core.AbstractReceiveListener; import io.undertow.websockets.core.BufferedTextMessage; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSockets; import io.undertow.websockets.spi.WebSocketHttpExchange; import io.undertow.websockets.utils.FrameChecker; import io.undertow.websockets.utils.WebSocketTestClient; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.FutureResult; import java.io.IOException; import java.net.URI; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.concurrent.atomic.AtomicBoolean; import javax.servlet.Servlet; /** * @author Stuart Douglas */ @HttpOneOnly @RunWith(DefaultServer.class) public class WebSocketServletTest { public static final Charset US_ASCII = StandardCharsets.US_ASCII; @Test public void testText() throws Exception { final AtomicBoolean connected = new AtomicBoolean(false); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentUtils.setupServlet(new ServletInfo("websocket", WebSocketServlet.class, new ImmediateInstanceFactory(new WebSocketServlet(new WebSocketConnectionCallback() { @Override public void onConnect(final WebSocketHttpExchange exchange, final WebSocketChannel channel) { connected.set(true); channel.getReceiveSetter().set(new AbstractReceiveListener() { @Override protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage message) throws IOException { final String string = message.getData(); if(string.equals("hello")) { WebSockets.sendText("world", channel, null); } else { WebSockets.sendText(string, channel, null); } } }); channel.resumeReceives(); } }))) .addMapping("/*")); final FutureResult latch = new FutureResult(); WebSocketTestClient client = new WebSocketTestClient(io.netty.handler.codec.http.websocketx.WebSocketVersion.V13, new URI("ws://" + NetworkUtils.formatPossibleIpv6Address(DefaultServer.getHostAddress("default")) + ":" + DefaultServer.getHostPort("default") + "/servletContext/")); client.connect(); client.send(new TextWebSocketFrame(Unpooled.copiedBuffer("hello", US_ASCII)), new FrameChecker(TextWebSocketFrame.class, "world".getBytes(US_ASCII), latch)); latch.getIoFuture().get(); client.destroy(); } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/wrapper/000077500000000000000000000000001420065311100272465ustar00rootroot00000000000000AbstractResponseWrapperTestCase.java000066400000000000000000000115311420065311100363120ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/wrapper/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.wrapper; import java.io.IOException; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.LoggingExceptionHandler; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.spec.HttpServletRequestImpl; import io.undertow.servlet.spec.HttpServletResponseImpl; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.jboss.logging.Logger; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; /** * Tests wrapped requests and responses * * TODO: these tests should be expanded to add more functionality to the wrappers, and also test request dispatches * * * @author Stuart Douglas */ @RunWith(DefaultServer.class) public abstract class AbstractResponseWrapperTestCase { @Before public void setup() throws ServletException { DeploymentInfo builder = new DeploymentInfo(); builder.setExceptionHandler(LoggingExceptionHandler.builder().add(IllegalArgumentException.class, "io.undertow", Logger.Level.DEBUG).build()); final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); builder.addServlet(new ServletInfo("wrapperServlet", WrapperServlet.class) .addMapping("/*")); builder.addFilter(new FilterInfo("standard", StandardRequestWrappingFilter.class)); builder.addFilterUrlMapping("standard", "/standard", DispatcherType.REQUEST); builder.addFilter(new FilterInfo("nonstandard", NonStandardRequestWrappingFilter.class)); builder.addFilterUrlMapping("nonstandard", "/nonstandard", DispatcherType.REQUEST); builder.setClassIntrospecter(TestClassIntrospector.INSTANCE) .setClassLoader(AbstractResponseWrapperTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setDeploymentName("servletContext.war") .setAllowNonStandardWrappers(isNonStandardAllowed()); final DeploymentManager manager = container.addDeployment(builder); manager.deploy(); root.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(root); } abstract boolean isNonStandardAllowed(); @Test public void testNoWrapper() throws IOException, ServletException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(HttpServletRequestImpl.class.getName() + "\n" + HttpServletResponseImpl.class.getName(), response); } finally { client.getConnectionManager().shutdown(); } } @Test public void testStandardWrapper() throws IOException, ServletException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/standard"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(StandardRequestWrapper.class.getName() + "\n" + StandardResponseWrapper.class.getName(), response); } finally { client.getConnectionManager().shutdown(); } } } NonStandardRequestWrapper.java000066400000000000000000000605621420065311100351700ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/wrapper/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.wrapper; import java.io.BufferedReader; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Collection; import java.util.Enumeration; import java.util.Locale; import java.util.Map; import javax.servlet.AsyncContext; import javax.servlet.DispatcherType; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletRequestWrapper; import javax.servlet.ServletResponse; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpUpgradeHandler; import javax.servlet.http.Part; /** * @author Stuart Douglas */ public class NonStandardRequestWrapper implements HttpServletRequest { private ServletRequest request; /** * Creates a ServletRequest adaptor wrapping the given request object. * @throws java.lang.IllegalArgumentException if the request is null */ public NonStandardRequestWrapper(ServletRequest request) { if (request == null) { throw new IllegalArgumentException("Request cannot be null"); } this.request = request; } /** * Return the wrapped request object. */ public ServletRequest getRequest() { return this.request; } /** * Sets the request object being wrapped. * @throws java.lang.IllegalArgumentException if the request is null. */ public void setRequest(ServletRequest request) { if (request == null) { throw new IllegalArgumentException("Request cannot be null"); } this.request = request; } /** * The default behavior of this method is to call getAttribute(String name) * on the wrapped request object. */ public Object getAttribute(String name) { return this.request.getAttribute(name); } /** * The default behavior of this method is to return getAttributeNames() * on the wrapped request object. */ public Enumeration getAttributeNames() { return this.request.getAttributeNames(); } /** * The default behavior of this method is to return getCharacterEncoding() * on the wrapped request object. */ public String getCharacterEncoding() { return this.request.getCharacterEncoding(); } /** * The default behavior of this method is to set the character encoding * on the wrapped request object. */ public void setCharacterEncoding(String enc) throws UnsupportedEncodingException { this.request.setCharacterEncoding(enc); } /** * The default behavior of this method is to return getContentLength() * on the wrapped request object. */ public int getContentLength() { return this.request.getContentLength(); } /** * The default behavior of this method is to return getContentLengthLong() * on the wrapped request object. * * @since Servlet 3.1 */ public long getContentLengthLong() { return this.request.getContentLengthLong(); } /** * The default behavior of this method is to return getContentType() * on the wrapped request object. */ public String getContentType() { return this.request.getContentType(); } /** * The default behavior of this method is to return getInputStream() * on the wrapped request object. */ public ServletInputStream getInputStream() throws IOException { return this.request.getInputStream(); } /** * The default behavior of this method is to return * getParameter(String name) on the wrapped request object. */ public String getParameter(String name) { return this.request.getParameter(name); } /** * The default behavior of this method is to return getParameterMap() * on the wrapped request object. */ public Map getParameterMap() { return this.request.getParameterMap(); } /** * The default behavior of this method is to return getParameterNames() * on the wrapped request object. */ public Enumeration getParameterNames() { return this.request.getParameterNames(); } /** * The default behavior of this method is to return * getParameterValues(String name) on the wrapped request object. */ public String[] getParameterValues(String name) { return this.request.getParameterValues(name); } /** * The default behavior of this method is to return getProtocol() * on the wrapped request object. */ public String getProtocol() { return this.request.getProtocol(); } /** * The default behavior of this method is to return getScheme() * on the wrapped request object. */ public String getScheme() { return this.request.getScheme(); } /** * The default behavior of this method is to return getServerName() * on the wrapped request object. */ public String getServerName() { return this.request.getServerName(); } /** * The default behavior of this method is to return getServerPort() * on the wrapped request object. */ public int getServerPort() { return this.request.getServerPort(); } /** * The default behavior of this method is to return getReader() * on the wrapped request object. */ public BufferedReader getReader() throws IOException { return this.request.getReader(); } /** * The default behavior of this method is to return getRemoteAddr() * on the wrapped request object. */ public String getRemoteAddr() { return this.request.getRemoteAddr(); } /** * The default behavior of this method is to return getRemoteHost() * on the wrapped request object. */ public String getRemoteHost() { return this.request.getRemoteHost(); } /** * The default behavior of this method is to return * setAttribute(String name, Object o) on the wrapped request object. */ public void setAttribute(String name, Object o) { this.request.setAttribute(name, o); } /** * The default behavior of this method is to call * removeAttribute(String name) on the wrapped request object. */ public void removeAttribute(String name) { this.request.removeAttribute(name); } /** * The default behavior of this method is to return getLocale() * on the wrapped request object. */ public Locale getLocale() { return this.request.getLocale(); } /** * The default behavior of this method is to return getLocales() * on the wrapped request object. */ public Enumeration getLocales() { return this.request.getLocales(); } /** * The default behavior of this method is to return isSecure() * on the wrapped request object. */ public boolean isSecure() { return this.request.isSecure(); } /** * The default behavior of this method is to return * getRequestDispatcher(String path) on the wrapped request object. */ public RequestDispatcher getRequestDispatcher(String path) { return this.request.getRequestDispatcher(path); } /** * The default behavior of this method is to return * getRealPath(String path) on the wrapped request object. * * @deprecated As of Version 2.1 of the Java Servlet API, * use {@link ServletContext#getRealPath} instead */ public String getRealPath(String path) { return this.request.getRealPath(path); } /** * The default behavior of this method is to return * getRemotePort() on the wrapped request object. * * @since Servlet 2.4 */ public int getRemotePort(){ return this.request.getRemotePort(); } /** * The default behavior of this method is to return * getLocalName() on the wrapped request object. * * @since Servlet 2.4 */ public String getLocalName(){ return this.request.getLocalName(); } /** * The default behavior of this method is to return * getLocalAddr() on the wrapped request object. * * @since Servlet 2.4 */ public String getLocalAddr(){ return this.request.getLocalAddr(); } /** * The default behavior of this method is to return * getLocalPort() on the wrapped request object. * * @since Servlet 2.4 */ public int getLocalPort(){ return this.request.getLocalPort(); } /** * Gets the servlet context to which the wrapped servlet request was last * dispatched. * * @return the servlet context to which the wrapped servlet request was * last dispatched * * @since Servlet 3.0 */ public ServletContext getServletContext() { return request.getServletContext(); } /** * The default behavior of this method is to invoke * {@link ServletRequest#startAsync} on the wrapped request object. * * @return the (re)initialized AsyncContext * * @throws IllegalStateException if the request is within the scope of * a filter or servlet that does not support asynchronous operations * (that is, {@link #isAsyncSupported} returns false), * or if this method is called again without any asynchronous dispatch * (resulting from one of the {@link AsyncContext#dispatch} methods), * is called outside the scope of any such dispatch, or is called again * within the scope of the same dispatch, or if the response has * already been closed * * @see ServletRequest#startAsync * * @since Servlet 3.0 */ public AsyncContext startAsync() throws IllegalStateException { return request.startAsync(); } /** * The default behavior of this method is to invoke * {@link ServletRequest#startAsync(ServletRequest, ServletResponse)} * on the wrapped request object. * * @param servletRequest the ServletRequest used to initialize the * AsyncContext * @param servletResponse the ServletResponse used to initialize the * AsyncContext * * @return the (re)initialized AsyncContext * * @throws IllegalStateException if the request is within the scope of * a filter or servlet that does not support asynchronous operations * (that is, {@link #isAsyncSupported} returns false), * or if this method is called again without any asynchronous dispatch * (resulting from one of the {@link AsyncContext#dispatch} methods), * is called outside the scope of any such dispatch, or is called again * within the scope of the same dispatch, or if the response has * already been closed * * @see ServletRequest#startAsync(ServletRequest, ServletResponse) * * @since Servlet 3.0 */ public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { return request.startAsync(servletRequest, servletResponse); } /** * Checks if the wrapped request has been put into asynchronous mode. * * @return true if this request has been put into asynchronous mode, * false otherwise * * @see ServletRequest#isAsyncStarted * * @since Servlet 3.0 */ public boolean isAsyncStarted() { return request.isAsyncStarted(); } /** * Checks if the wrapped request supports asynchronous operation. * * @return true if this request supports asynchronous operation, false * otherwise * * @see ServletRequest#isAsyncSupported * * @since Servlet 3.0 */ public boolean isAsyncSupported() { return request.isAsyncSupported(); } /** * Gets the AsyncContext that was created or reinitialized by the * most recent invocation of {@link #startAsync} or * {@link #startAsync(ServletRequest,ServletResponse)} on the wrapped * request. * * @return the AsyncContext that was created or reinitialized by the * most recent invocation of {@link #startAsync} or * {@link #startAsync(ServletRequest,ServletResponse)} on * the wrapped request * * @throws IllegalStateException if this request has not been put * into asynchronous mode, i.e., if neither {@link #startAsync} nor * {@link #startAsync(ServletRequest,ServletResponse)} has been called * * @see ServletRequest#getAsyncContext * * @since Servlet 3.0 */ public AsyncContext getAsyncContext() { return request.getAsyncContext(); } /** * Checks (recursively) if this ServletRequestWrapper wraps the given * {@link ServletRequest} instance. * * @param wrapped the ServletRequest instance to search for * * @return true if this ServletRequestWrapper wraps the * given ServletRequest instance, false otherwise * * @since Servlet 3.0 */ public boolean isWrapperFor(ServletRequest wrapped) { if (request == wrapped) { return true; } else if (request instanceof ServletRequestWrapper) { return ((ServletRequestWrapper) request).isWrapperFor(wrapped); } else { return false; } } /** * Checks (recursively) if this ServletRequestWrapper wraps a * {@link ServletRequest} of the given class type. * * @param wrappedType the ServletRequest class type to * search for * * @return true if this ServletRequestWrapper wraps a * ServletRequest of the given class type, false otherwise * * @throws IllegalArgumentException if the given class does not * implement {@link ServletRequest} * * @since Servlet 3.0 */ public boolean isWrapperFor(Class wrappedType) { if (!ServletRequest.class.isAssignableFrom(wrappedType)) { throw new IllegalArgumentException("Given class " + wrappedType.getName() + " not a subinterface of " + ServletRequest.class.getName()); } if (wrappedType.isAssignableFrom(request.getClass())) { return true; } else if (request instanceof ServletRequestWrapper) { return ((ServletRequestWrapper) request).isWrapperFor(wrappedType); } else { return false; } } /** * Gets the dispatcher type of the wrapped request. * * @return the dispatcher type of the wrapped request * * @see ServletRequest#getDispatcherType * * @since Servlet 3.0 */ public DispatcherType getDispatcherType() { return request.getDispatcherType(); } private HttpServletRequest _getHttpServletRequest() { return (HttpServletRequest) getRequest(); } /** * The default behavior of this method is to return getAuthType() * on the wrapped request object. */ @Override public String getAuthType() { return this._getHttpServletRequest().getAuthType(); } /** * The default behavior of this method is to return getCookies() * on the wrapped request object. */ @Override public Cookie[] getCookies() { return this._getHttpServletRequest().getCookies(); } /** * The default behavior of this method is to return getDateHeader(String name) * on the wrapped request object. */ @Override public long getDateHeader(String name) { return this._getHttpServletRequest().getDateHeader(name); } /** * The default behavior of this method is to return getHeader(String name) * on the wrapped request object. */ @Override public String getHeader(String name) { return this._getHttpServletRequest().getHeader(name); } /** * The default behavior of this method is to return getHeaders(String name) * on the wrapped request object. */ @Override public Enumeration getHeaders(String name) { return this._getHttpServletRequest().getHeaders(name); } /** * The default behavior of this method is to return getHeaderNames() * on the wrapped request object. */ @Override public Enumeration getHeaderNames() { return this._getHttpServletRequest().getHeaderNames(); } /** * The default behavior of this method is to return * getIntHeader(String name) on the wrapped request object. */ @Override public int getIntHeader(String name) { return this._getHttpServletRequest().getIntHeader(name); } /** * The default behavior of this method is to return getMethod() * on the wrapped request object. */ @Override public String getMethod() { return this._getHttpServletRequest().getMethod(); } /** * The default behavior of this method is to return getPathInfo() * on the wrapped request object. */ @Override public String getPathInfo() { return this._getHttpServletRequest().getPathInfo(); } /** * The default behavior of this method is to return getPathTranslated() * on the wrapped request object. */ @Override public String getPathTranslated() { return this._getHttpServletRequest().getPathTranslated(); } /** * The default behavior of this method is to return getContextPath() * on the wrapped request object. */ @Override public String getContextPath() { return this._getHttpServletRequest().getContextPath(); } /** * The default behavior of this method is to return getQueryString() * on the wrapped request object. */ @Override public String getQueryString() { return this._getHttpServletRequest().getQueryString(); } /** * The default behavior of this method is to return getRemoteUser() * on the wrapped request object. */ @Override public String getRemoteUser() { return this._getHttpServletRequest().getRemoteUser(); } /** * The default behavior of this method is to return isUserInRole(String role) * on the wrapped request object. */ @Override public boolean isUserInRole(String role) { return this._getHttpServletRequest().isUserInRole(role); } /** * The default behavior of this method is to return getUserPrincipal() * on the wrapped request object. */ @Override public java.security.Principal getUserPrincipal() { return this._getHttpServletRequest().getUserPrincipal(); } /** * The default behavior of this method is to return getRequestedSessionId() * on the wrapped request object. */ @Override public String getRequestedSessionId() { return this._getHttpServletRequest().getRequestedSessionId(); } /** * The default behavior of this method is to return getRequestURI() * on the wrapped request object. */ @Override public String getRequestURI() { return this._getHttpServletRequest().getRequestURI(); } /** * The default behavior of this method is to return getRequestURL() * on the wrapped request object. */ @Override public StringBuffer getRequestURL() { return this._getHttpServletRequest().getRequestURL(); } /** * The default behavior of this method is to return getServletPath() * on the wrapped request object. */ @Override public String getServletPath() { return this._getHttpServletRequest().getServletPath(); } /** * The default behavior of this method is to return getSession(boolean create) * on the wrapped request object. */ @Override public HttpSession getSession(boolean create) { return this._getHttpServletRequest().getSession(create); } /** * The default behavior of this method is to return getSession() * on the wrapped request object. */ @Override public HttpSession getSession() { return this._getHttpServletRequest().getSession(); } /** * The default behavior of this method is to return changeSessionId() * on the wrapped request object. */ @Override public String changeSessionId() { return this._getHttpServletRequest().changeSessionId(); } /** * The default behavior of this method is to return isRequestedSessionIdValid() * on the wrapped request object. */ @Override public boolean isRequestedSessionIdValid() { return this._getHttpServletRequest().isRequestedSessionIdValid(); } /** * The default behavior of this method is to return isRequestedSessionIdFromCookie() * on the wrapped request object. */ @Override public boolean isRequestedSessionIdFromCookie() { return this._getHttpServletRequest().isRequestedSessionIdFromCookie(); } /** * The default behavior of this method is to return isRequestedSessionIdFromURL() * on the wrapped request object. */ @Override public boolean isRequestedSessionIdFromURL() { return this._getHttpServletRequest().isRequestedSessionIdFromURL(); } /** * The default behavior of this method is to return isRequestedSessionIdFromUrl() * on the wrapped request object. */ @Override public boolean isRequestedSessionIdFromUrl() { return this._getHttpServletRequest().isRequestedSessionIdFromUrl(); } /** * The default behavior of this method is to call authenticate on the * wrapped request object. * * @since Servlet 3.0 */ @Override public boolean authenticate(HttpServletResponse response) throws IOException, ServletException { return this._getHttpServletRequest().authenticate(response); } /** * The default behavior of this method is to call login on the wrapped * request object. * * @since Servlet 3.0 */ @Override public void login(String username, String password) throws ServletException { this._getHttpServletRequest().login(username,password); } /** * The default behavior of this method is to call login on the wrapped * request object. * * @since Servlet 3.0 */ @Override public void logout() throws ServletException { this._getHttpServletRequest().logout(); } /** * The default behavior of this method is to call getParts on the wrapped * request object. * *

    Any changes to the returned Collection must not * affect this HttpServletRequestWrapper. * * @since Servlet 3.0 */ @Override public Collection getParts() throws IOException, ServletException { return this._getHttpServletRequest().getParts(); } /** * The default behavior of this method is to call getPart on the wrapped * request object. * * @since Servlet 3.0 */ @Override public Part getPart(String name) throws IOException, ServletException { return this._getHttpServletRequest().getPart(name); } /** * Create an instance of HttpUpgradeHandler for an given * class and uses it for the http protocol upgrade processing. * * @since Servlet 3.1 */ @Override public T upgrade(Class handlerClass) throws IOException, ServletException { return this._getHttpServletRequest().upgrade(handlerClass); } } NonStandardRequestWrappingFilter.java000066400000000000000000000027711420065311100365030ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/wrapper/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.wrapper; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; /** * @author Stuart Douglas */ public class NonStandardRequestWrappingFilter implements Filter { @Override public void init(final FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { chain.doFilter(new NonStandardRequestWrapper(request), new NonStandardResponseWrapper(response)); } @Override public void destroy() { } } NonStandardResponseWrapper.java000066400000000000000000000353511420065311100353340ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/wrapper/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.wrapper; import java.io.IOException; import java.io.PrintWriter; import java.util.Collection; import java.util.Locale; import javax.servlet.ServletOutputStream; import javax.servlet.ServletResponse; import javax.servlet.ServletResponseWrapper; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class NonStandardResponseWrapper implements HttpServletResponse { private ServletResponse response; /** * Creates a ServletResponse adaptor wrapping the given response object. * * @throws java.lang.IllegalArgumentException * if the response is null. */ public NonStandardResponseWrapper(ServletResponse response) { if (response == null) { throw new IllegalArgumentException("Response cannot be null"); } this.response = response; } /** * Return the wrapped ServletResponse object. */ public ServletResponse getResponse() { return this.response; } /** * Sets the response being wrapped. * * @throws java.lang.IllegalArgumentException * if the response is null. */ public void setResponse(ServletResponse response) { if (response == null) { throw new IllegalArgumentException("Response cannot be null"); } this.response = response; } /** * The default behavior of this method is to call setCharacterEncoding(String charset) * on the wrapped response object. * * @since Servlet 2.4 */ public void setCharacterEncoding(String charset) { this.response.setCharacterEncoding(charset); } /** * The default behavior of this method is to return getCharacterEncoding() * on the wrapped response object. */ public String getCharacterEncoding() { return this.response.getCharacterEncoding(); } /** * The default behavior of this method is to return getOutputStream() * on the wrapped response object. */ public ServletOutputStream getOutputStream() throws IOException { return this.response.getOutputStream(); } /** * The default behavior of this method is to return getWriter() * on the wrapped response object. */ public PrintWriter getWriter() throws IOException { return this.response.getWriter(); } /** * The default behavior of this method is to call setContentLength(int len) * on the wrapped response object. */ public void setContentLength(int len) { this.response.setContentLength(len); } /** * The default behavior of this method is to call setContentLengthLong(long len) * on the wrapped response object. */ public void setContentLengthLong(long len) { this.response.setContentLengthLong(len); } /** * The default behavior of this method is to call setContentType(String type) * on the wrapped response object. */ public void setContentType(String type) { this.response.setContentType(type); } /** * The default behavior of this method is to return getContentType() * on the wrapped response object. * * @since Servlet 2.4 */ public String getContentType() { return this.response.getContentType(); } /** * The default behavior of this method is to call setBufferSize(int size) * on the wrapped response object. */ public void setBufferSize(int size) { this.response.setBufferSize(size); } /** * The default behavior of this method is to return getBufferSize() * on the wrapped response object. */ public int getBufferSize() { return this.response.getBufferSize(); } /** * The default behavior of this method is to call flushBuffer() * on the wrapped response object. */ public void flushBuffer() throws IOException { this.response.flushBuffer(); } /** * The default behavior of this method is to return isCommitted() * on the wrapped response object. */ public boolean isCommitted() { return this.response.isCommitted(); } /** * The default behavior of this method is to call reset() * on the wrapped response object. */ public void reset() { this.response.reset(); } /** * The default behavior of this method is to call resetBuffer() * on the wrapped response object. */ public void resetBuffer() { this.response.resetBuffer(); } /** * The default behavior of this method is to call setLocale(Locale loc) * on the wrapped response object. */ public void setLocale(Locale loc) { this.response.setLocale(loc); } /** * The default behavior of this method is to return getLocale() * on the wrapped response object. */ public Locale getLocale() { return this.response.getLocale(); } /** * Checks (recursively) if this ServletResponseWrapper wraps the given * {@link ServletResponse} instance. * * @param wrapped the ServletResponse instance to search for * @return true if this ServletResponseWrapper wraps the * given ServletResponse instance, false otherwise * @since Servlet 3.0 */ public boolean isWrapperFor(ServletResponse wrapped) { if (response == wrapped) { return true; } else if (response instanceof ServletResponseWrapper) { return ((ServletResponseWrapper) response).isWrapperFor(wrapped); } else { return false; } } /** * Checks (recursively) if this ServletResponseWrapper wraps a * {@link ServletResponse} of the given class type. * * @param wrappedType the ServletResponse class type to * search for * @return true if this ServletResponseWrapper wraps a * ServletResponse of the given class type, false otherwise * @throws IllegalArgumentException if the given class does not * implement {@link ServletResponse} * @since Servlet 3.0 */ public boolean isWrapperFor(Class wrappedType) { if (!ServletResponse.class.isAssignableFrom(wrappedType)) { throw new IllegalArgumentException("Given class " + wrappedType.getName() + " not a subinterface of " + ServletResponse.class.getName()); } if (wrappedType.isAssignableFrom(response.getClass())) { return true; } else if (response instanceof ServletResponseWrapper) { return ((ServletResponseWrapper) response).isWrapperFor(wrappedType); } else { return false; } } private HttpServletResponse _getHttpServletResponse() { return (HttpServletResponse) response; } /** * The default behavior of this method is to call addCookie(Cookie cookie) * on the wrapped response object. */ @Override public void addCookie(Cookie cookie) { this._getHttpServletResponse().addCookie(cookie); } /** * The default behavior of this method is to call containsHeader(String name) * on the wrapped response object. */ @Override public boolean containsHeader(String name) { return this._getHttpServletResponse().containsHeader(name); } /** * The default behavior of this method is to call encodeURL(String url) * on the wrapped response object. */ @Override public String encodeURL(String url) { return this._getHttpServletResponse().encodeURL(url); } /** * The default behavior of this method is to return encodeRedirectURL(String url) * on the wrapped response object. */ @Override public String encodeRedirectURL(String url) { return this._getHttpServletResponse().encodeRedirectURL(url); } /** * The default behavior of this method is to call encodeUrl(String url) * on the wrapped response object. * * @deprecated As of version 2.1, use {@link #encodeURL(String url)} * instead */ @Override public String encodeUrl(String url) { return this._getHttpServletResponse().encodeUrl(url); } /** * The default behavior of this method is to return * encodeRedirectUrl(String url) on the wrapped response object. * * @deprecated As of version 2.1, use * {@link #encodeRedirectURL(String url)} instead */ @Override public String encodeRedirectUrl(String url) { return this._getHttpServletResponse().encodeRedirectUrl(url); } /** * The default behavior of this method is to call sendError(int sc, String msg) * on the wrapped response object. */ @Override public void sendError(int sc, String msg) throws IOException { this._getHttpServletResponse().sendError(sc, msg); } /** * The default behavior of this method is to call sendError(int sc) * on the wrapped response object. */ @Override public void sendError(int sc) throws IOException { this._getHttpServletResponse().sendError(sc); } /** * The default behavior of this method is to return sendRedirect(String location) * on the wrapped response object. */ @Override public void sendRedirect(String location) throws IOException { this._getHttpServletResponse().sendRedirect(location); } /** * The default behavior of this method is to call setDateHeader(String name, long date) * on the wrapped response object. */ @Override public void setDateHeader(String name, long date) { this._getHttpServletResponse().setDateHeader(name, date); } /** * The default behavior of this method is to call addDateHeader(String name, long date) * on the wrapped response object. */ @Override public void addDateHeader(String name, long date) { this._getHttpServletResponse().addDateHeader(name, date); } /** * The default behavior of this method is to return setHeader(String name, String value) * on the wrapped response object. */ @Override public void setHeader(String name, String value) { this._getHttpServletResponse().setHeader(name, value); } /** * The default behavior of this method is to return addHeader(String name, String value) * on the wrapped response object. */ @Override public void addHeader(String name, String value) { this._getHttpServletResponse().addHeader(name, value); } /** * The default behavior of this method is to call setIntHeader(String name, int value) * on the wrapped response object. */ @Override public void setIntHeader(String name, int value) { this._getHttpServletResponse().setIntHeader(name, value); } /** * The default behavior of this method is to call addIntHeader(String name, int value) * on the wrapped response object. */ @Override public void addIntHeader(String name, int value) { this._getHttpServletResponse().addIntHeader(name, value); } /** * The default behavior of this method is to call setStatus(int sc) * on the wrapped response object. */ @Override public void setStatus(int sc) { this._getHttpServletResponse().setStatus(sc); } /** * The default behavior of this method is to call * setStatus(int sc, String sm) on the wrapped response object. * * @deprecated As of version 2.1, due to ambiguous meaning of the * message parameter. To set a status code * use {@link #setStatus(int)}, to send an error with a description * use {@link #sendError(int, String)} */ @Override public void setStatus(int sc, String sm) { this._getHttpServletResponse().setStatus(sc, sm); } /** * The default behaviour of this method is to call * {@link HttpServletResponse#getStatus} on the wrapped response * object. * * @return the current status code of the wrapped response */ @Override public int getStatus() { return _getHttpServletResponse().getStatus(); } /** * The default behaviour of this method is to call * {@link HttpServletResponse#getHeader} on the wrapped response * object. * * @param name the name of the response header whose value to return * @return the value of the response header with the given name, * or null if no header with the given name has been set * on the wrapped response * @since Servlet 3.0 */ @Override public String getHeader(String name) { return _getHttpServletResponse().getHeader(name); } /** * The default behaviour of this method is to call * {@link HttpServletResponse#getHeaders} on the wrapped response * object. *

    *

    Any changes to the returned Collection must not * affect this HttpServletResponseWrapper. * * @param name the name of the response header whose values to return * @return a (possibly empty) Collection of the values * of the response header with the given name * @since Servlet 3.0 */ @Override public Collection getHeaders(String name) { return _getHttpServletResponse().getHeaders(name); } /** * The default behaviour of this method is to call * {@link HttpServletResponse#getHeaderNames} on the wrapped response * object. *

    *

    Any changes to the returned Collection must not * affect this HttpServletResponseWrapper. * * @return a (possibly empty) Collection of the names * of the response headers * @since Servlet 3.0 */ @Override public Collection getHeaderNames() { return _getHttpServletResponse().getHeaderNames(); } } NonStandardResponseWrapperTestCase.java000066400000000000000000000041001420065311100367540ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/wrapper/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.wrapper; import java.io.IOException; import javax.servlet.ServletException; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class NonStandardResponseWrapperTestCase extends AbstractResponseWrapperTestCase { @Test public void testNonStandardWrapper() throws IOException, ServletException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/nonstandard"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); Assert.assertEquals(NonStandardRequestWrapper.class.getName() + "\n" + NonStandardResponseWrapper.class.getName(), response); } finally { client.getConnectionManager().shutdown(); } } @Override boolean isNonStandardAllowed() { return true; } } StandardRequestWrapper.java000066400000000000000000000023171420065311100345070ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/wrapper/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.wrapper; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; /** * @author Stuart Douglas */ public class StandardRequestWrapper extends HttpServletRequestWrapper { /** * Constructs a request object wrapping the given request. * * @throws IllegalArgumentException * if the request is null */ public StandardRequestWrapper(final HttpServletRequest request) { super(request); } } StandardRequestWrappingFilter.java000066400000000000000000000031701420065311100360220ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/wrapper/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.wrapper; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class StandardRequestWrappingFilter implements Filter { @Override public void init(final FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { chain.doFilter(new StandardRequestWrapper((HttpServletRequest) request), new StandardResponseWrapper((HttpServletResponse) response)); } @Override public void destroy() { } } StandardResponseWrapper.java000066400000000000000000000023131420065311100346510ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/wrapper/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.wrapper; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; /** * @author Stuart Douglas */ public class StandardResponseWrapper extends HttpServletResponseWrapper { /** * Constructs a response adaptor wrapping the given response. * * @throws IllegalArgumentException if the response is null */ public StandardResponseWrapper(final HttpServletResponse response) { super(response); } } StandardResponseWrapperTestCase.java000066400000000000000000000036561420065311100363200ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/wrapper/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.wrapper; import java.io.IOException; import javax.servlet.ServletException; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.StatusCodes; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) public class StandardResponseWrapperTestCase extends AbstractResponseWrapperTestCase { @Test public void testNonStandardWrapper() throws IOException, ServletException { TestHttpClient client = new TestHttpClient(); try { HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/nonstandard"); HttpResponse result = client.execute(get); Assert.assertEquals(StatusCodes.INTERNAL_SERVER_ERROR, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); } finally { client.getConnectionManager().shutdown(); } } @Override boolean isNonStandardAllowed() { return false; } } undertow-2.2.16.Final/servlet/src/test/java/io/undertow/servlet/test/wrapper/WrapperServlet.java000066400000000000000000000025001420065311100330730ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.wrapper; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Stuart Douglas */ public class WrapperServlet extends HttpServlet{ @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write(req.getClass().getName()); resp.getWriter().write("\n"); resp.getWriter().write(resp.getClass().getName()); } } undertow-2.2.16.Final/servlet/src/test/resources/000077500000000000000000000000001420065311100217365ustar00rootroot00000000000000undertow-2.2.16.Final/servlet/src/test/resources/dummy.keystore000066400000000000000000000042271420065311100246650ustar00rootroot00000000000000dummytP00 +*u,7ۦt)߼@JEq]<;a:*RLK)̃Vڐg\͟VJ;'2p]]f8CHFhǑb5tbb>tBP\ Lcfa[zfWON@qHn>W-P/utX%b:ÊL#v@Pyن YN1LݚJpT uL?#Q!=YUQc,Q-ը;(ԢQ.W?qU]o;FIrN^w}+c5+LtΌe*<%U=>UhM(  c`6pK` Dݝҝ0Q7~e)D?\LXLvU[m ŵ+w˾Î0. u⭐a/g P6CiܹdP@BݲH~Wj 礼Wrb#tI"dێe? ϵ̙}!j2讑zZ undertow-2.2.16.Final/websockets-jsr/000077500000000000000000000000001420065311100174375ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/pom.xml000066400000000000000000000226171420065311100207640ustar00rootroot00000000000000 4.0.0 io.undertow undertow-parent 2.2.16.Final io.undertow undertow-websockets-jsr 2.2.16.Final Undertow WebSockets JSR356 implementations INFO 7777 false false io.undertow undertow-core io.undertow undertow-servlet org.jboss.spec.javax.websocket jboss-websocket-api_1.1_spec org.jboss.logging jboss-logging-processor provided io.undertow undertow-core test-jar test io.undertow undertow-servlet test-jar test org.jboss.xnio xnio-nio test junit junit test io.netty netty-all test org.jboss.logmanager jboss-logmanager test org.apache.httpcomponents httpclient test org.wildfly.openssl wildfly-openssl ${version.org.wildfly.openssl} test src/test/resources src/test/java **/*.java org.apache.felix maven-bundle-plugin generate-manifest manifest io.undertow.websockets.jsr*;version=${project.version};-noimport:=true !sun.*, * org.apache.maven.plugins maven-jar-plugin ${project.build.outputDirectory}/META-INF/MANIFEST.MF org.apache.maven.plugins maven-surefire-plugin true reversealphabetical ${skipWebSocketTests} true ${proxy} localhost 7777 org.jboss.logmanager.LogManager ${test.level} ${test.ipv6} false ${org.wildfly.openssl.path} ${surefire.system.args} ${jacoco.agent.argLine} mac mac /usr/local/opt/openssl/lib spdy-no-tests test.spdy true true h2-no-tests test.h2 true true autobahn autobahn me.normanmaurer.maven.autobahntestsuite autobahntestsuite-maven-plugin 0.1.3 127.0.0.1 7777 io.undertow.websockets.jsr.test.autobahn.AnnotatedAutobahnServer * false true test fuzzingclient undertow-2.2.16.Final/websockets-jsr/src/000077500000000000000000000000001420065311100202265ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/000077500000000000000000000000001420065311100211525ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/000077500000000000000000000000001420065311100220735ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/000077500000000000000000000000001420065311100225025ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/000077500000000000000000000000001420065311100243515ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/000077500000000000000000000000001420065311100265225ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/000077500000000000000000000000001420065311100273205ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/Bootstrap.java000066400000000000000000000132021420065311100321360ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import io.undertow.servlet.ServletExtension; import io.undertow.servlet.Servlets; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.ThreadSetupHandler; import io.undertow.servlet.core.ContextClassLoaderSetupAction; import io.undertow.servlet.spec.ServletContextImpl; import io.undertow.connector.ByteBufferPool; import io.undertow.websockets.extensions.ExtensionHandshake; import org.xnio.XnioWorker; import javax.servlet.DispatcherType; import javax.servlet.FilterRegistration; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.websocket.DeploymentException; import javax.websocket.Extension; import javax.websocket.server.ServerContainer; import javax.websocket.server.ServerEndpointConfig; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.function.Supplier; /** * @author Stuart Douglas */ public class Bootstrap implements ServletExtension { public static final String FILTER_NAME = "Undertow Web Socket Filter"; @Override public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servletContext) { WebSocketDeploymentInfo info = (WebSocketDeploymentInfo) deploymentInfo.getServletContextAttributes().get(WebSocketDeploymentInfo.ATTRIBUTE_NAME); if (info == null) { return; } Supplier worker = info.getWorker(); ByteBufferPool buffers = info.getBuffers(); if(buffers == null) { ServerWebSocketContainer defaultContainer = UndertowContainerProvider.getDefaultContainer(); if(defaultContainer == null) { throw JsrWebSocketLogger.ROOT_LOGGER.bufferPoolWasNullAndNoDefault(); } JsrWebSocketLogger.ROOT_LOGGER.bufferPoolWasNull(); buffers = defaultContainer.getBufferPool(); } final List setup = new ArrayList<>(); setup.add(new ContextClassLoaderSetupAction(deploymentInfo.getClassLoader())); setup.addAll(deploymentInfo.getThreadSetupActions()); InetSocketAddress bind = null; if(info.getClientBindAddress() != null) { bind = new InetSocketAddress(info.getClientBindAddress(), 0); } List extensions = new ArrayList<>(); for(ExtensionHandshake e: info.getExtensions()) { extensions.add(new ExtensionImpl(e.getName(), Collections.emptyList())); } ServerWebSocketContainer container = new ServerWebSocketContainer(deploymentInfo.getClassIntrospecter(), servletContext.getClassLoader(), worker, buffers, setup, info.isDispatchToWorkerThread(), bind, info.getReconnectHandler(), extensions); try { for (Class annotation : info.getAnnotatedEndpoints()) { container.addEndpoint(annotation); } for(ServerEndpointConfig programatic : info.getProgramaticEndpoints()) { container.addEndpoint(programatic); } } catch (DeploymentException e) { throw new RuntimeException(e); } servletContext.setAttribute(ServerContainer.class.getName(), container); info.containerReady(container); SecurityActions.addContainer(deploymentInfo.getClassLoader(), container); deploymentInfo.addListener(Servlets.listener(WebSocketListener.class)); deploymentInfo.addDeploymentCompleteListener(new ServletContextListener() { @Override public void contextInitialized(ServletContextEvent sce) { container.validateDeployment(); } @Override public void contextDestroyed(ServletContextEvent sce) { } }); } private static final class WebSocketListener implements ServletContextListener { private ServerWebSocketContainer container; @Override public void contextInitialized(ServletContextEvent sce) { container = (ServerWebSocketContainer) sce.getServletContext().getAttribute(ServerContainer.class.getName()); FilterRegistration.Dynamic filter = sce.getServletContext().addFilter(FILTER_NAME, JsrWebSocketFilter.class); sce.getServletContext().addListener(JsrWebSocketFilter.LogoutListener.class); filter.setAsyncSupported(true); if(!container.getConfiguredServerEndpoints().isEmpty()){ filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); } else { container.setContextToAddFilter((ServletContextImpl) sce.getServletContext()); } } @Override public void contextDestroyed(ServletContextEvent sce) { SecurityActions.removeContainer(sce.getServletContext().getClassLoader()); container.close(); } } } ConfiguredClientEndpoint.java000066400000000000000000000037161420065311100350400ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import javax.websocket.ClientEndpointConfig; import io.undertow.servlet.api.InstanceFactory; import io.undertow.websockets.jsr.annotated.AnnotatedEndpointFactory; /** * @author Stuart Douglas */ public class ConfiguredClientEndpoint extends SessionContainer { private final ClientEndpointConfig config; private final AnnotatedEndpointFactory factory; private final EncodingFactory encodingFactory; private final InstanceFactory instanceFactory; public ConfiguredClientEndpoint(final ClientEndpointConfig config, final AnnotatedEndpointFactory factory, final EncodingFactory encodingFactory, InstanceFactory instanceFactory) { this.config = config; this.factory = factory; this.encodingFactory = encodingFactory; this.instanceFactory = instanceFactory; } public ConfiguredClientEndpoint() { this(null, null, null, null); } public ClientEndpointConfig getConfig() { return config; } public AnnotatedEndpointFactory getFactory() { return factory; } public EncodingFactory getEncodingFactory() { return encodingFactory; } public InstanceFactory getInstanceFactory() { return instanceFactory; } } ConfiguredServerEndpoint.java000066400000000000000000000062651420065311100350720ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import java.util.List; import io.undertow.servlet.api.InstanceFactory; import io.undertow.util.PathTemplate; import io.undertow.websockets.jsr.annotated.AnnotatedEndpointFactory; import javax.websocket.Extension; import javax.websocket.server.ServerEndpointConfig; /** * @author Stuart Douglas */ public class ConfiguredServerEndpoint extends SessionContainer { private final ServerEndpointConfig endpointConfiguration; private final AnnotatedEndpointFactory annotatedEndpointFactory; private final InstanceFactory endpointFactory; private final PathTemplate pathTemplate; private final EncodingFactory encodingFactory; private final List extensions; public ConfiguredServerEndpoint(final ServerEndpointConfig endpointConfiguration, final InstanceFactory endpointFactory, final PathTemplate pathTemplate, final EncodingFactory encodingFactory, AnnotatedEndpointFactory annotatedEndpointFactory, List installed) { this.endpointConfiguration = endpointConfiguration; this.endpointFactory = endpointFactory; this.pathTemplate = pathTemplate; this.encodingFactory = encodingFactory; this.annotatedEndpointFactory = annotatedEndpointFactory; this.extensions = installed; } public ConfiguredServerEndpoint(final ServerEndpointConfig endpointConfiguration, final InstanceFactory endpointFactory, final PathTemplate pathTemplate, final EncodingFactory encodingFactory) { this.endpointConfiguration = endpointConfiguration; this.endpointFactory = endpointFactory; this.pathTemplate = pathTemplate; this.encodingFactory = encodingFactory; this.annotatedEndpointFactory = null; this.extensions = endpointConfiguration.getExtensions(); } public ServerEndpointConfig getEndpointConfiguration() { return endpointConfiguration; } public InstanceFactory getEndpointFactory() { return endpointFactory; } public PathTemplate getPathTemplate() { return pathTemplate; } public EncodingFactory getEncodingFactory() { return encodingFactory; } public AnnotatedEndpointFactory getAnnotatedEndpointFactory() { return annotatedEndpointFactory; } /** * Return the websocket extensions configured. * * @return the list of extensions, the empty list if none. */ public List getExtensions() { return extensions; } } DefaultContainerConfigurator.java000066400000000000000000000072411420065311100357220ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import io.undertow.servlet.api.InstanceFactory; import io.undertow.servlet.api.InstanceHandle; import java.util.ArrayList; import java.util.List; import javax.websocket.Extension; import javax.websocket.HandshakeResponse; import javax.websocket.server.HandshakeRequest; import javax.websocket.server.ServerEndpointConfig; /** * Server default container configurator. *

    * This API is stupid, because it has no way to attach deployment specific context. * * @author Stuart Douglas */ public class DefaultContainerConfigurator extends ServerEndpointConfig.Configurator { public static final DefaultContainerConfigurator INSTANCE = new DefaultContainerConfigurator(); /** * thread local hacks to work around a horrible horrible broken API */ private static final ThreadLocal> currentInstanceFactory = new ThreadLocal<>(); private static final ThreadLocal> currentInstanceHandle = new ThreadLocal<>(); @Override public String getNegotiatedSubprotocol(final List supported, final List requested) { for(String proto : requested) { if(supported.contains(proto)) { return proto; } } return ""; } @Override public List getNegotiatedExtensions(final List installed, final List requested) { final List ret = new ArrayList<>(); for (Extension req : requested) { for (Extension extension : installed) { if (extension.getName().equals(req.getName())) { ret.add(req); break; } } } return ret; } @Override public boolean checkOrigin(final String originHeaderValue) { //we can't actually do anything here, because have have absolutely no context. return true; } @Override public void modifyHandshake(final ServerEndpointConfig sec, final HandshakeRequest request, final HandshakeResponse response) { } @Override public T getEndpointInstance(final Class endpointClass) throws InstantiationException { InstanceFactory factory = currentInstanceFactory.get(); if(factory != null) { InstanceHandle instance = factory.createInstance(); currentInstanceHandle.set(instance); return (T) instance.getInstance(); } try { return endpointClass.newInstance(); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } static void setCurrentInstanceFactory(InstanceFactory factory) { currentInstanceFactory.set(factory); } static InstanceHandle clearCurrentInstanceFactory() { currentInstanceFactory.remove(); InstanceHandle handle = currentInstanceHandle.get(); currentInstanceHandle.remove(); return handle; } } DefaultPongMessage.java000066400000000000000000000031121420065311100336160ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import org.xnio.Buffers; import javax.websocket.PongMessage; import java.nio.ByteBuffer; /** * Default {@link PongMessage} implementation * * @author Norman Maurer */ public final class DefaultPongMessage implements PongMessage { private static final PongMessage EMPTY = new DefaultPongMessage(Buffers.EMPTY_BYTE_BUFFER); private final ByteBuffer data; private DefaultPongMessage(ByteBuffer data) { this.data = data; } @Override public ByteBuffer getApplicationData() { return data; } /** * Create a {@link PongMessage} from the given {@link ByteBuffer}. */ public static PongMessage create(ByteBuffer data) { if (data == null || data.hasRemaining()) { return new DefaultPongMessage(data); } return EMPTY; } } DefaultWebSocketClientSslProvider.java000066400000000000000000000055551420065311100366450ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import io.undertow.protocols.ssl.UndertowXnioSsl; import org.xnio.OptionMap; import org.xnio.XnioWorker; import org.xnio.ssl.XnioSsl; import javax.net.ssl.SSLContext; import javax.websocket.ClientEndpointConfig; import javax.websocket.Endpoint; import java.net.URI; /** * Client SSL provider that gets the SSL context in one of two ways. * * Either the {@link #setSslContext(javax.net.ssl.SSLContext)} method can * be invoked before connecting, and this context will be used for the next * client connection from this thread, or alternatively the * io.undertow.websocket.SSL_CONTEXT property can be set in the user properties * of the ClientEndpointConfig. * * @author Stuart Douglas */ public class DefaultWebSocketClientSslProvider implements WebsocketClientSslProvider { public static final String SSL_CONTEXT = "io.undertow.websocket.SSL_CONTEXT"; private static final ThreadLocal LOCAL_SSL_CONTEXT = new ThreadLocal<>(); @Override public XnioSsl getSsl(XnioWorker worker, Class annotatedEndpoint, URI uri) { return getThreadLocalSsl(worker); } @Override public XnioSsl getSsl(XnioWorker worker, Object annotatedEndpointInstance, URI uri) { return getThreadLocalSsl(worker); } @Override public XnioSsl getSsl(XnioWorker worker, Endpoint endpoint, ClientEndpointConfig cec, URI uri) { XnioSsl ssl = getThreadLocalSsl(worker); if(ssl != null) { return ssl; } //look for some SSL config SSLContext sslContext = (SSLContext) cec.getUserProperties().get(SSL_CONTEXT); if (sslContext != null) { return new UndertowXnioSsl(worker.getXnio(), OptionMap.EMPTY, sslContext); } return null; } public static void setSslContext(final SSLContext context) { LOCAL_SSL_CONTEXT.set(context); } private XnioSsl getThreadLocalSsl(XnioWorker worker) { SSLContext val = LOCAL_SSL_CONTEXT.get(); if (val != null) { LOCAL_SSL_CONTEXT.remove(); return new UndertowXnioSsl(worker.getXnio(), OptionMap.EMPTY, val); } return null; } } undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/Encoding.java000066400000000000000000000272741420065311100317250ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.nio.ByteBuffer; import java.util.List; import java.util.Map; import javax.websocket.DecodeException; import javax.websocket.Decoder; import javax.websocket.EncodeException; import javax.websocket.Encoder; import io.undertow.servlet.api.InstanceHandle; /** * Manages all encoders and decoders for an endpoint instance * * @author Stuart Douglas */ public class Encoding implements Closeable { private final Map, List>> binaryEncoders; private final Map, List>> binaryDecoders; private final Map, List>> textEncoders; private final Map, List>> textDecoders; public Encoding(final Map, List>> binaryEncoders, final Map, List>> binaryDecoders, final Map, List>> textEncoders, final Map, List>> textDecoders) { this.binaryEncoders = binaryEncoders; this.binaryDecoders = binaryDecoders; this.textEncoders = textEncoders; this.textDecoders = textDecoders; } public boolean canEncodeText(final Class type) { if(textEncoders.containsKey(type)) { return true; } for(Class key : textEncoders.keySet()) { if(key.isAssignableFrom(type)) { return true; } } if (EncodingFactory.isPrimitiveOrBoxed(type)) { Class primType = boxedType(type); return !binaryEncoders.containsKey(primType) && !binaryEncoders.containsKey(Object.class); //don't use a built in coding if a user supplied binary one is present } return false; } public boolean canDecodeText(final Class type) { if(textDecoders.containsKey(type)) { return true; } if (EncodingFactory.isPrimitiveOrBoxed(type)) { Class primType = boxedType(type); return !binaryDecoders.containsKey(primType) && !binaryEncoders.containsKey(Object.class); //don't use a built in coding if a user supplied binary one is present } return false; } public boolean canEncodeBinary(final Class type) { if(binaryEncoders.containsKey(type)) { return true; } for(Class key : binaryEncoders.keySet()) { if(key.isAssignableFrom(type)) { return true; } } return false; } public boolean canDecodeBinary(final Class type) { return binaryDecoders.containsKey(type); } public Object decodeText(final Class targetType, final String message) throws DecodeException { if (EncodingFactory.isPrimitiveOrBoxed(targetType)) { return decodePrimitive(targetType, message); } List> decoders = textDecoders.get(targetType); if (decoders != null) { for (InstanceHandle decoderHandle : decoders) { Decoder decoder = decoderHandle.getInstance(); if (decoder instanceof Decoder.Text) { if (((Decoder.Text) decoder).willDecode(message)) { return ((Decoder.Text) decoder).decode(message); } } else { try { return ((Decoder.TextStream) decoder).decode(new StringReader(message)); } catch (IOException e) { throw new DecodeException(message, "Could not decode string", e); } } } } throw new DecodeException(message, "Could not decode string"); } private Object decodePrimitive(final Class targetType, final String message) throws DecodeException { if (targetType == Boolean.class || targetType == boolean.class) { return Boolean.valueOf(message); } else if (targetType == Character.class || targetType == char.class) { return message.charAt(0); } else if (targetType == Byte.class || targetType == byte.class) { return Byte.valueOf(message); } else if (targetType == Short.class || targetType == short.class) { return Short.valueOf(message); } else if (targetType == Integer.class || targetType == int.class) { return Integer.valueOf(message); } else if (targetType == Long.class || targetType == long.class) { return Long.valueOf(message); } else if (targetType == Float.class || targetType == float.class) { return Float.valueOf(message); } else if (targetType == Double.class || targetType == double.class) { return Double.valueOf(message); } return null; // impossible } public Object decodeBinary(final Class targetType, final byte[] bytes) throws DecodeException { List> decoders = binaryDecoders.get(targetType); if (decoders != null) { for (InstanceHandle decoderHandle : decoders) { Decoder decoder = decoderHandle.getInstance(); if (decoder instanceof Decoder.Binary) { if (((Decoder.Binary) decoder).willDecode(ByteBuffer.wrap(bytes))) { return ((Decoder.Binary) decoder).decode(ByteBuffer.wrap(bytes)); } } else { try { return ((Decoder.BinaryStream) decoder).decode(new ByteArrayInputStream(bytes)); } catch (IOException e) { throw new DecodeException(ByteBuffer.wrap(bytes), "Could not decode binary", e); } } } } throw new DecodeException(ByteBuffer.wrap(bytes), "Could not decode binary"); } public String encodeText(final Object o) throws EncodeException { List> encoders = textEncoders.get(o.getClass()); if(encoders == null) { for(Map.Entry, List>> entry : textEncoders.entrySet()) { if(entry.getKey().isAssignableFrom(o.getClass())) { encoders = entry.getValue(); break; } } } if (encoders != null) { for (InstanceHandle decoderHandle : encoders) { Encoder decoder = decoderHandle.getInstance(); if (decoder instanceof Encoder.Text) { return ((Encoder.Text) decoder).encode(o); } else { try { StringWriter out = new StringWriter(); ((Encoder.TextStream) decoder).encode(o, out); return out.toString(); } catch (IOException e) { throw new EncodeException(o, "Could not encode text", e); } } } } if (EncodingFactory.isPrimitiveOrBoxed(o.getClass())) { return o.toString(); } throw new EncodeException(o, "Could not encode text"); } public ByteBuffer encodeBinary(final Object o) throws EncodeException { List> encoders = binaryEncoders.get(o.getClass()); if(encoders == null) { for(Map.Entry, List>> entry : binaryEncoders.entrySet()) { if(entry.getKey().isAssignableFrom(o.getClass())) { encoders = entry.getValue(); break; } } } if (encoders != null) { for (InstanceHandle decoderHandle : encoders) { Encoder decoder = decoderHandle.getInstance(); if (decoder instanceof Encoder.Binary) { return ((Encoder.Binary) decoder).encode(o); } else { try { ByteArrayOutputStream out = new ByteArrayOutputStream(); ((Encoder.BinaryStream) decoder).encode(o, out); return ByteBuffer.wrap(out.toByteArray()); } catch (IOException e) { throw new EncodeException(o, "Could not encode binary", e); } } } } throw new EncodeException(o, "Could not encode binary"); } @Override public void close() { for (Map.Entry, List>> entry : binaryDecoders.entrySet()) { for (InstanceHandle val : entry.getValue()) { val.getInstance().destroy(); val.release(); } } for (Map.Entry, List>> entry : textDecoders.entrySet()) { for (InstanceHandle val : entry.getValue()) { val.getInstance().destroy(); val.release(); } } for (Map.Entry, List>> entry : binaryEncoders.entrySet()) { for (InstanceHandle val : entry.getValue()) { val.getInstance().destroy(); val.release(); } } for (Map.Entry, List>> entry : textEncoders.entrySet()) { for (InstanceHandle val : entry.getValue()) { val.getInstance().destroy(); val.release(); } } } private static Class boxedType(final Class targetType) { if (targetType == Boolean.class || targetType == boolean.class) { return Boolean.class; } else if (targetType == Character.class || targetType == char.class) { return Character.class; } else if (targetType == Byte.class || targetType == byte.class) { return Byte.class; } else if (targetType == Short.class || targetType == short.class) { return Short.class; } else if (targetType == Integer.class || targetType == int.class) { return Integer.class; } else if (targetType == Long.class || targetType == long.class) { return Long.class; } else if (targetType == Float.class || targetType == float.class) { return Float.class; } else if (targetType == Double.class || targetType == double.class) { return Double.class; } return targetType; } } undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/EncodingFactory.java000066400000000000000000000413201420065311100332410ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.websocket.Decoder; import javax.websocket.DeploymentException; import javax.websocket.Encoder; import javax.websocket.EndpointConfig; import io.undertow.servlet.api.ClassIntrospecter; import io.undertow.servlet.api.InstanceFactory; import io.undertow.servlet.api.InstanceHandle; /** * Factory class that produces encoding instances for an endpoint. This also provides static * methods about the capabilities of encoders. *

    * These classes also perform implicit encodings for java primitives * * @author Stuart Douglas */ public class EncodingFactory { /** * An encoding factory that can deal with primitive types. */ public static final EncodingFactory DEFAULT = new EncodingFactory(Collections.EMPTY_MAP, Collections.EMPTY_MAP, Collections.EMPTY_MAP, Collections.EMPTY_MAP); private final Map, List>> binaryEncoders; private final Map, List>> binaryDecoders; private final Map, List>> textEncoders; private final Map, List>> textDecoders; public EncodingFactory(final Map, List>> binaryEncoders, final Map, List>> binaryDecoders, final Map, List>> textEncoders, final Map, List>> textDecoders) { this.binaryEncoders = binaryEncoders; this.binaryDecoders = binaryDecoders; this.textEncoders = textEncoders; this.textDecoders = textDecoders; } public boolean canEncodeText(final Class type) { if (isPrimitiveOrBoxed(type)) { return true; } return textEncoders.containsKey(type); } public boolean canDecodeText(final Class type) { if (isPrimitiveOrBoxed(type)) { return true; } return textDecoders.containsKey(type); } public boolean canEncodeBinary(final Class type) { return binaryEncoders.containsKey(type); } public boolean canDecodeBinary(final Class type) { return binaryDecoders.containsKey(type); } public Encoding createEncoding(final EndpointConfig endpointConfig) { try { Map, List>> binaryEncoders = this.binaryEncoders.isEmpty() ? Collections., List>>emptyMap() : new HashMap, List>>(); Map, List>> binaryDecoders = this.binaryDecoders.isEmpty() ? Collections., List>>emptyMap() : new HashMap, List>>(); Map, List>> textEncoders = this.textEncoders.isEmpty() ? Collections., List>>emptyMap() : new HashMap, List>>(); Map, List>> textDecoders = this.textDecoders.isEmpty() ? Collections., List>>emptyMap() : new HashMap, List>>(); for (Map.Entry, List>> entry : this.binaryEncoders.entrySet()) { final List> val = new ArrayList<>(entry.getValue().size()); binaryEncoders.put(entry.getKey(), val); for (InstanceFactory factory : entry.getValue()) { InstanceHandle instance = factory.createInstance(); instance.getInstance().init(endpointConfig); val.add(instance); } } for (Map.Entry, List>> entry : this.binaryDecoders.entrySet()) { final List> val = new ArrayList<>(entry.getValue().size()); binaryDecoders.put(entry.getKey(), val); for (InstanceFactory factory : entry.getValue()) { InstanceHandle instance = factory.createInstance(); instance.getInstance().init(endpointConfig); val.add(instance); } } for (Map.Entry, List>> entry : this.textEncoders.entrySet()) { final List> val = new ArrayList<>(entry.getValue().size()); textEncoders.put(entry.getKey(), val); for (InstanceFactory factory : entry.getValue()) { InstanceHandle instance = factory.createInstance(); instance.getInstance().init(endpointConfig); val.add(instance); } } for (Map.Entry, List>> entry : this.textDecoders.entrySet()) { final List> val = new ArrayList<>(entry.getValue().size()); textDecoders.put(entry.getKey(), val); for (InstanceFactory factory : entry.getValue()) { InstanceHandle instance = factory.createInstance(); instance.getInstance().init(endpointConfig); val.add(instance); } } return new Encoding(binaryEncoders, binaryDecoders, textEncoders, textDecoders); } catch (InstantiationException e) { throw new RuntimeException(e); } } public static EncodingFactory createFactory(final ClassIntrospecter classIntrospecter, final Class[] decoders, final Class[] encoders) throws DeploymentException { return createFactory(classIntrospecter, Arrays.asList(decoders), Arrays.asList(encoders)); } public static EncodingFactory createFactory(final ClassIntrospecter classIntrospecter, final List> decoders, final List> encoders) throws DeploymentException { final Map, List>> binaryEncoders = new HashMap<>(); final Map, List>> binaryDecoders = new HashMap<>(); final Map, List>> textEncoders = new HashMap<>(); final Map, List>> textDecoders = new HashMap<>(); for (Class decoder : decoders) { if (Decoder.Binary.class.isAssignableFrom(decoder)) { try { Method method = decoder.getMethod("decode", ByteBuffer.class); final Class type = resolveReturnType(method, decoder); List> list = binaryDecoders.get(type); if (list == null) { binaryDecoders.put(type, list = new ArrayList<>()); } list.add(classIntrospecter.createInstanceFactory(decoder)); } catch (NoSuchMethodException e) { throw JsrWebSocketMessages.MESSAGES.couldNotDetermineTypeOfDecodeMethodForClass(decoder, e); } } else if (Decoder.BinaryStream.class.isAssignableFrom(decoder)) { try { Method method = decoder.getMethod("decode", InputStream.class); final Class type = resolveReturnType(method, decoder); List> list = binaryDecoders.get(type); if (list == null) { binaryDecoders.put(type, list = new ArrayList<>()); } list.add(classIntrospecter.createInstanceFactory(decoder)); } catch (NoSuchMethodException e) { throw JsrWebSocketMessages.MESSAGES.couldNotDetermineTypeOfDecodeMethodForClass(decoder, e); } } else if (Decoder.Text.class.isAssignableFrom(decoder)) { try { Method method = decoder.getMethod("decode", String.class); final Class type = resolveReturnType(method, decoder); List> list = textDecoders.get(type); if (list == null) { textDecoders.put(type, list = new ArrayList<>()); } list.add(classIntrospecter.createInstanceFactory(decoder)); } catch (NoSuchMethodException e) { throw JsrWebSocketMessages.MESSAGES.couldNotDetermineTypeOfDecodeMethodForClass(decoder, e); } } else if (Decoder.TextStream.class.isAssignableFrom(decoder)) { try { Method method = decoder.getMethod("decode", Reader.class); final Class type = resolveReturnType(method, decoder); List> list = textDecoders.get(type); if (list == null) { textDecoders.put(type, list = new ArrayList<>()); } list.add(createInstanceFactory(classIntrospecter, decoder)); } catch (NoSuchMethodException e) { throw JsrWebSocketMessages.MESSAGES.couldNotDetermineTypeOfDecodeMethodForClass(decoder, e); } } else { throw JsrWebSocketMessages.MESSAGES.didNotImplementKnownDecoderSubclass(decoder); } } for (Class encoder : encoders) { if (Encoder.Binary.class.isAssignableFrom(encoder)) { final Class type = findEncodeMethod(encoder, ByteBuffer.class); List> list = binaryEncoders.get(type); if (list == null) { binaryEncoders.put(type, list = new ArrayList<>()); } list.add(createInstanceFactory(classIntrospecter, encoder)); } else if (Encoder.BinaryStream.class.isAssignableFrom(encoder)) { final Class type = findEncodeMethod(encoder, void.class, OutputStream.class); List> list = binaryEncoders.get(type); if (list == null) { binaryEncoders.put(type, list = new ArrayList<>()); } list.add(createInstanceFactory(classIntrospecter, encoder)); } else if (Encoder.Text.class.isAssignableFrom(encoder)) { final Class type = findEncodeMethod(encoder, String.class); List> list = textEncoders.get(type); if (list == null) { textEncoders.put(type, list = new ArrayList<>()); } list.add(createInstanceFactory(classIntrospecter, encoder)); } else if (Encoder.TextStream.class.isAssignableFrom(encoder)) { final Class type = findEncodeMethod(encoder, void.class, Writer.class); List> list = textEncoders.get(type); if (list == null) { textEncoders.put(type, list = new ArrayList<>()); } list.add(createInstanceFactory(classIntrospecter, encoder)); } } return new EncodingFactory(binaryEncoders, binaryDecoders, textEncoders, textDecoders); } private static Class resolveReturnType(Method method, Class decoder) { Type genericReturnType = method.getGenericReturnType(); if (genericReturnType instanceof Class) { return (Class) genericReturnType; } else if (genericReturnType instanceof TypeVariable) { TypeVariable type = ((TypeVariable) genericReturnType); List classes = new ArrayList<>(); Class c = decoder; while (c != method.getDeclaringClass() && c != null) { classes.add(c); c = c.getSuperclass(); } Collections.reverse(classes); String currentName = type.getName(); int currentPos = -1; for (Class clz : classes) { for (int i = 0; i < clz.getSuperclass().getTypeParameters().length; ++i) { TypeVariable> param = clz.getSuperclass().getTypeParameters()[i]; if (param.getName().equals(currentName)) { currentPos = i; break; } } Type gs = clz.getGenericSuperclass(); if (gs instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) gs; Type at = pt.getActualTypeArguments()[currentPos]; if (at instanceof Class) { return (Class) at; } else if (at instanceof TypeVariable) { TypeVariable tv = (TypeVariable) at; currentName = tv.getName(); } } } //todo: should we throw an exception here? It should never actually be reached return method.getReturnType(); } else { return method.getReturnType(); } } private static InstanceFactory createInstanceFactory(final ClassIntrospecter classIntrospecter, final Class decoder) throws DeploymentException { try { return classIntrospecter.createInstanceFactory(decoder); } catch (NoSuchMethodException e) { throw JsrWebSocketMessages.MESSAGES.classDoesNotHaveDefaultConstructor(decoder, e); } } private static Class findEncodeMethod(final Class encoder, final Class returnType, Class... otherParameters) throws DeploymentException { for (Method method : encoder.getMethods()) { if (method.getName().equals("encode") && !method.isBridge() && method.getParameterCount() == 1 + otherParameters.length && method.getReturnType() == returnType) { boolean ok = true; for (int i = 1; i < method.getParameterCount(); ++i) { if (method.getParameterTypes()[i] != otherParameters[i - 1]) { ok = false; break; } } if (ok) { return method.getParameterTypes()[0]; } } } throw JsrWebSocketMessages.MESSAGES.couldNotDetermineTypeOfEncodeMethodForClass(encoder); } static boolean isPrimitiveOrBoxed(final Class clazz) { return clazz.isPrimitive() || clazz == Boolean.class || clazz == Byte.class || clazz == Character.class || clazz == Short.class || clazz == Integer.class || clazz == Long.class || clazz == Float.class || clazz == Double.class;//we don't care about void } } EndpointSessionHandler.java000066400000000000000000000146351420065311100345370ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import io.undertow.servlet.api.InstanceFactory; import io.undertow.servlet.api.InstanceHandle; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.servlet.util.ImmediateInstanceHandle; import io.undertow.websockets.WebSocketConnectionCallback; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.jsr.annotated.AnnotatedEndpoint; import io.undertow.websockets.jsr.handshake.HandshakeUtil; import io.undertow.websockets.spi.WebSocketHttpExchange; import org.xnio.IoUtils; import javax.servlet.http.HttpServletRequest; import javax.websocket.Endpoint; import javax.websocket.Extension; import javax.websocket.server.ServerEndpointConfig; import java.net.URI; import java.security.Principal; import java.util.Collections; /** * {@link WebSocketConnectionCallback} implementation which will setuo the {@link UndertowSession} and notify * the {@link Endpoint} about the new session. * * @author Norman Maurer */ public final class EndpointSessionHandler implements WebSocketConnectionCallback { private final ServerWebSocketContainer container; public EndpointSessionHandler(ServerWebSocketContainer container) { this.container = container; } /** * Returns the {@link ServerWebSocketContainer} which was used for this {@link WebSocketConnectionCallback}. */ ServerWebSocketContainer getContainer() { return container; } @Override public void onConnect(WebSocketHttpExchange exchange, WebSocketChannel channel) { ConfiguredServerEndpoint config = HandshakeUtil.getConfig(channel); try { if(container.isClosed()) { //if the underlying container is closed we just reject channel.sendClose(); channel.resumeReceives(); return; } InstanceFactory endpointFactory = config.getEndpointFactory(); ServerEndpointConfig.Configurator configurator = config.getEndpointConfiguration().getConfigurator(); final InstanceHandle instance; DefaultContainerConfigurator.setCurrentInstanceFactory(endpointFactory); final Object instanceFromConfigurator = configurator.getEndpointInstance(config.getEndpointConfiguration().getEndpointClass()); final InstanceHandle factoryInstance = DefaultContainerConfigurator.clearCurrentInstanceFactory(); if (factoryInstance == null) { instance = new ImmediateInstanceHandle<>(instanceFromConfigurator); } else if (factoryInstance.getInstance() == instanceFromConfigurator) { instance = factoryInstance; } else { //the default instance has been wrapped instance = new InstanceHandle() { @Override public Object getInstance() { return instanceFromConfigurator; } @Override public void release() { factoryInstance.release(); } }; } ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); Principal principal = exchange.getAttachment(HandshakeUtil.PRINCIPAL); if(principal == null) { if(src.getServletRequest() instanceof HttpServletRequest) { principal = ((HttpServletRequest)src.getServletRequest()).getUserPrincipal(); } else { principal = src.getOriginalRequest().getUserPrincipal(); } } final InstanceHandle endpointInstance; if(config.getAnnotatedEndpointFactory() != null) { final AnnotatedEndpoint annotated = config.getAnnotatedEndpointFactory().createInstance(instance); endpointInstance = new InstanceHandle() { @Override public Endpoint getInstance() { return annotated; } @Override public void release() { instance.release(); } }; } else { endpointInstance = (InstanceHandle) instance; } UndertowSession session = new UndertowSession(channel, URI.create(exchange.getRequestURI()), exchange.getAttachment(HandshakeUtil.PATH_PARAMS), exchange.getRequestParameters(), this, principal, endpointInstance, config.getEndpointConfiguration(), exchange.getQueryString(), config.getEncodingFactory().createEncoding(config.getEndpointConfiguration()), config, channel.getSubProtocol(), Collections.emptyList(), null); config.addOpenSession(session); session.setMaxBinaryMessageBufferSize(getContainer().getDefaultMaxBinaryMessageBufferSize()); session.setMaxTextMessageBufferSize(getContainer().getDefaultMaxTextMessageBufferSize()); session.setMaxIdleTimeout(getContainer().getDefaultMaxSessionIdleTimeout()); session.getAsyncRemote().setSendTimeout(getContainer().getDefaultAsyncSendTimeout()); try { endpointInstance.getInstance().onOpen(session, config.getEndpointConfiguration()); } catch (Exception e) { endpointInstance.getInstance().onError(session, e); IoUtils.safeClose(session); } channel.resumeReceives(); } catch (Exception e) { JsrWebSocketLogger.REQUEST_LOGGER.endpointCreationFailed(e); IoUtils.safeClose(channel); } } } undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/ExtensionImpl.java000066400000000000000000000041731420065311100327660ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import io.undertow.websockets.WebSocketExtension; import javax.websocket.Extension; import java.util.ArrayList; import java.util.List; /** * @author Stuart Douglas */ public class ExtensionImpl implements Extension { private final String name; private final List parameters; ExtensionImpl(String name, List parameters) { this.name = name; this.parameters = parameters; } @Override public String getName() { return name; } @Override public List getParameters() { return parameters; } public static class ParameterImpl implements Parameter { private final String name; private final String value; public ParameterImpl(String name, String value) { this.name = name; this.value = value; } @Override public String getName() { return name; } @Override public String getValue() { return value; } } public static Extension create(WebSocketExtension extension) { List params = new ArrayList<>(extension.getParameters().size()); for(WebSocketExtension.Parameter p : extension.getParameters()) { params.add(new ParameterImpl(p.getName(), p.getValue())); } return new ExtensionImpl(extension.getName(), params); } } undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/FrameHandler.java000066400000000000000000000543211420065311100325200ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import io.undertow.websockets.core.AbstractReceiveListener; import io.undertow.websockets.core.BufferedBinaryMessage; import io.undertow.websockets.core.BufferedTextMessage; import io.undertow.websockets.core.StreamSourceFrameChannel; import io.undertow.websockets.core.UTF8Output; import io.undertow.websockets.core.WebSocketCallback; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSockets; import io.undertow.websockets.jsr.util.ClassUtils; import org.xnio.Buffers; import org.xnio.Pooled; import javax.websocket.CloseReason; import javax.websocket.Endpoint; import javax.websocket.MessageHandler; import javax.websocket.PongMessage; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.StringReader; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; /** * @author Stuart Douglas * @author Norman Maurer */ class FrameHandler extends AbstractReceiveListener { private final Endpoint endpoint; private final UndertowSession session; protected static final byte[] EMPTY = new byte[0]; private final ConcurrentMap handlers = new ConcurrentHashMap<>(); private final Executor executor; /** * Supported types of WebSocket frames for which a {@link MessageHandler} can be added. */ enum FrameType { PONG, BYTE, TEXT } protected FrameHandler(UndertowSession session, Endpoint endpoint) { this.session = session; this.endpoint = endpoint; final Executor executor; if (session.getContainer().isDispatchToWorker()) { executor = new OrderedExecutor(session.getWebSocketChannel().getWorker()); } else { executor = session.getWebSocketChannel().getIoThread(); } this.executor = executor; } @Override protected void onFullCloseMessage(final WebSocketChannel channel, final BufferedBinaryMessage message) { if(session.isSessionClosed()) { //we have already handled this when we sent the close frame message.getData().free(); return; } final Pooled pooled = message.getData(); final ByteBuffer singleBuffer = toBuffer(pooled.getResource()); final ByteBuffer toSend = singleBuffer.duplicate(); //send the close immediatly WebSockets.sendClose(toSend, channel, null); session.getContainer().invokeEndpointMethod(executor, new Runnable() { @Override public void run() { try { if (singleBuffer.remaining() > 1) { final CloseReason.CloseCode code = CloseReason.CloseCodes.getCloseCode(singleBuffer.getShort()); final String reasonPhrase = singleBuffer.remaining() > 1 ? new UTF8Output(singleBuffer).extract() : null; session.closeInternal(new CloseReason(code, reasonPhrase)); } else { session.closeInternal(new CloseReason(CloseReason.CloseCodes.NO_STATUS_CODE, null)); } } catch (IOException e) { invokeOnError(e); } finally { pooled.close(); } } }); } private void invokeOnError(final Throwable e) { session.getContainer().invokeEndpointMethod(executor, new Runnable() { @Override public void run() { getEndpoint().onError(session, e); } }); } @Override protected void onFullPongMessage(final WebSocketChannel webSocketChannel, BufferedBinaryMessage bufferedBinaryMessage) { if(session.isSessionClosed()) { //to bad, the channel has already been closed //we just ignore messages that are received after we have closed, as the endpoint is no longer in a valid state to deal with them //this this should only happen if a message was on the wire when we called close() bufferedBinaryMessage.getData().free(); return; } final HandlerWrapper handler = getHandler(FrameType.PONG); if (handler != null) { final Pooled pooled = bufferedBinaryMessage.getData(); final PongMessage message = DefaultPongMessage.create(toBuffer(pooled.getResource())); session.getContainer().invokeEndpointMethod(executor, new Runnable() { @Override public void run() { try { ((MessageHandler.Whole) handler.getHandler()).onMessage(message); } catch (Exception e) { invokeOnError(e); } finally { pooled.close(); } } }); } else { bufferedBinaryMessage.getData().free(); } } @Override protected void onText(WebSocketChannel webSocketChannel, StreamSourceFrameChannel messageChannel) throws IOException { if(session.isSessionClosed()) { //to bad, the channel has already been closed //we just ignore messages that are received after we have closed, as the endpoint is no longer in a valid state to deal with them //this this should only happen if a message was on the wire when we called close() messageChannel.close(); return; } final HandlerWrapper handler = getHandler(FrameType.TEXT); if (handler != null && handler.isPartialHandler()) { BufferedTextMessage data = new BufferedTextMessage(false); data.read(messageChannel, new WebSocketCallback() { @Override public void complete(WebSocketChannel channel, BufferedTextMessage context) { invokeTextHandler(context, handler, context.isComplete()); } @Override public void onError(WebSocketChannel channel, BufferedTextMessage context, Throwable throwable) { invokeOnError(throwable); } }); } else { bufferFullMessage(messageChannel); } } @Override protected void onBinary(WebSocketChannel webSocketChannel, StreamSourceFrameChannel messageChannel) throws IOException { if(session.isSessionClosed()) { //to bad, the channel has already been closed //we just ignore messages that are received after we have closed, as the endpoint is no longer in a valid state to deal with them //this this should only happen if a message was on the wire when we called close() messageChannel.close(); return; } final HandlerWrapper handler = getHandler(FrameType.BYTE); if (handler != null && handler.isPartialHandler()) { BufferedBinaryMessage data = new BufferedBinaryMessage(session.getMaxBinaryMessageBufferSize(), false); data.read(messageChannel, new WebSocketCallback() { @Override public void complete(WebSocketChannel channel, BufferedBinaryMessage context) { invokeBinaryHandler(context, handler, context.isComplete()); } @Override public void onError(WebSocketChannel channel, BufferedBinaryMessage context, Throwable throwable) { invokeOnError(throwable); } }); } else { bufferFullMessage(messageChannel); } } private void invokeBinaryHandler(final BufferedBinaryMessage context, final HandlerWrapper handler, final boolean finalFragment) { final Pooled pooled = context.getData(); session.getContainer().invokeEndpointMethod(executor, new Runnable() { @Override public void run() { try { if (handler.isPartialHandler()) { MessageHandler.Partial mHandler = (MessageHandler.Partial) handler.getHandler(); ByteBuffer[] payload = pooled.getResource(); if(handler.decodingNeeded) { Object object = getSession().getEncoding().decodeBinary(handler.getMessageType(), toArray(payload)); mHandler.onMessage(object, finalFragment); } else if (handler.getMessageType() == ByteBuffer.class) { mHandler.onMessage(toBuffer(payload), finalFragment); } else if (handler.getMessageType() == byte[].class) { byte[] data = toArray(payload); mHandler.onMessage(data, finalFragment); } else if (handler.getMessageType() == InputStream.class) { byte[] data = toArray(payload); mHandler.onMessage(new ByteArrayInputStream(data), finalFragment); } } else { MessageHandler.Whole mHandler = (MessageHandler.Whole) handler.getHandler(); ByteBuffer[] payload = pooled.getResource(); if(handler.decodingNeeded) { Object object = getSession().getEncoding().decodeBinary(handler.getMessageType(), toArray(payload)); mHandler.onMessage(object); } else if (handler.getMessageType() == ByteBuffer.class) { mHandler.onMessage(toBuffer(payload)); } else if (handler.getMessageType() == byte[].class) { byte[] data = toArray(payload); mHandler.onMessage(data); } else if (handler.getMessageType() == InputStream.class) { byte[] data = toArray(payload); mHandler.onMessage(new ByteArrayInputStream(data)); } } } catch (Exception e) { invokeOnError(e); } finally { pooled.close(); } } }); } private void invokeTextHandler(final BufferedTextMessage data, final HandlerWrapper handler, final boolean finalFragment) { final String message = data.getData(); session.getContainer().invokeEndpointMethod(executor, new Runnable() { @Override public void run() { MessageHandler mHandler = handler.getHandler(); try { if (mHandler instanceof MessageHandler.Partial) { if (handler.decodingNeeded) { Object object = getSession().getEncoding().decodeText(handler.getMessageType(), message); ((MessageHandler.Partial) handler.getHandler()).onMessage(object, finalFragment); } else if (handler.getMessageType() == String.class) { ((MessageHandler.Partial) handler.getHandler()).onMessage(message, finalFragment); } else if (handler.getMessageType() == Reader.class) { ((MessageHandler.Partial) handler.getHandler()).onMessage(new StringReader(message), finalFragment); } } else { if(handler.decodingNeeded) { Object object = getSession().getEncoding().decodeText(handler.getMessageType(), message); ((MessageHandler.Whole) handler.getHandler()).onMessage(object); } else if (handler.getMessageType() == String.class) { ((MessageHandler.Whole) handler.getHandler()).onMessage(message); } else if (handler.getMessageType() == Reader.class) { ((MessageHandler.Whole) handler.getHandler()).onMessage(new StringReader(message)); } } } catch (Exception e) { invokeOnError(e); } } }); } @Override protected void onError(WebSocketChannel channel, Throwable error) { try { getEndpoint().onError(session, error); } finally { session.forceClose(); } } @Override protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage message) { if(session.isSessionClosed()) { //to bad, the channel has already been closed //we just ignore messages that are received after we have closed, as the endpoint is no longer in a valid state to deal with them //this this should only happen if a message was on the wire when we called close() return; } HandlerWrapper handler = getHandler(FrameType.TEXT); if (handler != null) { invokeTextHandler(message, handler, true); } } @Override protected void onFullBinaryMessage(WebSocketChannel channel, BufferedBinaryMessage message) { if(session.isSessionClosed()) { //to bad, the channel has already been closed //we just ignore messages that are received after we have closed, as the endpoint is no longer in a valid state to deal with them //this this should only happen if a message was on the wire when we called close() message.getData().close(); return; } HandlerWrapper handler = getHandler(FrameType.BYTE); if (handler != null) { invokeBinaryHandler(message, handler, true); } else { message.getData().close(); } } protected static ByteBuffer toBuffer(ByteBuffer... payload) { if (payload.length == 1) { return payload[0]; } int size = (int) Buffers.remaining(payload); if (size == 0) { return Buffers.EMPTY_BYTE_BUFFER; } ByteBuffer buffer = ByteBuffer.allocate(size); for (ByteBuffer buf : payload) { buffer.put(buf); } buffer.flip(); return buffer; } protected static byte[] toArray(ByteBuffer... payload) { if (payload.length == 1) { ByteBuffer buf = payload[0]; if (buf.hasArray() && buf.arrayOffset() == 0 && buf.position() == 0 && buf.array().length == buf.remaining()) { return buf.array(); } } return Buffers.take(payload, 0, payload.length); } public final void addHandler(Class messageType, MessageHandler handler) { addHandlerInternal(handler, messageType, handler instanceof MessageHandler.Partial); } public final void addHandler(MessageHandler handler) { Map, Boolean> types = ClassUtils.getHandlerTypes(handler.getClass()); for (Entry, Boolean> e : types.entrySet()) { Class type = e.getKey(); boolean partial = e.getValue(); addHandlerInternal(handler, type, partial); } } private void addHandlerInternal(MessageHandler handler, Class type, boolean partial) { verify(type, handler); List handlerWrappers = createHandlerWrappers(type, handler, partial); for(HandlerWrapper handlerWrapper : handlerWrappers) { if (handlers.containsKey(handlerWrapper.getFrameType())) { throw JsrWebSocketMessages.MESSAGES.handlerAlreadyRegistered(handlerWrapper.getFrameType()); } else { if (handlers.putIfAbsent(handlerWrapper.getFrameType(), handlerWrapper) != null) { throw JsrWebSocketMessages.MESSAGES.handlerAlreadyRegistered(handlerWrapper.getFrameType()); } } } } /** * Return the {@link FrameType} for the given {@link Class}. * * Note that multiple wrappers can be returned if both text and binary frames can be decoded to the given type */ protected List createHandlerWrappers(Class type, MessageHandler handler, boolean partialHandler) { //check the encodings first Encoding encoding = session.getEncoding(); List ret = new ArrayList<>(2); if (encoding.canDecodeText(type)) { ret.add(new HandlerWrapper(FrameType.TEXT, handler, type, true, false)); } if (encoding.canDecodeBinary(type)) { ret.add(new HandlerWrapper(FrameType.BYTE, handler, type, true, false)); } if(!ret.isEmpty()) { return ret; } if (partialHandler) { // Partial message handler supports only String, byte[] and ByteBuffer. // See JavaDocs of the MessageHandler.Partial interface. if (type == String.class) { return Collections.singletonList(new HandlerWrapper(FrameType.TEXT, handler, type, false, true)); } if (type == byte[].class || type == ByteBuffer.class) { return Collections.singletonList(new HandlerWrapper(FrameType.BYTE, handler, type, false, true)); } throw JsrWebSocketMessages.MESSAGES.unsupportedFrameType(type); } if (type == byte[].class || type == ByteBuffer.class || type == InputStream.class) { return Collections.singletonList(new HandlerWrapper(FrameType.BYTE, handler, type, false, false)); } if (type == String.class || type == Reader.class) { return Collections.singletonList(new HandlerWrapper(FrameType.TEXT, handler, type, false, false)); } if (type == PongMessage.class) { return Collections.singletonList(new HandlerWrapper(FrameType.PONG, handler, type, false, false)); } throw JsrWebSocketMessages.MESSAGES.unsupportedFrameType(type); } /** * Sub-classes may override this to do validations. This method is called before the add operations is executed. */ protected void verify(Class type, MessageHandler handler) { // NOOP } public final void removeHandler(MessageHandler handler) { Map, Boolean> types = ClassUtils.getHandlerTypes(handler.getClass()); for (Entry, Boolean> e : types.entrySet()) { Class type = e.getKey(); List handlerWrappers = createHandlerWrappers(type, handler, e.getValue()); for(HandlerWrapper handlerWrapper : handlerWrappers) { FrameType frameType = handlerWrapper.getFrameType(); HandlerWrapper wrapper = handlers.get(frameType); if (wrapper != null && wrapper.getMessageType() == type) { handlers.remove(frameType, wrapper); } } } } /** * Return a safe copy of all registered {@link MessageHandler}s. */ public final Set getHandlers() { Set msgHandlers = new HashSet<>(); for (HandlerWrapper handler : handlers.values()) { msgHandlers.add(handler.getHandler()); } return msgHandlers; } /** * Return the {@link HandlerWrapper} for the given {@link FrameType} or {@code null} if non was registered for * the given {@link FrameType}. */ protected final HandlerWrapper getHandler(FrameType type) { return handlers.get(type); } @Override protected long getMaxTextBufferSize() { return session.getMaxTextMessageBufferSize(); } protected long getMaxBinaryBufferSize() { return session.getMaxBinaryMessageBufferSize(); } static final class HandlerWrapper { private final FrameType frameType; private final MessageHandler handler; private final Class msgType; private final boolean decodingNeeded; private final boolean partialHandler; private HandlerWrapper(final FrameType frameType, MessageHandler handler, final Class msgType, final boolean decodingNeeded, final boolean partialHandler) { this.frameType = frameType; this.handler = handler; this.msgType = msgType; this.decodingNeeded = decodingNeeded; this.partialHandler = partialHandler; } /** * Return the {@link MessageHandler} which is used. */ public MessageHandler getHandler() { return handler; } /** * Return the {@link Class} of the arguments accepted by the {@link MessageHandler}. */ public Class getMessageType() { return msgType; } FrameType getFrameType() { return frameType; } boolean isDecodingNeeded() { return decodingNeeded; } boolean isPartialHandler() { return partialHandler; } } public Executor getExecutor() { return executor; } UndertowSession getSession() { return session; } Endpoint getEndpoint() { return endpoint; } } JsrWebSocketFilter.java000066400000000000000000000223641420065311100336260ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import static io.undertow.websockets.jsr.ServerWebSocketContainer.WebSocketHandshakeHolder; import java.io.IOException; import java.security.AccessController; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; import javax.websocket.CloseReason; import javax.websocket.server.ServerContainer; import org.xnio.ChannelListener; import org.xnio.StreamConnection; import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpUpgradeListener; import io.undertow.server.session.Session; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.servlet.spec.HttpSessionImpl; import io.undertow.servlet.websockets.ServletWebSocketHttpExchange; import io.undertow.util.Headers; import io.undertow.util.PathTemplateMatcher; import io.undertow.util.StatusCodes; import io.undertow.websockets.WebSocketConnectionCallback; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSockets; import io.undertow.websockets.core.protocol.Handshake; import io.undertow.websockets.jsr.handshake.HandshakeUtil; /** * Filter that provides HTTP upgrade functionality. This should be run after all user filters, but before any servlets. *

    * The use of a filter rather than a servlet allows for normal HTTP requests to be served from the same location * as a web socket endpoint if no upgrade header is found. *

    * * @author Stuart Douglas */ public class JsrWebSocketFilter implements Filter { private WebSocketConnectionCallback callback; private PathTemplateMatcher pathTemplateMatcher; private Set peerConnections; private ServerWebSocketContainer container; private static final String SESSION_ATTRIBUTE = "io.undertow.websocket.current-connections"; @Override public void init(final FilterConfig filterConfig) throws ServletException { peerConnections = Collections.newSetFromMap(new ConcurrentHashMap()); container = (ServerWebSocketContainer) filterConfig.getServletContext().getAttribute(ServerContainer.class.getName()); container.deploymentComplete(); pathTemplateMatcher = new PathTemplateMatcher<>(); WebSocketDeploymentInfo info = (WebSocketDeploymentInfo)filterConfig.getServletContext().getAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME); for (ConfiguredServerEndpoint endpoint : container.getConfiguredServerEndpoints()) { if (info == null || info.getExtensions().isEmpty()) { pathTemplateMatcher.add(endpoint.getPathTemplate(), ServerWebSocketContainer.handshakes(endpoint)); } else { pathTemplateMatcher.add(endpoint.getPathTemplate(), ServerWebSocketContainer.handshakes(endpoint, info.getExtensions())); } } this.callback = new EndpointSessionHandler(container); } @Override public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; if (req.getHeader(Headers.UPGRADE_STRING) != null) { final ServletWebSocketHttpExchange facade = new ServletWebSocketHttpExchange(req, resp, peerConnections); String path; if (req.getPathInfo() == null) { path = req.getServletPath(); } else { path = req.getServletPath() + req.getPathInfo(); } if (!path.startsWith("/")) { path = "/" + path; } PathTemplateMatcher.PathMatchResult matchResult = pathTemplateMatcher.match(path); if (matchResult != null) { Handshake handshaker = null; for (Handshake method : matchResult.getValue().handshakes) { if (method.matches(facade)) { handshaker = method; break; } } if (handshaker != null) { if(container.isClosed()) { resp.sendError(StatusCodes.SERVICE_UNAVAILABLE); return; } facade.putAttachment(HandshakeUtil.PATH_PARAMS, matchResult.getParameters()); facade.putAttachment(HandshakeUtil.PRINCIPAL, req.getUserPrincipal()); final Handshake selected = handshaker; ServletRequestContext src = ServletRequestContext.requireCurrent(); final HttpSessionImpl session = src.getCurrentServletContext().getSession(src.getExchange(), false); facade.upgradeChannel(new HttpUpgradeListener() { @Override public void handleUpgrade(StreamConnection streamConnection, HttpServerExchange exchange) { WebSocketChannel channel = selected.createChannel(facade, streamConnection, facade.getBufferPool()); peerConnections.add(channel); if(session != null) { final Session underlying; if (System.getSecurityManager() == null) { underlying = session.getSession(); } else { underlying = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(session)); } List connections; synchronized (underlying) { connections = (List) underlying.getAttribute(SESSION_ATTRIBUTE); if(connections == null) { underlying.setAttribute(SESSION_ATTRIBUTE, connections = new ArrayList<>()); } connections.add(channel); } final List finalConnections = connections; channel.addCloseTask(new ChannelListener() { @Override public void handleEvent(WebSocketChannel channel) { synchronized (underlying) { finalConnections.remove(channel); } } }); } callback.onConnect(facade, channel); } }); handshaker.handshake(facade); return; } } } chain.doFilter(request, response); } @Override public void destroy() { } public static class LogoutListener implements HttpSessionListener { @Override public void sessionCreated(HttpSessionEvent se) { } @Override public void sessionDestroyed(HttpSessionEvent se) { HttpSessionImpl session = (HttpSessionImpl) se.getSession(); final Session underlying; if (System.getSecurityManager() == null) { underlying = session.getSession(); } else { underlying = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(session)); } List connections = (List) underlying.getAttribute(SESSION_ATTRIBUTE); if(connections != null) { synchronized (underlying) { for(WebSocketChannel c : connections) { WebSockets.sendClose(CloseReason.CloseCodes.VIOLATED_POLICY.getCode(), "", c, null); } } } } } } JsrWebSocketLogger.java000066400000000000000000000075601420065311100336210ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import org.jboss.logging.BasicLogger; import org.jboss.logging.Logger; import org.jboss.logging.annotations.Cause; import org.jboss.logging.annotations.LogMessage; import org.jboss.logging.annotations.Message; import org.jboss.logging.annotations.MessageLogger; import javax.websocket.server.PathParam; import java.lang.reflect.Method; import java.util.Set; /** * log messages start at 26000 * * @author Norman Maurer */ @MessageLogger(projectCode = "UT") public interface JsrWebSocketLogger extends BasicLogger { JsrWebSocketLogger ROOT_LOGGER = Logger.getMessageLogger(JsrWebSocketLogger.class, JsrWebSocketLogger.class.getPackage().getName()); JsrWebSocketLogger REQUEST_LOGGER = Logger.getMessageLogger(JsrWebSocketLogger.class, JsrWebSocketLogger.class.getPackage().getName() + ".request"); @LogMessage(level = Logger.Level.ERROR) @Message(id = 26001, value = "Unable to instantiate endpoint") void endpointCreationFailed(@Cause Exception cause); @LogMessage(level = Logger.Level.ERROR) @Message(id = 26002, value = "Unable to instantiate server configuration %s") void couldNotInitializeConfiguration(Class clazz, @Cause Throwable t); @LogMessage(level = Logger.Level.INFO) @Message(id = 26003, value = "Adding annotated server endpoint %s for path %s") void addingAnnotatedServerEndpoint(Class endpoint, String value); @LogMessage(level = Logger.Level.INFO) @Message(id = 26004, value = "Adding annotated client endpoint %s") void addingAnnotatedClientEndpoint(Class endpoint); @LogMessage(level = Logger.Level.INFO) @Message(id = 26005, value = "Adding programmatic server endpoint %s for path %s") void addingProgramaticEndpoint(Class endpointClass, String path); @LogMessage(level = Logger.Level.ERROR) @Message(id = 26006, value = "Exception running web socket method") void exceptionInWebSocketMethod(@Cause Throwable e); @LogMessage(level = Logger.Level.WARN) @Message(id = 26007, value = "On Endpoint class %s path param %s on method %s does not reference a valid parameter, valid parameters are %s.") void pathTemplateNotFound(Class endpointClass, PathParam param, Method method, Set paths); @LogMessage(level = Logger.Level.ERROR) @Message(id = 26008, value = "Could not close endpoint on undeploy.") void couldNotCloseOnUndeploy(@Cause Exception e); @LogMessage(level = Logger.Level.WARN) @Message(id = 26009, value = "XNIO worker was not set on WebSocketDeploymentInfo, the default worker will be used") void xnioWorkerWasNull(); @LogMessage(level = Logger.Level.WARN) @Message(id = 26010, value = "Buffer pool was not set on WebSocketDeploymentInfo, the default pool will be used") void bufferPoolWasNull(); @Message(id = 26011, value = "XNIO worker was not set on WebSocketDeploymentInfo, and there is no default to use") IllegalArgumentException xnioWorkerWasNullAndNoDefault(); @Message(id = 26012, value = "Buffer pool was not set on WebSocketDeploymentInfo, and there is no default to use") IllegalArgumentException bufferPoolWasNullAndNoDefault(); } JsrWebSocketMessages.java000066400000000000000000000170221420065311100341430ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import io.undertow.util.PathTemplate; import io.undertow.websockets.WebSocketExtension; import org.jboss.logging.Messages; import org.jboss.logging.annotations.Cause; import org.jboss.logging.annotations.Message; import org.jboss.logging.annotations.MessageBundle; import javax.websocket.Decoder; import javax.websocket.DeploymentException; import javax.websocket.Encoder; import java.io.IOException; import java.lang.reflect.Method; import java.util.List; import java.util.Set; /** * start at 3000 * * @author Norman Maurer */ @MessageBundle(projectCode = "UT") public interface JsrWebSocketMessages { JsrWebSocketMessages MESSAGES = Messages.getBundle(JsrWebSocketMessages.class); @Message(id = 3001, value = "PongMessage not supported with MessageHandler.Async") IllegalStateException pongMessageNotSupported(); @Message(id = 3002, value = "SendStream is closed") IOException sendStreamClosed(); @Message(id = 3003, value = "SendWriter is closed") IOException sendWriterClosed(); @Message(id = 3004, value = "Client not supported") DeploymentException clientNotSupported(); @Message(id = 3005, value = "MessageHandler for type %s already registered") IllegalStateException handlerAlreadyRegistered(FrameHandler.FrameType frameType); @Message(id = 3006, value = "Unable to detect FrameType for clazz %s") IllegalStateException unsupportedFrameType(Class clazz); @Message(id = 3007, value = "Unable to detect MessageHandler type for %s") IllegalStateException unknownHandlerType(Class clazz); @Message(id = 3008, value = "Unable to detect Encoder type for %s") IllegalStateException unknownEncoderType(Class clazz); @Message(id = 3009, value = "More than one %s parameter for %s") IllegalArgumentException moreThanOneParameterOfType(Class type, Method method); @Message(id = 3010, value = "No parameter of type %s found in method %s") IllegalArgumentException parameterNotFound(Class type, Method method); @Message(id = 3011, value = "More than one method is annotated with %s") DeploymentException moreThanOneAnnotation(Class clazz); @Message(id = 3012, value = "Method %s has invalid parameters at locations %s") DeploymentException invalidParameters(Method method, Set allParams); @Message(id = 3014, value = "Could not determine decoder type for %s") IllegalArgumentException couldNotDetermineDecoderTypeFor(Class decoderClass); @Message(id = 3015, value = "No decoder accepted message %s") String noDecoderAcceptedMessage(List decoders); @Message(id = 3016, value = "Cannot send in middle of fragmeneted message") IllegalStateException cannotSendInMiddleOfFragmentedMessage(); @Message(id = 3017, value = "Cannot add endpoint after deployment") IllegalStateException cannotAddEndpointAfterDeployment(); @Message(id = 3018, value = "Could not determine type of decode method for class %s") DeploymentException couldNotDetermineTypeOfDecodeMethodForClass(Class decoder, @Cause Exception e); @Message(id = 3019, value = "Could not determine type of encode method for class %s") DeploymentException couldNotDetermineTypeOfEncodeMethodForClass(Class encoder); @Message(id = 3020, value = "%s did not implement known decoder interface") DeploymentException didNotImplementKnownDecoderSubclass(Class decoder); @Message(id = 3021, value = "%s does not have default constructor") DeploymentException classDoesNotHaveDefaultConstructor(Class c, @Cause NoSuchMethodException e); @Message(id = 3023, value = "Multiple endpoints with the same logical mapping %s and %s") DeploymentException multipleEndpointsWithOverlappingPaths(PathTemplate template, PathTemplate existing); @Message(id = 3024, value = "Web socket deployment failed") DeploymentException couldNotDeploy(@Cause Exception e); @Message(id = 3025, value = "Cannot connect until deployment is complete") IllegalStateException cannotConnectUntilDeploymentComplete(); @Message(id = 3026, value = "%s is not a valid client endpoint type") DeploymentException notAValidClientEndpointType(Class type); @Message(id = 3027, value = "Class %s was not annotated with @ClientEndpoint or @ServerEndpoint") DeploymentException classWasNotAnnotated(Class endpoint); @Message(id = 3028, value = "Could not find decoder for type %s on method %s") DeploymentException couldNotFindDecoderForType(Class param, Method method); @Message(id = 3029, value = "Could not find message parameter on method %s") DeploymentException couldNotFindMessageParameter(Method method); @Message(id = 3030, value = "Received a text frame however endpoint does not have a method capable of handling it") RuntimeException receivedTextFrameButNoMethod(); @Message(id = 3031, value = "Received a binary frame however endpoint does not have a method capable of handling it") RuntimeException receivedBinaryFrameButNoMethod(); @Message(id = 3033, value = "Method %s has invalid parameters at locations %s. It looks like you may have accidentally used javax.ws.rs.PathParam instead of javax.websocket.server.PathParam") DeploymentException invalidParametersWithWrongAnnotation(Method method, Set allParams); @Message(id = 3034, value = "Server provided extension %s which was not in client supported extensions %s") IOException extensionWasNotPresentInClientHandshake(String e, List supportedExtensions); @Message(id = 3035, value = "Connection timed out") IOException connectionTimedOut(); @Message(id = 3036, value = "SendHandler is null") IllegalArgumentException handlerIsNull(); @Message(id = 3037, value = "Message is null") IllegalArgumentException messageInNull(); @Message(id = 3038, value = "Message of size %s was larger than the maximum of %s") IllegalArgumentException messageTooLarge(int size, int max); @Message(id = 3039, value = "The container cannot find a suitable constructor to instantiate endpoint of type %s") InstantiationException cannotInstantiateEndpoint(Class c); @Message(id = 3040, value = "Annotated endpoint instance %s was not of correct type %s") IllegalArgumentException endpointNotOfCorrectType(Object instance, Class expected); @Message(id = 3041, value = "Annotated endpoint %s does not have a no arg constructor, but is using a custom configurator. The custom configurator must create the instance.") InstantiationException endpointDoesNotHaveAppropriateConstructor(Class endpoint); @Message(id = 3042, value = "Deployment failed due to invalid programmatically added endpoints") RuntimeException deploymentFailedDueToProgramaticErrors(); } JsrWebSocketProtocolHandshakeHandler.java000066400000000000000000000041161420065311100373020ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import java.util.HashSet; import java.util.Set; import io.undertow.websockets.WebSocketConnectionCallback; import io.undertow.websockets.WebSocketProtocolHandshakeHandler; import io.undertow.websockets.core.protocol.Handshake; import io.undertow.websockets.jsr.handshake.JsrHybi07Handshake; import io.undertow.websockets.jsr.handshake.JsrHybi08Handshake; import io.undertow.websockets.jsr.handshake.JsrHybi13Handshake; /** * {@link WebSocketProtocolHandshakeHandler} implementation which takes care to add the right {@link Handshake} instances * to the mix and so support everything needed as specified in the SPEC. * * @author Norman Maurer */ final class JsrWebSocketProtocolHandshakeHandler extends WebSocketProtocolHandshakeHandler { JsrWebSocketProtocolHandshakeHandler(WebSocketConnectionCallback callback, ConfiguredServerEndpoint... configs) { super(handshakes(configs), callback); } private static Set handshakes(ConfiguredServerEndpoint... configs) { Set handshakes = new HashSet<>(); for (ConfiguredServerEndpoint config : configs) { handshakes.add(new JsrHybi07Handshake(config)); handshakes.add(new JsrHybi08Handshake(config)); handshakes.add(new JsrHybi13Handshake(config)); } return handshakes; } } undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/OrderedExecutor.java000066400000000000000000000061341420065311100332720ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import java.util.Deque; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; /** * Executor that executes tasks in the order they are submitted, using at most one thread at a time. * * @author Stuart Douglas */ public class OrderedExecutor implements Executor { private final Deque tasks = new ConcurrentLinkedDeque<>(); private final Executor delegate; private final ExecutorTask task = new ExecutorTask(); private volatile int state = 0; private static final AtomicIntegerFieldUpdater stateUpdater = AtomicIntegerFieldUpdater.newUpdater(OrderedExecutor.class, "state"); private static final int STATE_NOT_RUNNING = 0; private static final int STATE_RUNNING = 1; public OrderedExecutor(Executor delegate) { this.delegate = delegate; } @Override public void execute(Runnable command) { tasks.add(command); if (stateUpdater.get(this) == STATE_NOT_RUNNING) { delegate.execute(task); } } private final class ExecutorTask implements Runnable { @Override public void run() { do { //if there is no thread active then we run if (stateUpdater.compareAndSet(OrderedExecutor.this, STATE_NOT_RUNNING, STATE_RUNNING)) { Runnable task = tasks.poll(); //while the queue is not empty we process in order while (task != null) { try { task.run(); } catch (Throwable e) { JsrWebSocketLogger.REQUEST_LOGGER.exceptionInWebSocketMethod(e); } task = tasks.poll(); } //set state back to not running. stateUpdater.set(OrderedExecutor.this, STATE_NOT_RUNNING); } else { return; } //we loop again based on tasks not being empty. Otherwise there is a window where the state is running, //but poll() has returned null, so a submitting thread will believe that it does not need re-execute. //this check fixes the issue } while (!tasks.isEmpty()); } } } undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/SecurityActions.java000066400000000000000000000036651420065311100333250ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import java.security.AccessController; import java.security.PrivilegedAction; import javax.websocket.WebSocketContainer; class SecurityActions { static void addContainer(final ClassLoader classLoader, final WebSocketContainer webSocketContainer) { if (System.getSecurityManager() == null) { UndertowContainerProvider.addContainer(classLoader, webSocketContainer); } else { AccessController.doPrivileged(new PrivilegedAction() { @Override public Object run() { UndertowContainerProvider.addContainer(classLoader, webSocketContainer); return null; } }); } } static void removeContainer(final ClassLoader classLoader) { if (System.getSecurityManager() == null) { UndertowContainerProvider.removeContainer(classLoader); } else { AccessController.doPrivileged(new PrivilegedAction() { @Override public Object run() { UndertowContainerProvider.removeContainer(classLoader); return null; } }); } } } SendHandlerAdapter.java000066400000000000000000000034021420065311100335730ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import io.undertow.websockets.core.WebSocketCallback; import io.undertow.websockets.core.WebSocketChannel; import javax.websocket.SendHandler; import javax.websocket.SendResult; /** * {@link WebSocketCallback} implementation which will notify a wrapped {@link SendHandler} once a send operation * completes. * * @author Norman Maurer */ final class SendHandlerAdapter implements WebSocketCallback { private final SendHandler handler; private volatile boolean done; SendHandlerAdapter(SendHandler handler) { this.handler = handler; } @Override public void complete(WebSocketChannel channel, Void context) { if(done) { return; } done = true; handler.onResult(new SendResult()); } @Override public void onError(WebSocketChannel channel, Void context, Throwable throwable) { if(done) { return; } done = true; handler.onResult(new SendResult(throwable)); } } undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/SendResultFuture.java000066400000000000000000000102141420065311100334440ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import io.undertow.websockets.core.WebSocketCallback; import io.undertow.websockets.core.WebSocketChannel; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** * Default implementation of a {@link Future} that is used in the {@link javax.websocket.RemoteEndpoint.Async} * implementation * * @author Norman Maurer */ final class SendResultFuture implements Future, WebSocketCallback { private boolean done; private Throwable exception; private int waiters; @Override public synchronized void complete(WebSocketChannel channel, T context) { if (done) { return; } if (waiters > 0) { notifyAll(); } done = true; } @Override public synchronized void onError(WebSocketChannel channel, T context, Throwable throwable) { if (done) { return; } exception = throwable; done = true; if (waiters > 0) { notifyAll(); } } /** * Returns {@code true} */ @Override public boolean cancel(boolean mayInterruptIfRunning) { return false; } @Override public boolean isCancelled() { return false; } @Override public synchronized boolean isDone() { return done; } @Override public Void get() throws InterruptedException, ExecutionException { if (Thread.interrupted()) { throw new InterruptedException(); } synchronized (this) { while (!done) { waiters++; try { wait(); } finally { waiters--; } } } return handleResult(); } @Override public Void get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { if (Thread.interrupted()) { throw new InterruptedException(); } long timeoutNanos = unit.toNanos(timeout); long startTime = timeoutNanos <= 0 ? 0 : System.nanoTime(); long waitTime = timeoutNanos; synchronized (this) { if (done) { return handleResult(); } if (waitTime <= 0) { throw new TimeoutException(); } waiters++; try { for (; ; ) { wait(waitTime / 1000000, (int) (waitTime % 1000000)); if (done) { return handleResult(); } else { waitTime = timeoutNanos - (System.nanoTime() - startTime); if (waitTime <= 0) { if (done) { return handleResult(); } if (waitTime <= 0) { throw new TimeoutException(); } } } } } finally { waiters--; } } } private Void handleResult() throws ExecutionException { if (exception != null) { throw new ExecutionException(exception); } return null; } } ServerEndpointConfigImpl.java000066400000000000000000000043211420065311100350230ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import javax.websocket.Decoder; import javax.websocket.Encoder; import javax.websocket.Extension; import javax.websocket.server.ServerEndpointConfig; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * @author Stuart Douglas */ public class ServerEndpointConfigImpl implements ServerEndpointConfig { private final Class endpointclass; private final String path; private final Map userProperties = new ConcurrentHashMap<>(); public ServerEndpointConfigImpl(Class endpointclass, String path) { this.endpointclass = endpointclass; this.path = path; } @Override public Class getEndpointClass() { return endpointclass; } @Override public String getPath() { return path; } @Override public List getSubprotocols() { return Collections.emptyList(); } @Override public List getExtensions() { return Collections.emptyList(); } @Override public Configurator getConfigurator() { return new Configurator(); } @Override public List> getEncoders() { return Collections.emptyList(); } @Override public List> getDecoders() { return Collections.emptyList(); } @Override public Map getUserProperties() { return userProperties; } } ServerWebSocketContainer.java000066400000000000000000001352451420065311100350360ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpUpgradeListener; import io.undertow.servlet.api.ClassIntrospecter; import io.undertow.servlet.api.InstanceFactory; import io.undertow.servlet.api.InstanceHandle; import io.undertow.servlet.api.ThreadSetupHandler; import io.undertow.servlet.spec.ServletContextImpl; import io.undertow.servlet.util.ConstructorInstanceFactory; import io.undertow.servlet.util.ImmediateInstanceHandle; import io.undertow.servlet.websockets.ServletWebSocketHttpExchange; import io.undertow.util.CopyOnWriteMap; import io.undertow.util.PathTemplate; import io.undertow.util.StatusCodes; import io.undertow.websockets.WebSocketExtension; import io.undertow.websockets.client.WebSocketClient; import io.undertow.websockets.client.WebSocketClientNegotiation; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.protocol.Handshake; import io.undertow.websockets.extensions.ExtensionHandshake; import io.undertow.websockets.jsr.annotated.AnnotatedEndpointFactory; import io.undertow.websockets.jsr.handshake.HandshakeUtil; import io.undertow.websockets.jsr.handshake.JsrHybi07Handshake; import io.undertow.websockets.jsr.handshake.JsrHybi08Handshake; import io.undertow.websockets.jsr.handshake.JsrHybi13Handshake; import org.xnio.IoFuture; import org.xnio.IoUtils; import io.undertow.connector.ByteBufferPool; import org.xnio.OptionMap; import org.xnio.StreamConnection; import org.xnio.XnioWorker; import org.xnio.http.UpgradeFailedException; import org.xnio.ssl.XnioSsl; import javax.net.ssl.SSLContext; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.websocket.ClientEndpoint; import javax.websocket.ClientEndpointConfig; import javax.websocket.CloseReason; import javax.websocket.DeploymentException; import javax.websocket.Endpoint; import javax.websocket.Extension; import javax.websocket.HandshakeResponse; import javax.websocket.Session; import javax.websocket.server.ServerContainer; import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpointConfig; import java.io.Closeable; import java.io.IOException; import java.net.InetSocketAddress; import java.net.URI; import java.nio.channels.ClosedChannelException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.ServiceLoader; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import static java.lang.System.*; /** * {@link ServerContainer} implementation which allows to deploy endpoints for a server. * * @author Norman Maurer */ public class ServerWebSocketContainer implements ServerContainer, Closeable { public static final String TIMEOUT = "io.undertow.websocket.CONNECT_TIMEOUT"; public static final int DEFAULT_WEB_SOCKET_TIMEOUT_SECONDS = 10; private final ClassIntrospecter classIntrospecter; private final Map, ConfiguredClientEndpoint> clientEndpoints = new CopyOnWriteMap<>(); private final List configuredServerEndpoints = new ArrayList<>(); private final Set> annotatedEndpointClasses = new HashSet<>(); /** * set of all deployed server endpoint paths. Due to the comparison function we can detect * overlaps */ private final TreeSet seenPaths = new TreeSet<>(); private final Supplier xnioWorker; private final ByteBufferPool bufferPool; private final boolean dispatchToWorker; private final InetSocketAddress clientBindAddress; private final WebSocketReconnectHandler webSocketReconnectHandler; private volatile long defaultAsyncSendTimeout; private volatile long defaultMaxSessionIdleTimeout; private volatile int defaultMaxBinaryMessageBufferSize; private volatile int defaultMaxTextMessageBufferSize; private volatile boolean deploymentComplete = false; private final List deploymentExceptions = new ArrayList<>(); private ServletContextImpl contextToAddFilter = null; private final List clientSslProviders; private final List pauseListeners = new ArrayList<>(); private final List installedExtensions; private final ThreadSetupHandler.Action invokeEndpointTask; private volatile boolean closed = false; public ServerWebSocketContainer(final ClassIntrospecter classIntrospecter, final Supplier xnioWorker, ByteBufferPool bufferPool, List threadSetupHandlers, boolean dispatchToWorker, boolean clientMode) { this(classIntrospecter, ServerWebSocketContainer.class.getClassLoader(), xnioWorker, bufferPool, threadSetupHandlers, dispatchToWorker, null, null); } public ServerWebSocketContainer(final ClassIntrospecter classIntrospecter, final ClassLoader classLoader, Supplier xnioWorker, ByteBufferPool bufferPool, List threadSetupHandlers, boolean dispatchToWorker) { this(classIntrospecter, classLoader, xnioWorker, bufferPool, threadSetupHandlers, dispatchToWorker, null, null); } public ServerWebSocketContainer(final ClassIntrospecter classIntrospecter, final ClassLoader classLoader, Supplier xnioWorker, ByteBufferPool bufferPool, List threadSetupHandlers, boolean dispatchToWorker, InetSocketAddress clientBindAddress, WebSocketReconnectHandler reconnectHandler) { this(classIntrospecter, classLoader, xnioWorker, bufferPool, threadSetupHandlers, dispatchToWorker, clientBindAddress, reconnectHandler, Collections.emptyList()); } public ServerWebSocketContainer(final ClassIntrospecter classIntrospecter, final ClassLoader classLoader, Supplier xnioWorker, ByteBufferPool bufferPool, List threadSetupHandlers, boolean dispatchToWorker, InetSocketAddress clientBindAddress, WebSocketReconnectHandler reconnectHandler, List installedExtensions) { this.classIntrospecter = classIntrospecter; this.bufferPool = bufferPool; this.xnioWorker = xnioWorker; this.dispatchToWorker = dispatchToWorker; this.clientBindAddress = clientBindAddress; this.installedExtensions = new ArrayList<>(installedExtensions); List clientSslProviders = new ArrayList<>(); for (WebsocketClientSslProvider provider : ServiceLoader.load(WebsocketClientSslProvider.class, classLoader)) { clientSslProviders.add(provider); } this.clientSslProviders = Collections.unmodifiableList(clientSslProviders); this.webSocketReconnectHandler = reconnectHandler; ThreadSetupHandler.Action task = new ThreadSetupHandler.Action() { @Override public Void call(HttpServerExchange exchange, Runnable context) throws Exception { context.run(); return null; } }; for(ThreadSetupHandler handler : threadSetupHandlers) { task = handler.create(task); } this.invokeEndpointTask = task; } @Override public long getDefaultAsyncSendTimeout() { return defaultAsyncSendTimeout; } @Override public void setAsyncSendTimeout(long defaultAsyncSendTimeout) { this.defaultAsyncSendTimeout = defaultAsyncSendTimeout; } public Session connectToServer(final Object annotatedEndpointInstance, WebSocketClient.ConnectionBuilder connectionBuilder) throws DeploymentException, IOException { if(closed) { throw new ClosedChannelException(); } ConfiguredClientEndpoint config = getClientEndpoint(annotatedEndpointInstance.getClass(), false); if (config == null) { throw JsrWebSocketMessages.MESSAGES.notAValidClientEndpointType(annotatedEndpointInstance.getClass()); } Endpoint instance = config.getFactory().createInstance(new ImmediateInstanceHandle<>(annotatedEndpointInstance)); return connectToServerInternal(instance, config, connectionBuilder); } @Override public Session connectToServer(final Object annotatedEndpointInstance, final URI path) throws DeploymentException, IOException { if(closed) { throw new ClosedChannelException(); } ConfiguredClientEndpoint config = getClientEndpoint(annotatedEndpointInstance.getClass(), false); if (config == null) { throw JsrWebSocketMessages.MESSAGES.notAValidClientEndpointType(annotatedEndpointInstance.getClass()); } Endpoint instance = config.getFactory().createInstance(new ImmediateInstanceHandle<>(annotatedEndpointInstance)); XnioSsl ssl = null; for (WebsocketClientSslProvider provider : clientSslProviders) { ssl = provider.getSsl(xnioWorker.get(), annotatedEndpointInstance, path); if (ssl != null) { break; } } if(ssl == null) { try { ssl = new UndertowXnioSsl(xnioWorker.get().getXnio(), OptionMap.EMPTY, SSLContext.getDefault()); } catch (NoSuchAlgorithmException e) { //ignore } } return connectToServerInternal(instance, ssl, config, path); } public Session connectToServer(Class aClass, WebSocketClient.ConnectionBuilder connectionBuilder) throws DeploymentException, IOException { if(closed) { throw new ClosedChannelException(); } ConfiguredClientEndpoint config = getClientEndpoint(aClass, true); if (config == null) { throw JsrWebSocketMessages.MESSAGES.notAValidClientEndpointType(aClass); } try { AnnotatedEndpointFactory factory = config.getFactory(); InstanceHandle instance = config.getInstanceFactory().createInstance(); return connectToServerInternal(factory.createInstance(instance), config, connectionBuilder); } catch (InstantiationException e) { throw new RuntimeException(e); } } @Override public Session connectToServer(Class aClass, URI uri) throws DeploymentException, IOException { if(closed) { throw new ClosedChannelException(); } ConfiguredClientEndpoint config = getClientEndpoint(aClass, true); if (config == null) { throw JsrWebSocketMessages.MESSAGES.notAValidClientEndpointType(aClass); } try { AnnotatedEndpointFactory factory = config.getFactory(); InstanceHandle instance = config.getInstanceFactory().createInstance(); XnioSsl ssl = null; for (WebsocketClientSslProvider provider : clientSslProviders) { ssl = provider.getSsl(xnioWorker.get(), aClass, uri); if (ssl != null) { break; } } if(ssl == null) { try { ssl = new UndertowXnioSsl(xnioWorker.get().getXnio(), OptionMap.EMPTY, SSLContext.getDefault()); } catch (NoSuchAlgorithmException e) { //ignore } } return connectToServerInternal(factory.createInstance(instance), ssl, config, uri); } catch (InstantiationException e) { throw new RuntimeException(e); } } @Override public Session connectToServer(final Endpoint endpointInstance, final ClientEndpointConfig config, final URI path) throws DeploymentException, IOException { if(closed) { throw new ClosedChannelException(); } ClientEndpointConfig cec = config != null ? config : ClientEndpointConfig.Builder.create().build(); XnioSsl ssl = null; for (WebsocketClientSslProvider provider : clientSslProviders) { ssl = provider.getSsl(xnioWorker.get(), endpointInstance, cec, path); if (ssl != null) { break; } } if(ssl == null) { try { ssl = new UndertowXnioSsl(xnioWorker.get().getXnio(), OptionMap.EMPTY, SSLContext.getDefault()); } catch (NoSuchAlgorithmException e) { //ignore } } //in theory we should not be able to connect until the deployment is complete, but the definition of when a deployment is complete is a bit nebulous. WebSocketClientNegotiation clientNegotiation = new ClientNegotiation(cec.getPreferredSubprotocols(), toExtensionList(cec.getExtensions()), cec); WebSocketClient.ConnectionBuilder connectionBuilder = WebSocketClient.connectionBuilder(xnioWorker.get(), bufferPool, path) .setSsl(ssl) .setBindAddress(clientBindAddress) .setClientNegotiation(clientNegotiation); return connectToServer(endpointInstance, config, connectionBuilder); } public Session connectToServer(final Endpoint endpointInstance, final ClientEndpointConfig config, WebSocketClient.ConnectionBuilder connectionBuilder) throws DeploymentException, IOException { if(closed) { throw new ClosedChannelException(); } ClientEndpointConfig cec = config != null ? config : ClientEndpointConfig.Builder.create().build(); WebSocketClientNegotiation clientNegotiation = connectionBuilder.getClientNegotiation(); IoFuture session = connectionBuilder .connect(); Number timeout = (Number) cec.getUserProperties().get(TIMEOUT); if(session.await(timeout == null ? DEFAULT_WEB_SOCKET_TIMEOUT_SECONDS: timeout.intValue(), TimeUnit.SECONDS) == IoFuture.Status.WAITING) { //add a notifier to close the channel if the connection actually completes session.cancel(); session.addNotifier(new IoFuture.HandlingNotifier() { @Override public void handleDone(WebSocketChannel data, Object attachment) { IoUtils.safeClose(data); } }, null); throw JsrWebSocketMessages.MESSAGES.connectionTimedOut(); } WebSocketChannel channel; try { channel = session.get(); } catch (UpgradeFailedException e) { throw new DeploymentException(e.getMessage(), e); } EndpointSessionHandler sessionHandler = new EndpointSessionHandler(this); final List extensions = new ArrayList<>(); final Map extMap = new HashMap<>(); for (Extension ext : cec.getExtensions()) { extMap.put(ext.getName(), ext); } for (WebSocketExtension e : clientNegotiation.getSelectedExtensions()) { Extension ext = extMap.get(e.getName()); if (ext == null) { throw JsrWebSocketMessages.MESSAGES.extensionWasNotPresentInClientHandshake(e.getName(), clientNegotiation.getSupportedExtensions()); } extensions.add(ExtensionImpl.create(e)); } ConfiguredClientEndpoint configured = clientEndpoints.get(endpointInstance.getClass()); Endpoint instance = endpointInstance; if(configured == null) { synchronized (clientEndpoints) { // make sure to create an instance of AnnotatedEndpoint if we have the annotation configured = getClientEndpoint(endpointInstance.getClass(), false); if(configured == null) { // if we don't, add an endpoint anyway to the list of clientEndpoints clientEndpoints.put(endpointInstance.getClass(), configured = new ConfiguredClientEndpoint()); } else { // use the factory in configured to reach the endpoint instance = configured.getFactory().createInstance(new ImmediateInstanceHandle<>(endpointInstance)); } } } EncodingFactory encodingFactory = EncodingFactory.createFactory(classIntrospecter, cec.getDecoders(), cec.getEncoders()); UndertowSession undertowSession = new UndertowSession(channel, connectionBuilder.getUri(), Collections.emptyMap(), Collections.>emptyMap(), sessionHandler, null, new ImmediateInstanceHandle<>(endpointInstance), cec, connectionBuilder.getUri().getQuery(), encodingFactory.createEncoding(cec), configured, clientNegotiation.getSelectedSubProtocol(), extensions, connectionBuilder); instance.onOpen(undertowSession, cec); channel.resumeReceives(); return undertowSession; } @Override public Session connectToServer(final Class endpointClass, final ClientEndpointConfig cec, final URI path) throws DeploymentException, IOException { if(closed) { throw new ClosedChannelException(); } try { Endpoint endpoint = classIntrospecter.createInstanceFactory(endpointClass).createInstance().getInstance(); return connectToServer(endpoint, cec, path); } catch (InstantiationException | NoSuchMethodException e) { throw new RuntimeException(e); } } public void doUpgrade(HttpServletRequest request, HttpServletResponse response, final ServerEndpointConfig sec, Map pathParams) throws ServletException, IOException { ServerEndpointConfig.Configurator configurator = sec.getConfigurator(); try { EncodingFactory encodingFactory = EncodingFactory.createFactory(classIntrospecter, sec.getDecoders(), sec.getEncoders()); PathTemplate pt = PathTemplate.create(sec.getPath()); InstanceFactory instanceFactory = null; try { instanceFactory = classIntrospecter.createInstanceFactory(sec.getEndpointClass()); } catch (Exception e) { //so it is possible that this is still valid if a custom configurator is in use if (configurator == null || configurator.getClass() == ServerEndpointConfig.Configurator.class) { throw JsrWebSocketMessages.MESSAGES.couldNotDeploy(e); } else { instanceFactory = new InstanceFactory() { @Override public InstanceHandle createInstance() throws InstantiationException { throw JsrWebSocketMessages.MESSAGES.endpointDoesNotHaveAppropriateConstructor(sec.getEndpointClass()); } }; } } if (configurator == null) { configurator = DefaultContainerConfigurator.INSTANCE; } ServerEndpointConfig config = ServerEndpointConfig.Builder.create(sec.getEndpointClass(), sec.getPath()) .decoders(sec.getDecoders()) .encoders(sec.getEncoders()) .subprotocols(sec.getSubprotocols()) .extensions(sec.getExtensions()) .configurator(configurator) .build(); AnnotatedEndpointFactory annotatedEndpointFactory = null; if(!Endpoint.class.isAssignableFrom(sec.getEndpointClass())) { annotatedEndpointFactory = AnnotatedEndpointFactory.create(sec.getEndpointClass(), encodingFactory, pt.getParameterNames()); } ConfiguredServerEndpoint confguredServerEndpoint; if(annotatedEndpointFactory == null) { confguredServerEndpoint = new ConfiguredServerEndpoint(config, instanceFactory, null, encodingFactory); } else { confguredServerEndpoint = new ConfiguredServerEndpoint(config, instanceFactory, null, encodingFactory, annotatedEndpointFactory, installedExtensions); } WebSocketHandshakeHolder hand; WebSocketDeploymentInfo info = (WebSocketDeploymentInfo)request.getServletContext().getAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME); if (info == null || info.getExtensions() == null) { hand = ServerWebSocketContainer.handshakes(confguredServerEndpoint); } else { hand = ServerWebSocketContainer.handshakes(confguredServerEndpoint, info.getExtensions()); } final ServletWebSocketHttpExchange facade = new ServletWebSocketHttpExchange(request, response, new HashSet()); Handshake handshaker = null; for (Handshake method : hand.handshakes) { if (method.matches(facade)) { handshaker = method; break; } } if (handshaker != null) { if(isClosed()) { response.sendError(StatusCodes.SERVICE_UNAVAILABLE); return; } facade.putAttachment(HandshakeUtil.PATH_PARAMS, pathParams); final Handshake selected = handshaker; facade.upgradeChannel(new HttpUpgradeListener() { @Override public void handleUpgrade(StreamConnection streamConnection, HttpServerExchange exchange) { WebSocketChannel channel = selected.createChannel(facade, streamConnection, facade.getBufferPool()); new EndpointSessionHandler(ServerWebSocketContainer.this).onConnect(facade, channel); } }); handshaker.handshake(facade); return; } } catch (Exception e) { throw new ServletException(e); } } private Session connectToServerInternal(final Endpoint endpointInstance, XnioSsl ssl, final ConfiguredClientEndpoint cec, final URI path) throws DeploymentException, IOException { //in theory we should not be able to connect until the deployment is complete, but the definition of when a deployment is complete is a bit nebulous. WebSocketClientNegotiation clientNegotiation = new ClientNegotiation(cec.getConfig().getPreferredSubprotocols(), toExtensionList(cec.getConfig().getExtensions()), cec.getConfig()); WebSocketClient.ConnectionBuilder connectionBuilder = WebSocketClient.connectionBuilder(xnioWorker.get(), bufferPool, path) .setSsl(ssl) .setBindAddress(clientBindAddress) .setClientNegotiation(clientNegotiation); return connectToServerInternal(endpointInstance, cec, connectionBuilder); } private Session connectToServerInternal(final Endpoint endpointInstance, final ConfiguredClientEndpoint cec, WebSocketClient.ConnectionBuilder connectionBuilder) throws DeploymentException, IOException { IoFuture session = connectionBuilder .connect(); Number timeout = (Number) cec.getConfig().getUserProperties().get(TIMEOUT); IoFuture.Status result = session.await(timeout == null ? DEFAULT_WEB_SOCKET_TIMEOUT_SECONDS : timeout.intValue(), TimeUnit.SECONDS); if(result == IoFuture.Status.WAITING) { //add a notifier to close the channel if the connection actually completes session.cancel(); session.addNotifier(new IoFuture.HandlingNotifier() { @Override public void handleDone(WebSocketChannel data, Object attachment) { IoUtils.safeClose(data); } }, null); throw JsrWebSocketMessages.MESSAGES.connectionTimedOut(); } WebSocketChannel channel; try { channel = session.get(); } catch (UpgradeFailedException e) { throw new DeploymentException(e.getMessage(), e); } EndpointSessionHandler sessionHandler = new EndpointSessionHandler(this); final List extensions = new ArrayList<>(); final Map extMap = new HashMap<>(); for (Extension ext : cec.getConfig().getExtensions()) { extMap.put(ext.getName(), ext); } String subProtocol = null; if(connectionBuilder.getClientNegotiation() != null) { for (WebSocketExtension e : connectionBuilder.getClientNegotiation().getSelectedExtensions()) { Extension ext = extMap.get(e.getName()); if (ext == null) { throw JsrWebSocketMessages.MESSAGES.extensionWasNotPresentInClientHandshake(e.getName(), connectionBuilder.getClientNegotiation().getSupportedExtensions()); } extensions.add(ExtensionImpl.create(e)); } subProtocol = connectionBuilder.getClientNegotiation().getSelectedSubProtocol(); } UndertowSession undertowSession = new UndertowSession(channel, connectionBuilder.getUri(), Collections.emptyMap(), Collections.>emptyMap(), sessionHandler, null, new ImmediateInstanceHandle<>(endpointInstance), cec.getConfig(), connectionBuilder.getUri().getQuery(), cec.getEncodingFactory().createEncoding(cec.getConfig()), cec, subProtocol, extensions, connectionBuilder); endpointInstance.onOpen(undertowSession, cec.getConfig()); channel.resumeReceives(); return undertowSession; } @Override public long getDefaultMaxSessionIdleTimeout() { return defaultMaxSessionIdleTimeout; } @Override public void setDefaultMaxSessionIdleTimeout(final long timeout) { this.defaultMaxSessionIdleTimeout = timeout; } @Override public int getDefaultMaxBinaryMessageBufferSize() { return defaultMaxBinaryMessageBufferSize; } @Override public void setDefaultMaxBinaryMessageBufferSize(int defaultMaxBinaryMessageBufferSize) { this.defaultMaxBinaryMessageBufferSize = defaultMaxBinaryMessageBufferSize; } @Override public int getDefaultMaxTextMessageBufferSize() { return defaultMaxTextMessageBufferSize; } @Override public void setDefaultMaxTextMessageBufferSize(int defaultMaxTextMessageBufferSize) { this.defaultMaxTextMessageBufferSize = defaultMaxTextMessageBufferSize; } @Override public Set getInstalledExtensions() { return new HashSet<>(installedExtensions); } /** * Runs a web socket invocation, setting up the threads and dispatching a thread pool *

    * Unfortunately we need to dispatch to a thread pool, because there is a good chance that the endpoint * will use blocking IO methods. We suspend recieves while this is in progress, to make sure that we do not have multiple * methods invoked at once. *

    * * @param invocation The task to run */ public void invokeEndpointMethod(final Executor executor, final Runnable invocation) { if (dispatchToWorker) { executor.execute(new Runnable() { @Override public void run() { invokeEndpointMethod(invocation); } }); } else { invokeEndpointMethod(invocation); } } /** * Directly invokes an endpoint method, without dispatching to an executor * @param invocation The invocation */ public void invokeEndpointMethod(final Runnable invocation) { try { invokeEndpointTask.call(null, invocation); } catch (Exception e) { throw new RuntimeException(e); } } @Override public void addEndpoint(final Class endpoint) throws DeploymentException { if (deploymentComplete) { throw JsrWebSocketMessages.MESSAGES.cannotAddEndpointAfterDeployment(); } //work around a TCK7 problem //if the class has already been added we just ignore it if(annotatedEndpointClasses.contains(endpoint)) { return; } annotatedEndpointClasses.add(endpoint); try { addEndpointInternal(endpoint, true); } catch (DeploymentException e) { deploymentExceptions.add(e); throw e; } } private synchronized void addEndpointInternal(final Class endpoint, boolean requiresCreation) throws DeploymentException { ServerEndpoint serverEndpoint = endpoint.getAnnotation(ServerEndpoint.class); ClientEndpoint clientEndpoint = endpoint.getAnnotation(ClientEndpoint.class); if (serverEndpoint != null) { JsrWebSocketLogger.ROOT_LOGGER.addingAnnotatedServerEndpoint(endpoint, serverEndpoint.value()); final PathTemplate template = PathTemplate.create(serverEndpoint.value()); if (seenPaths.contains(template)) { PathTemplate existing = null; for (PathTemplate p : seenPaths) { if (p.compareTo(template) == 0) { existing = p; break; } } throw JsrWebSocketMessages.MESSAGES.multipleEndpointsWithOverlappingPaths(template, existing); } seenPaths.add(template); Class configuratorClass = serverEndpoint.configurator(); EncodingFactory encodingFactory = EncodingFactory.createFactory(classIntrospecter, serverEndpoint.decoders(), serverEndpoint.encoders()); AnnotatedEndpointFactory annotatedEndpointFactory = AnnotatedEndpointFactory.create(endpoint, encodingFactory, template.getParameterNames()); InstanceFactory instanceFactory = null; try { instanceFactory = classIntrospecter.createInstanceFactory(endpoint); } catch (Exception e) { //so it is possible that this is still valid if a custom configurator is in use if(configuratorClass == ServerEndpointConfig.Configurator.class) { throw JsrWebSocketMessages.MESSAGES.couldNotDeploy(e); } else { instanceFactory = new InstanceFactory() { @Override public InstanceHandle createInstance() throws InstantiationException { throw JsrWebSocketMessages.MESSAGES.endpointDoesNotHaveAppropriateConstructor(endpoint); } }; } } ServerEndpointConfig.Configurator configurator; if (configuratorClass != ServerEndpointConfig.Configurator.class) { try { configurator = classIntrospecter.createInstanceFactory(configuratorClass).createInstance().getInstance(); } catch (InstantiationException | NoSuchMethodException e) { throw JsrWebSocketMessages.MESSAGES.couldNotDeploy(e); } } else { configurator = DefaultContainerConfigurator.INSTANCE; } ServerEndpointConfig config = ServerEndpointConfig.Builder.create(endpoint, serverEndpoint.value()) .decoders(Arrays.asList(serverEndpoint.decoders())) .encoders(Arrays.asList(serverEndpoint.encoders())) .subprotocols(Arrays.asList(serverEndpoint.subprotocols())) .extensions(Collections.emptyList()) .configurator(configurator) .build(); ConfiguredServerEndpoint confguredServerEndpoint = new ConfiguredServerEndpoint(config, instanceFactory, template, encodingFactory, annotatedEndpointFactory, installedExtensions); configuredServerEndpoints.add(confguredServerEndpoint); handleAddingFilterMapping(); } else if (clientEndpoint != null) { JsrWebSocketLogger.ROOT_LOGGER.addingAnnotatedClientEndpoint(endpoint); EncodingFactory encodingFactory = EncodingFactory.createFactory(classIntrospecter, clientEndpoint.decoders(), clientEndpoint.encoders()); InstanceFactory instanceFactory; try { instanceFactory = classIntrospecter.createInstanceFactory(endpoint); } catch (Exception e) { try { instanceFactory = new ConstructorInstanceFactory<>(endpoint.getConstructor()); //this endpoint cannot be created by the container, the user will instantiate it } catch (NoSuchMethodException e1) { if(requiresCreation) { throw JsrWebSocketMessages.MESSAGES.couldNotDeploy(e); } else { instanceFactory = new InstanceFactory() { @Override public InstanceHandle createInstance() throws InstantiationException { throw new InstantiationException(); } }; } } } AnnotatedEndpointFactory factory = AnnotatedEndpointFactory.create(endpoint, encodingFactory, Collections.emptySet()); ClientEndpointConfig.Configurator configurator = null; try { configurator = classIntrospecter.createInstanceFactory(clientEndpoint.configurator()).createInstance().getInstance(); } catch (InstantiationException | NoSuchMethodException e) { throw JsrWebSocketMessages.MESSAGES.couldNotDeploy(e); } ClientEndpointConfig config = ClientEndpointConfig.Builder.create() .decoders(Arrays.asList(clientEndpoint.decoders())) .encoders(Arrays.asList(clientEndpoint.encoders())) .preferredSubprotocols(Arrays.asList(clientEndpoint.subprotocols())) .configurator(configurator) .build(); ConfiguredClientEndpoint configuredClientEndpoint = new ConfiguredClientEndpoint(config, factory, encodingFactory, instanceFactory); clientEndpoints.put(endpoint, configuredClientEndpoint); } else { throw JsrWebSocketMessages.MESSAGES.classWasNotAnnotated(endpoint); } } private void handleAddingFilterMapping() { if (contextToAddFilter != null) { contextToAddFilter.getDeployment().getDeploymentInfo().addFilterUrlMapping(Bootstrap.FILTER_NAME, "/*", DispatcherType.REQUEST); contextToAddFilter.getDeployment().getServletPaths().invalidate(); contextToAddFilter = null; } } @Override public void addEndpoint(final ServerEndpointConfig endpoint) throws DeploymentException { if (deploymentComplete) { throw JsrWebSocketMessages.MESSAGES.cannotAddEndpointAfterDeployment(); } JsrWebSocketLogger.ROOT_LOGGER.addingProgramaticEndpoint(endpoint.getEndpointClass(), endpoint.getPath()); final PathTemplate template = PathTemplate.create(endpoint.getPath()); if (seenPaths.contains(template)) { PathTemplate existing = null; for (PathTemplate p : seenPaths) { if (p.compareTo(template) == 0) { existing = p; break; } } throw JsrWebSocketMessages.MESSAGES.multipleEndpointsWithOverlappingPaths(template, existing); } seenPaths.add(template); EncodingFactory encodingFactory = EncodingFactory.createFactory(classIntrospecter, endpoint.getDecoders(), endpoint.getEncoders()); AnnotatedEndpointFactory annotatedEndpointFactory = null; if(!Endpoint.class.isAssignableFrom(endpoint.getEndpointClass())) { // We may want to check that the path in @ServerEndpoint matches the specified path, and throw if they are not equivalent annotatedEndpointFactory = AnnotatedEndpointFactory.create(endpoint.getEndpointClass(), encodingFactory, template.getParameterNames()); } ConfiguredServerEndpoint confguredServerEndpoint = new ConfiguredServerEndpoint(endpoint, null, template, encodingFactory, annotatedEndpointFactory, endpoint.getExtensions()); configuredServerEndpoints.add(confguredServerEndpoint); handleAddingFilterMapping(); } private ConfiguredClientEndpoint getClientEndpoint(final Class endpointType, boolean requiresCreation) { Class type = endpointType; while (type != Object.class && type != null && !type.isAnnotationPresent(ClientEndpoint.class)) { type = type.getSuperclass(); } if(type == Object.class || type == null) { return null; } ConfiguredClientEndpoint existing = clientEndpoints.get(type); if (existing != null) { return existing; } synchronized (this) { existing = clientEndpoints.get(type); if (existing != null) { return existing; } if (type.isAnnotationPresent(ClientEndpoint.class)) { try { addEndpointInternal(type, requiresCreation); return clientEndpoints.get(type); } catch (DeploymentException e) { throw new RuntimeException(e); } } return null; } } public void validateDeployment() { if(!deploymentExceptions.isEmpty()) { RuntimeException e = JsrWebSocketMessages.MESSAGES.deploymentFailedDueToProgramaticErrors(); for(DeploymentException ex : deploymentExceptions) { e.addSuppressed(ex); } throw e; } } public void deploymentComplete() { deploymentComplete = true; validateDeployment(); } public List getConfiguredServerEndpoints() { return configuredServerEndpoints; } public ServletContextImpl getContextToAddFilter() { return contextToAddFilter; } public void setContextToAddFilter(ServletContextImpl contextToAddFilter) { this.contextToAddFilter = contextToAddFilter; } public synchronized void close(int waitTime) { doClose(); //wait for them to close long end = currentTimeMillis() + waitTime; for (ConfiguredServerEndpoint endpoint : configuredServerEndpoints) { endpoint.awaitClose(end - System.currentTimeMillis()); } } @Override public synchronized void close() { close(10000); } public ByteBufferPool getBufferPool() { return bufferPool; } public XnioWorker getXnioWorker() { return xnioWorker.get(); } private static List toExtensionList(final List extensions) { List ret = new ArrayList<>(); for (Extension e : extensions) { final List parameters = new ArrayList<>(); for (Extension.Parameter p : e.getParameters()) { parameters.add(new WebSocketExtension.Parameter(p.getName(), p.getValue())); } ret.add(new WebSocketExtension(e.getName(), parameters)); } return ret; } private static class ClientNegotiation extends WebSocketClientNegotiation { private final ClientEndpointConfig config; ClientNegotiation(List supportedSubProtocols, List supportedExtensions, ClientEndpointConfig config) { super(supportedSubProtocols, supportedExtensions); this.config = config; } @Override public void afterRequest(final Map> headers) { ClientEndpointConfig.Configurator configurator = config.getConfigurator(); if (configurator != null) { final Map> newHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); for (Map.Entry> entry : headers.entrySet()) { ArrayList arrayList = new ArrayList<>(); arrayList.addAll(entry.getValue()); newHeaders.put(entry.getKey(), arrayList); } configurator.afterResponse(new HandshakeResponse() { @Override public Map> getHeaders() { return newHeaders; } }); } } @Override public void beforeRequest(Map> headers) { ClientEndpointConfig.Configurator configurator = config.getConfigurator(); if (configurator != null) { final Map> newHeaders = new HashMap<>(); for (Map.Entry> entry : headers.entrySet()) { ArrayList arrayList = new ArrayList<>(); arrayList.addAll(entry.getValue()); newHeaders.put(entry.getKey(), arrayList); } configurator.beforeRequest(newHeaders); headers.clear(); //TODO: more efficient way for (Map.Entry> entry : newHeaders.entrySet()) { if (!entry.getValue().isEmpty()) { headers.put(entry.getKey(), entry.getValue()); } } } } } /** * Pauses the container * @param listener */ public synchronized void pause(PauseListener listener) { closed = true; if(configuredServerEndpoints.isEmpty()) { listener.paused(); return; } if(listener != null) { pauseListeners.add(listener); } for (ConfiguredServerEndpoint endpoint : configuredServerEndpoints) { for (final Session session : endpoint.getOpenSessions()) { ((UndertowSession)session).getExecutor().execute(new Runnable() { @Override public void run() { try { session.close(new CloseReason(CloseReason.CloseCodes.GOING_AWAY, "")); } catch (Exception e) { JsrWebSocketLogger.ROOT_LOGGER.couldNotCloseOnUndeploy(e); } } }); } } Runnable done = new Runnable() { int count = configuredServerEndpoints.size(); @Override public synchronized void run() { List copy = null; synchronized (ServerWebSocketContainer.this) { count--; if (count == 0) { copy = new ArrayList<>(pauseListeners); pauseListeners.clear(); } } if(copy != null) { for (PauseListener p : copy) { p.paused(); } } } }; for (ConfiguredServerEndpoint endpoint : configuredServerEndpoints) { endpoint.notifyClosed(done); } } private void doClose() { closed = true; for (ConfiguredServerEndpoint endpoint : configuredServerEndpoints) { for (Session session : endpoint.getOpenSessions()) { try { session.close(new CloseReason(CloseReason.CloseCodes.GOING_AWAY, "")); } catch (Exception e) { JsrWebSocketLogger.ROOT_LOGGER.couldNotCloseOnUndeploy(e); } } } } static WebSocketHandshakeHolder handshakes(ConfiguredServerEndpoint config) { List handshakes = new ArrayList<>(); handshakes.add(new JsrHybi13Handshake(config)); handshakes.add(new JsrHybi08Handshake(config)); handshakes.add(new JsrHybi07Handshake(config)); return new WebSocketHandshakeHolder(handshakes, config); } static WebSocketHandshakeHolder handshakes(ConfiguredServerEndpoint config, List extensions) { List handshakes = new ArrayList<>(); Handshake jsrHybi13Handshake = new JsrHybi13Handshake(config); Handshake jsrHybi08Handshake = new JsrHybi08Handshake(config); Handshake jsrHybi07Handshake = new JsrHybi07Handshake(config); for (ExtensionHandshake extension : extensions) { jsrHybi13Handshake.addExtension(extension); jsrHybi08Handshake.addExtension(extension); jsrHybi07Handshake.addExtension(extension); } handshakes.add(jsrHybi13Handshake); handshakes.add(jsrHybi08Handshake); handshakes.add(jsrHybi07Handshake); return new WebSocketHandshakeHolder(handshakes, config); } static final class WebSocketHandshakeHolder { final List handshakes; final ConfiguredServerEndpoint endpoint; private WebSocketHandshakeHolder(List handshakes, ConfiguredServerEndpoint endpoint) { this.handshakes = handshakes; this.endpoint = endpoint; } } /** * resumes a paused container */ public synchronized void resume() { closed = false; for(PauseListener p : pauseListeners) { p.resumed(); } pauseListeners.clear(); } public WebSocketReconnectHandler getWebSocketReconnectHandler() { return webSocketReconnectHandler; } public boolean isClosed() { return closed; } public interface PauseListener { void paused(); void resumed(); } public boolean isDispatchToWorker() { return dispatchToWorker; } } undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/SessionContainer.java000066400000000000000000000052341420065311100334550ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import javax.websocket.Session; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * @author Stuart Douglas */ public class SessionContainer { private Runnable doneTask; private volatile int waiterCount; private final Set openSessions = Collections.newSetFromMap(new ConcurrentHashMap()); public Set getOpenSessions() { return Collections.unmodifiableSet(openSessions); } public void addOpenSession(Session session) { synchronized (this) { openSessions.add(session); } } public void removeOpenSession(Session session) { Runnable task = null; synchronized (this) { openSessions.remove(session); if (waiterCount > 0 && openSessions.isEmpty()) { notifyAll(); } if(doneTask != null) { task = doneTask; doneTask = null; } } if(task != null) { task.run(); } } public void awaitClose(long timeout) { synchronized (this) { if(openSessions.isEmpty()) { return; } waiterCount++; long cur,end = System.currentTimeMillis() + timeout; try { while ((cur=System.currentTimeMillis()) < end && !openSessions.isEmpty()) { wait(end - cur); } } catch (InterruptedException e) { //ignore } finally { waiterCount--; } } } public void notifyClosed(Runnable done) { boolean run = false; synchronized (this) { if(openSessions.isEmpty()) { run = true; } else { this.doneTask = done; } } if(run) { done.run(); } } } UndertowContainerProvider.java000066400000000000000000000141541420065311100352760ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import java.io.IOException; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; import javax.websocket.ContainerProvider; import javax.websocket.WebSocketContainer; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.Xnio; import org.xnio.XnioWorker; import io.undertow.connector.ByteBufferPool; import io.undertow.server.DefaultByteBufferPool; import io.undertow.servlet.api.ClassIntrospecter; import io.undertow.servlet.api.InstanceFactory; import io.undertow.servlet.util.DefaultClassIntrospector; /** * @author Stuart Douglas */ public class UndertowContainerProvider extends ContainerProvider { private static final boolean directBuffers = Boolean.getBoolean("io.undertow.websockets.direct-buffers"); private static final boolean invokeInIoThread = Boolean.getBoolean("io.undertow.websockets.invoke-in-io-thread"); private static final RuntimePermission PERMISSION = new RuntimePermission("io.undertow.websockets.jsr.MODIFY_WEBSOCKET_CONTAINER"); private static final Map webSocketContainers = new ConcurrentHashMap<>(); private static volatile ServerWebSocketContainer defaultContainer; private static volatile boolean defaultContainerDisabled = false; private static final SwitchableClassIntrospector defaultIntrospector = new SwitchableClassIntrospector(); @Override protected WebSocketContainer getContainer() { ClassLoader tccl; if (System.getSecurityManager() == null) { tccl = Thread.currentThread().getContextClassLoader(); } else { tccl = AccessController.doPrivileged(new PrivilegedAction() { @Override public ClassLoader run() { return Thread.currentThread().getContextClassLoader(); } }); } WebSocketContainer webSocketContainer = webSocketContainers.get(tccl); if (webSocketContainer == null) { return getDefaultContainer(); } return webSocketContainer; } static ServerWebSocketContainer getDefaultContainer() { if (defaultContainerDisabled) { return null; } if (defaultContainer != null) { return defaultContainer; } synchronized (UndertowContainerProvider.class) { if (defaultContainer == null) { //this is not great, as we have no way to control the lifecycle //but there is not much we can do //todo: what options should we use here? ByteBufferPool buffers = new DefaultByteBufferPool(directBuffers, 1024, 100, 12); defaultContainer = new ServerWebSocketContainer(defaultIntrospector, UndertowContainerProvider.class.getClassLoader(), new Supplier() { volatile XnioWorker worker; @Override public XnioWorker get() { if(worker == null) { synchronized (this) { if(worker == null) { try { worker = Xnio.getInstance().createWorker(OptionMap.create(Options.THREAD_DAEMON, true)); } catch (IOException e) { throw new RuntimeException(e); } } } } return worker; } }, buffers, Collections.EMPTY_LIST, !invokeInIoThread); } return defaultContainer; } } public static void addContainer(final ClassLoader classLoader, final WebSocketContainer webSocketContainer) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(PERMISSION); } webSocketContainers.put(classLoader, webSocketContainer); } public static void removeContainer(final ClassLoader classLoader) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(PERMISSION); } webSocketContainers.remove(classLoader); } public void setDefaultClassIntrospector(ClassIntrospecter classIntrospector) { if (classIntrospector == null) { throw new IllegalArgumentException(); } defaultIntrospector.setIntrospecter(classIntrospector); } public static void disableDefaultContainer() { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(PERMISSION); } defaultContainerDisabled = true; } private static class SwitchableClassIntrospector implements ClassIntrospecter { private volatile ClassIntrospecter introspecter = DefaultClassIntrospector.INSTANCE; @Override public InstanceFactory createInstanceFactory(Class clazz) throws NoSuchMethodException { return introspecter.createInstanceFactory(clazz); } public void setIntrospecter(ClassIntrospecter introspecter) { this.introspecter = introspecter; } } } undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/UndertowSession.java000066400000000000000000000370721420065311100333470ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import io.undertow.server.session.SecureRandomSessionIdGenerator; import io.undertow.servlet.api.InstanceHandle; import io.undertow.util.WorkerUtils; import io.undertow.websockets.client.WebSocketClient; import io.undertow.websockets.core.CloseMessage; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSockets; import org.xnio.ChannelListener; import org.xnio.IoFuture; import org.xnio.IoUtils; import javax.websocket.CloseReason; import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.Extension; import javax.websocket.MessageHandler; import javax.websocket.RemoteEndpoint; import javax.websocket.Session; import java.io.IOException; import java.net.URI; import java.security.Principal; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * {@link Session} implementation which makes use of the high-level WebSocket API of undertow under the hood. * * @author Norman Maurer */ public final class UndertowSession implements Session { private final String sessionId; private WebSocketChannel webSocketChannel; private FrameHandler frameHandler; private final ServerWebSocketContainer container; private final Principal user; private final WebSocketSessionRemoteEndpoint remote; private final Map attrs; private final Map> requestParameterMap; private final URI requestUri; private final String queryString; private final Map pathParameters; private final InstanceHandle endpoint; private final Encoding encoding; private final AtomicBoolean closed = new AtomicBoolean(); private final SessionContainer openSessions; private final String subProtocol; private final List extensions; private final WebSocketClient.ConnectionBuilder clientConnectionBuilder; private final EndpointConfig config; private volatile int maximumBinaryBufferSize = 0; private volatile int maximumTextBufferSize = 0; private volatile boolean localClose; private int disconnectCount = 0; private int failedCount = 0; UndertowSession(WebSocketChannel webSocketChannel, URI requestUri, Map pathParameters, Map> requestParameterMap, EndpointSessionHandler handler, Principal user, InstanceHandle endpoint, EndpointConfig config, final String queryString, final Encoding encoding, final SessionContainer openSessions, final String subProtocol, final List extensions, WebSocketClient.ConnectionBuilder clientConnectionBuilder) { assert openSessions != null; this.webSocketChannel = webSocketChannel; this.queryString = queryString; this.encoding = encoding; this.openSessions = openSessions; this.clientConnectionBuilder = clientConnectionBuilder; container = handler.getContainer(); this.user = user; this.requestUri = requestUri; this.requestParameterMap = Collections.unmodifiableMap(requestParameterMap); this.pathParameters = Collections.unmodifiableMap(pathParameters); this.config = config; remote = new WebSocketSessionRemoteEndpoint(this, encoding); this.endpoint = endpoint; this.sessionId = new SecureRandomSessionIdGenerator().createSessionId(); this.attrs = Collections.synchronizedMap(new HashMap<>(config.getUserProperties())); this.extensions = extensions; this.subProtocol = subProtocol; setupWebSocketChannel(webSocketChannel); } @Override public ServerWebSocketContainer getContainer() { return container; } @SuppressWarnings({"rawtypes", "unchecked"}) @Override public synchronized void addMessageHandler(MessageHandler messageHandler) throws IllegalStateException { frameHandler.addHandler(messageHandler); } @Override public void addMessageHandler(Class clazz, MessageHandler.Whole handler) { frameHandler.addHandler(clazz, handler); } @Override public void addMessageHandler(Class clazz, MessageHandler.Partial handler) { frameHandler.addHandler(clazz, handler); } @SuppressWarnings("rawtypes") @Override public synchronized Set getMessageHandlers() { return frameHandler.getHandlers(); } @SuppressWarnings({"rawtypes", "unchecked"}) @Override public synchronized void removeMessageHandler(MessageHandler messageHandler) { frameHandler.removeHandler(messageHandler); } /** * sets the recieve listener This should only be used for annotated endpoints. * * @param handler The handler */ public void setReceiveListener(final ChannelListener handler) { webSocketChannel.getReceiveSetter().set(handler); } @Override public String getProtocolVersion() { return webSocketChannel.getVersion().toHttpHeaderValue(); } @Override public String getNegotiatedSubprotocol() { return subProtocol == null ? "" : subProtocol; } @Override public boolean isSecure() { return webSocketChannel.isSecure(); } @Override public boolean isOpen() { return webSocketChannel.isOpen(); } @Override public long getMaxIdleTimeout() { return webSocketChannel.getIdleTimeout(); } @Override public void setMaxIdleTimeout(final long milliseconds) { webSocketChannel.setIdleTimeout(milliseconds); } @Override public String getId() { return sessionId; } @Override public void close() throws IOException { close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, null)); } @Override public void close(CloseReason closeReason) throws IOException { localClose = true; closeInternal(closeReason); } public void closeInternal() throws IOException { closeInternal(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, null)); } public void closeInternal(CloseReason closeReason) throws IOException { if(closed.compareAndSet(false, true)) { try { try { if (!webSocketChannel.isCloseFrameReceived() && !webSocketChannel.isCloseFrameSent()) { //if we have already recieved a close frame then the close frame handler //will deal with sending back the reason message if (closeReason == null || closeReason.getCloseCode().getCode() == CloseReason.CloseCodes.NO_STATUS_CODE.getCode()) { webSocketChannel.sendClose(); } else { WebSockets.sendClose(new CloseMessage(closeReason.getCloseCode().getCode(), closeReason.getReasonPhrase()).toByteBuffer(), webSocketChannel, null); } } } finally { try { String reason = null; CloseReason.CloseCode code = CloseReason.CloseCodes.NO_STATUS_CODE; if(webSocketChannel.getCloseCode() != -1) { reason = webSocketChannel.getCloseReason(); code = CloseReason.CloseCodes.getCloseCode(webSocketChannel.getCloseCode()); } else if(closeReason != null) { reason = closeReason.getReasonPhrase(); code = closeReason.getCloseCode(); } //horrible hack //the spec says that if we (the local container) close locally then we need to use 1006 //although the TCK does not expect this behaviour for TOO_BIG and VIOLATED_POLICY //we need to really clean up the close behaviour in the next spec if(!webSocketChannel.isCloseInitiatedByRemotePeer() && !localClose && code.getCode() != CloseReason.CloseCodes.TOO_BIG.getCode() && code.getCode() != CloseReason.CloseCodes.VIOLATED_POLICY.getCode()) { //2.1.5: we must use 1006 if the close was initiated locally //however we only do this for normal closure //if the close was due to another reason such as a message being too long we need to report the real reason code = CloseReason.CloseCodes.CLOSED_ABNORMALLY; } endpoint.getInstance().onClose(this, new CloseReason(code, reason)); } catch (Exception e) { endpoint.getInstance().onError(this, e); } } } finally { close0(); if(clientConnectionBuilder != null && !localClose) { WebSocketReconnectHandler webSocketReconnectHandler = container.getWebSocketReconnectHandler(); if (webSocketReconnectHandler != null) { JsrWebSocketLogger.REQUEST_LOGGER.debugf("Calling reconnect handler for %s", this); long reconnect = webSocketReconnectHandler.disconnected(closeReason, requestUri, this, ++disconnectCount); if (reconnect >= 0) { handleReconnect(reconnect); } } } } } } private void handleReconnect(final long reconnect) { JsrWebSocketLogger.REQUEST_LOGGER.debugf("Attempting reconnect in %s ms for session %s", reconnect, this); WorkerUtils.executeAfter(webSocketChannel.getIoThread(), new Runnable() { @Override public void run() { clientConnectionBuilder.connect().addNotifier(new IoFuture.HandlingNotifier() { @Override public void handleDone(WebSocketChannel data, Object attachment) { closed.set(false); UndertowSession.this.webSocketChannel = data; UndertowSession.this.setupWebSocketChannel(data); localClose = false; endpoint.getInstance().onOpen(UndertowSession.this, config); webSocketChannel.resumeReceives(); } @Override public void handleFailed(IOException exception, Object attachment) { long timeout = container.getWebSocketReconnectHandler().reconnectFailed(exception, getRequestURI(), UndertowSession.this, ++failedCount); if(timeout >= 0) { handleReconnect(timeout); } } }, null); } }, reconnect, TimeUnit.MILLISECONDS); } public void forceClose() { IoUtils.safeClose(webSocketChannel); } @Override public URI getRequestURI() { return requestUri; } @Override public Map> getRequestParameterMap() { return requestParameterMap; } @Override public String getQueryString() { return queryString; } @Override public Map getPathParameters() { return pathParameters; } @Override public Map getUserProperties() { return attrs; } @Override public Principal getUserPrincipal() { return user; } @Override public void setMaxBinaryMessageBufferSize(int i) { maximumBinaryBufferSize = i; } @Override public int getMaxBinaryMessageBufferSize() { return maximumBinaryBufferSize; //return (int) webSocketChannel.getMaximumBinaryFrameSize(); } @Override public void setMaxTextMessageBufferSize(int i) { maximumTextBufferSize = i; } @Override public int getMaxTextMessageBufferSize() { return maximumTextBufferSize; } @Override public RemoteEndpoint.Async getAsyncRemote() { return remote.getAsync(); } @Override public RemoteEndpoint.Basic getBasicRemote() { return remote.getBasic(); } @Override public Set getOpenSessions() { return new HashSet<>(openSessions.getOpenSessions()); } @Override public List getNegotiatedExtensions() { return extensions; } void close0() { //we use the executor to preserve ordering getExecutor().execute(new Runnable() { @Override public void run() { try { endpoint.release(); } finally { try { encoding.close(); } finally { openSessions.removeOpenSession(UndertowSession.this); } } } }); } public Encoding getEncoding() { return encoding; } public WebSocketChannel getWebSocketChannel() { return webSocketChannel; } private void setupWebSocketChannel(WebSocketChannel webSocketChannel) { this.frameHandler = new FrameHandler(this, this.endpoint.getInstance()); webSocketChannel.getReceiveSetter().set(frameHandler); webSocketChannel.addCloseTask(new ChannelListener() { @Override public void handleEvent(WebSocketChannel channel) { //so this puts us in an interesting position. We know the underlying //TCP connection has been torn down, however this may have involved reading //a close frame, which will be delivered shortly //to get around this we schedule the code in the IO thread, so if there is a close //frame awaiting delivery it will be delivered before the close channel.getIoThread().execute(new Runnable() { @Override public void run() { //we delegate this execution to the IO thread try { closeInternal(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, null)); } catch (IOException e) { //ignore } } }); } }); } public Executor getExecutor() { return frameHandler.getExecutor(); } boolean isSessionClosed() { return closed.get(); } } WebSocketDeploymentInfo.java000066400000000000000000000154511420065311100346550ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import io.undertow.server.XnioByteBufferPool; import io.undertow.websockets.extensions.ExtensionHandshake; import io.undertow.connector.ByteBufferPool; import org.xnio.Pool; import org.xnio.XnioWorker; import javax.websocket.server.ServerEndpointConfig; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.function.Supplier; /** * Web socket deployment information * * @author Stuart Douglas */ public class WebSocketDeploymentInfo implements Cloneable { public static final String ATTRIBUTE_NAME = "io.undertow.websockets.jsr.WebSocketDeploymentInfo"; private Supplier worker = new Supplier() { volatile XnioWorker worker; @Override public XnioWorker get() { if(worker != null) { return worker; } return worker = UndertowContainerProvider.getDefaultContainer().getXnioWorker(); } }; private ByteBufferPool buffers; private boolean dispatchToWorkerThread = false; private final List> annotatedEndpoints = new ArrayList<>(); private final List programaticEndpoints = new ArrayList<>(); private final List containerReadyListeners = new ArrayList<>(); private final List extensions = new ArrayList<>(); private String clientBindAddress = null; private WebSocketReconnectHandler reconnectHandler; public Supplier getWorker() { return worker; } public WebSocketDeploymentInfo setWorker(Supplier worker) { this.worker = worker; return this; } public WebSocketDeploymentInfo setWorker(XnioWorker worker) { this.worker = new Supplier() { @Override public XnioWorker get() { return worker; } }; return this; } public ByteBufferPool getBuffers() { return buffers; } @Deprecated public WebSocketDeploymentInfo setBuffers(Pool buffers) { return setBuffers(new XnioByteBufferPool(buffers)); } public WebSocketDeploymentInfo setBuffers(ByteBufferPool buffers) { this.buffers = buffers; return this; } public WebSocketDeploymentInfo addEndpoint(final Class annotated) { this.annotatedEndpoints.add(annotated); return this; } public WebSocketDeploymentInfo addAnnotatedEndpoints(final Collection> annotatedEndpoints) { this.annotatedEndpoints.addAll(annotatedEndpoints); return this; } public WebSocketDeploymentInfo addEndpoint(final ServerEndpointConfig endpoint) { this.programaticEndpoints.add(endpoint); return this; } public WebSocketDeploymentInfo addProgramaticEndpoints(final Collection programaticEndpoints) { this.programaticEndpoints.addAll(programaticEndpoints); return this; } public List> getAnnotatedEndpoints() { return annotatedEndpoints; } public List getProgramaticEndpoints() { return programaticEndpoints; } void containerReady(ServerWebSocketContainer container) { for(ContainerReadyListener listener : containerReadyListeners) { listener.ready(container); } } public WebSocketDeploymentInfo addListener(final ContainerReadyListener listener) { containerReadyListeners.add(listener); return this; } public WebSocketDeploymentInfo addListeners(final Collection listeners) { containerReadyListeners.addAll(listeners); return this; } public List getListeners() { return containerReadyListeners; } public boolean isDispatchToWorkerThread() { return dispatchToWorkerThread; } public WebSocketDeploymentInfo setDispatchToWorkerThread(boolean dispatchToWorkerThread) { this.dispatchToWorkerThread = dispatchToWorkerThread; return this; } public interface ContainerReadyListener { void ready(ServerWebSocketContainer container); } /** * Add a new WebSocket Extension into this deployment info. * * @param extension a new {@code ExtensionHandshake} instance * @return current deployment info */ public WebSocketDeploymentInfo addExtension(final ExtensionHandshake extension) { if (null != extension) { this.extensions.add(extension); } return this; } public WebSocketDeploymentInfo addExtensions(final Collection extensions) { this.extensions.addAll(extensions); return this; } /** * @return list of extensions available for this deployment info */ public List getExtensions() { return extensions; } public String getClientBindAddress() { return clientBindAddress; } public WebSocketDeploymentInfo setClientBindAddress(String clientBindAddress) { this.clientBindAddress = clientBindAddress; return this; } public WebSocketReconnectHandler getReconnectHandler() { return reconnectHandler; } public WebSocketDeploymentInfo setReconnectHandler(WebSocketReconnectHandler reconnectHandler) { this.reconnectHandler = reconnectHandler; return this; } @Override public WebSocketDeploymentInfo clone() { return new WebSocketDeploymentInfo() .setWorker(this.worker) .setBuffers(this.buffers) .setDispatchToWorkerThread(this.dispatchToWorkerThread) .addAnnotatedEndpoints(this.annotatedEndpoints) .addProgramaticEndpoints(this.programaticEndpoints) .addListeners(this.containerReadyListeners) .addExtensions(this.extensions) .setClientBindAddress(this.clientBindAddress) .setReconnectHandler(this.reconnectHandler) ; } } WebSocketReconnectHandler.java000066400000000000000000000036661420065311100351440ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import javax.websocket.CloseReason; import javax.websocket.Session; import java.io.IOException; import java.net.URI; /** * A reconnect handler for web socket connections. If a websocket is reconnected it will re-use the same web socket * endpoint instance. * * Note that only a single reconnect handler instance can be registered for each deployment. If a reconnect handler * wishes to save state it should store it in the session attributes * * @author Stuart Douglas */ public interface WebSocketReconnectHandler { /** * Method that is invoked by the reconnect handler after disconnection * * @param closeReason The close reason * @return The number of milliseconds to wait for a reconnect, or -1 if no reconnect should be attempted */ long disconnected(CloseReason closeReason, URI connectionUri, Session session, int disconnectCount); /** * Method that is invoked if the reconnection fails * * @param exception The failure exception * @return The number of milliseconds to wait for a reconnect, or -1 if no reconnect should be attempted */ long reconnectFailed(IOException exception, URI connectionUri, Session session, int failedCount); } WebSocketSessionRemoteEndpoint.java000066400000000000000000000362611420065311100362230ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import io.undertow.websockets.core.BinaryOutputStream; import io.undertow.websockets.core.StreamSinkFrameChannel; import io.undertow.websockets.core.WebSocketCallback; import io.undertow.websockets.core.WebSocketFrameType; import io.undertow.websockets.core.WebSocketUtils; import io.undertow.websockets.core.WebSockets; import org.xnio.channels.Channels; import javax.websocket.EncodeException; import javax.websocket.RemoteEndpoint; import javax.websocket.SendHandler; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.concurrent.Future; /** * {@link RemoteEndpoint} implementation which uses a WebSocketSession for all its operation. * * @author Norman Maurer */ final class WebSocketSessionRemoteEndpoint implements RemoteEndpoint { private final UndertowSession undertowSession; private final Async async = new AsyncWebSocketSessionRemoteEndpoint(); private final Basic basic = new BasicWebSocketSessionRemoteEndpoint(); private final Encoding encoding; WebSocketSessionRemoteEndpoint(UndertowSession session, final Encoding encoding) { this.undertowSession = session; this.encoding = encoding; } public Async getAsync() { return async; } public Basic getBasic() { return basic; } @Override public void flushBatch() { // Do nothing } @Override public void setBatchingAllowed(final boolean allowed) throws IOException { } @Override public boolean getBatchingAllowed() { return false; } @Override public void sendPing(final ByteBuffer applicationData) throws IOException, IllegalArgumentException { if(applicationData == null) { throw JsrWebSocketMessages.MESSAGES.messageInNull(); } if(applicationData.remaining() > 125) { throw JsrWebSocketMessages.MESSAGES.messageTooLarge(applicationData.remaining(), 125); } WebSockets.sendPing(applicationData, undertowSession.getWebSocketChannel(), null); } @Override public void sendPong(final ByteBuffer applicationData) throws IOException, IllegalArgumentException { if(applicationData == null) { throw JsrWebSocketMessages.MESSAGES.messageInNull(); } if(applicationData.remaining() > 125) { throw JsrWebSocketMessages.MESSAGES.messageTooLarge(applicationData.remaining(), 125); } WebSockets.sendPong(applicationData, undertowSession.getWebSocketChannel(), null); } class AsyncWebSocketSessionRemoteEndpoint implements Async { private long sendTimeout = 0; @Override public long getSendTimeout() { return sendTimeout; } @Override public void setSendTimeout(final long timeoutmillis) { sendTimeout = timeoutmillis; } @Override public void sendText(final String text, final SendHandler handler) { if(handler == null) { throw JsrWebSocketMessages.MESSAGES.handlerIsNull(); } if(text == null) { throw JsrWebSocketMessages.MESSAGES.messageInNull(); } WebSockets.sendText(text, undertowSession.getWebSocketChannel(), new SendHandlerAdapter(handler), sendTimeout); } @Override public Future sendText(final String text) { if(text == null) { throw JsrWebSocketMessages.MESSAGES.messageInNull(); } final SendResultFuture future = new SendResultFuture(); WebSockets.sendText(text, undertowSession.getWebSocketChannel(), future, sendTimeout); return future; } @Override public Future sendBinary(final ByteBuffer data) { if(data == null) { throw JsrWebSocketMessages.MESSAGES.messageInNull(); } final SendResultFuture future = new SendResultFuture(); WebSockets.sendBinary(data, undertowSession.getWebSocketChannel(), future, sendTimeout); return future; } @Override public void sendBinary(final ByteBuffer data, final SendHandler completion) { if(completion == null) { throw JsrWebSocketMessages.MESSAGES.handlerIsNull(); } if(data == null) { throw JsrWebSocketMessages.MESSAGES.messageInNull(); } WebSockets.sendBinary(data, undertowSession.getWebSocketChannel(), new SendHandlerAdapter(completion), sendTimeout); } @Override public Future sendObject(final Object o) { if(o == null) { throw JsrWebSocketMessages.MESSAGES.messageInNull(); } final SendResultFuture future = new SendResultFuture(); sendObjectImpl(o, future); return future; } @Override public void sendObject(final Object data, final SendHandler handler) { if(handler == null) { throw JsrWebSocketMessages.MESSAGES.handlerIsNull(); } if(data == null) { throw JsrWebSocketMessages.MESSAGES.messageInNull(); } sendObjectImpl(data, new SendHandlerAdapter(handler)); } private void sendObjectImpl(final Object o, final WebSocketCallback callback) { try { if(o instanceof String) { WebSockets.sendText((String)o, undertowSession.getWebSocketChannel(), callback, sendTimeout); } else if(o instanceof byte[]) { WebSockets.sendBinary(ByteBuffer.wrap((byte[])o), undertowSession.getWebSocketChannel(), callback, sendTimeout); } else if(o instanceof ByteBuffer) { WebSockets.sendBinary((ByteBuffer)o, undertowSession.getWebSocketChannel(), callback, sendTimeout); } else if (encoding.canEncodeText(o.getClass())) { WebSockets.sendText(encoding.encodeText(o), undertowSession.getWebSocketChannel(), callback, sendTimeout); } else if (encoding.canEncodeBinary(o.getClass())) { WebSockets.sendBinary(encoding.encodeBinary(o), undertowSession.getWebSocketChannel(), callback, sendTimeout); } else { // TODO: Replace on bug is fixed // https://issues.jboss.org/browse/LOGTOOL-64 throw new EncodeException(o, "No suitable encoder found"); } } catch (Exception e) { callback.onError(undertowSession.getWebSocketChannel(), null, e); } } @Override public void setBatchingAllowed(final boolean allowed) throws IOException { undertowSession.getWebSocketChannel().setRequireExplicitFlush(allowed); } @Override public boolean getBatchingAllowed() { return undertowSession.getWebSocketChannel().isRequireExplicitFlush(); } @Override public void flushBatch() throws IOException { undertowSession.getWebSocketChannel().flush(); } @Override public void sendPing(final ByteBuffer applicationData) throws IOException, IllegalArgumentException { if(applicationData == null) { throw JsrWebSocketMessages.MESSAGES.messageInNull(); } if(applicationData.remaining() > 125) { throw JsrWebSocketMessages.MESSAGES.messageTooLarge(applicationData.remaining(), 125); } WebSockets.sendPing(applicationData, undertowSession.getWebSocketChannel(), null, sendTimeout); } @Override public void sendPong(final ByteBuffer applicationData) throws IOException, IllegalArgumentException { if(applicationData == null) { throw JsrWebSocketMessages.MESSAGES.messageInNull(); } if(applicationData.remaining() > 125) { throw JsrWebSocketMessages.MESSAGES.messageTooLarge(applicationData.remaining(), 125); } WebSockets.sendPong(applicationData, undertowSession.getWebSocketChannel(), null, sendTimeout); } } class BasicWebSocketSessionRemoteEndpoint implements Basic { private StreamSinkFrameChannel binaryFrameSender; private StreamSinkFrameChannel textFrameSender; public void assertNotInFragment() { if (textFrameSender != null || binaryFrameSender != null) { throw JsrWebSocketMessages.MESSAGES.cannotSendInMiddleOfFragmentedMessage(); } } @Override public void sendText(final String text) throws IOException { if(text == null) { throw JsrWebSocketMessages.MESSAGES.messageInNull(); } assertNotInFragment(); WebSockets.sendTextBlocking(text, undertowSession.getWebSocketChannel()); } @Override public void sendBinary(final ByteBuffer data) throws IOException { if(data == null) { throw JsrWebSocketMessages.MESSAGES.messageInNull(); } assertNotInFragment(); WebSockets.sendBinaryBlocking(data, undertowSession.getWebSocketChannel()); data.clear(); //for some reason the TCK expects this, might as well just match the RI behaviour } @Override public void sendText(final String partialMessage, final boolean isLast) throws IOException { if(partialMessage == null) { throw JsrWebSocketMessages.MESSAGES.messageInNull(); } if (binaryFrameSender != null) { throw JsrWebSocketMessages.MESSAGES.cannotSendInMiddleOfFragmentedMessage(); } if (textFrameSender == null) { textFrameSender = undertowSession.getWebSocketChannel().send(WebSocketFrameType.TEXT); } try { Channels.writeBlocking(textFrameSender, WebSocketUtils.fromUtf8String(partialMessage)); if(isLast) { textFrameSender.shutdownWrites(); } Channels.flushBlocking(textFrameSender); } finally { if (isLast) { textFrameSender = null; } } } @Override public void sendBinary(final ByteBuffer partialByte, final boolean isLast) throws IOException { if(partialByte == null) { throw JsrWebSocketMessages.MESSAGES.messageInNull(); } if (textFrameSender != null) { throw JsrWebSocketMessages.MESSAGES.cannotSendInMiddleOfFragmentedMessage(); } if (binaryFrameSender == null) { binaryFrameSender = undertowSession.getWebSocketChannel().send(WebSocketFrameType.BINARY); } try { Channels.writeBlocking(binaryFrameSender, partialByte); if(isLast) { binaryFrameSender.shutdownWrites(); } Channels.flushBlocking(binaryFrameSender); } finally { if (isLast) { binaryFrameSender = null; } } partialByte.clear(); } @Override public OutputStream getSendStream() throws IOException { assertNotInFragment(); //TODO: track fragment state return new BinaryOutputStream(undertowSession.getWebSocketChannel().send(WebSocketFrameType.BINARY)); } @Override public Writer getSendWriter() throws IOException { assertNotInFragment(); return new OutputStreamWriter(new BinaryOutputStream(undertowSession.getWebSocketChannel().send(WebSocketFrameType.TEXT)), StandardCharsets.UTF_8); } @Override public void sendObject(final Object data) throws IOException, EncodeException { if(data == null) { throw JsrWebSocketMessages.MESSAGES.messageInNull(); } sendObjectImpl(data); } private void sendObjectImpl(final Object o) throws IOException, EncodeException { if(o instanceof String) { sendText((String)o); } else if(o instanceof byte[]) { sendBinary(ByteBuffer.wrap((byte[])o)); } else if(o instanceof ByteBuffer) { sendBinary((ByteBuffer)o); } else if (encoding.canEncodeText(o.getClass())) { WebSockets.sendTextBlocking(encoding.encodeText(o), undertowSession.getWebSocketChannel()); } else if (encoding.canEncodeBinary(o.getClass())) { WebSockets.sendBinaryBlocking(encoding.encodeBinary(o), undertowSession.getWebSocketChannel()); } else { // TODO: Replace on bug is fixed // https://issues.jboss.org/browse/LOGTOOL-64 throw new EncodeException(o, "No suitable encoder found"); } } @Override public void setBatchingAllowed(final boolean allowed) throws IOException { } @Override public boolean getBatchingAllowed() { return false; } @Override public void flushBatch() throws IOException { } @Override public void sendPing(final ByteBuffer applicationData) throws IOException, IllegalArgumentException { if(applicationData == null) { throw JsrWebSocketMessages.MESSAGES.messageInNull(); } if(applicationData.remaining() > 125) { throw JsrWebSocketMessages.MESSAGES.messageTooLarge(applicationData.remaining(), 125); } WebSockets.sendPingBlocking(applicationData, undertowSession.getWebSocketChannel()); } @Override public void sendPong(final ByteBuffer applicationData) throws IOException, IllegalArgumentException { if(applicationData == null) { throw JsrWebSocketMessages.MESSAGES.messageInNull(); } if(applicationData.remaining() > 125) { throw JsrWebSocketMessages.MESSAGES.messageTooLarge(applicationData.remaining(), 125); } WebSockets.sendPongBlocking(applicationData, undertowSession.getWebSocketChannel()); } } } WebsocketClientSslProvider.java000066400000000000000000000025621420065311100353730ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr; import org.xnio.XnioWorker; import org.xnio.ssl.XnioSsl; import javax.websocket.ClientEndpointConfig; import javax.websocket.Endpoint; import java.net.URI; /** * Interface that is loaded from a service loader, that allows * you to configure SSL for web socket client connections. * * @author Stuart Douglas */ public interface WebsocketClientSslProvider { XnioSsl getSsl(XnioWorker worker, final Class annotatedEndpoint, URI uri); XnioSsl getSsl(XnioWorker worker, final Object annotatedEndpointInstance, URI uri); XnioSsl getSsl(XnioWorker worker, final Endpoint endpoint, final ClientEndpointConfig cec, URI uri); } undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/annotated/000077500000000000000000000000001420065311100312755ustar00rootroot00000000000000AnnotatedEndpoint.java000066400000000000000000000242541420065311100355060ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.annotated; import io.undertow.UndertowLogger; import io.undertow.servlet.api.InstanceHandle; import io.undertow.websockets.core.WebSocketLogger; import io.undertow.websockets.jsr.UndertowSession; import javax.websocket.CloseReason; import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.MessageHandler; import javax.websocket.SendHandler; import javax.websocket.SendResult; import javax.websocket.Session; import java.io.IOException; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; /** * @author Stuart Douglas */ public class AnnotatedEndpoint extends Endpoint { private final InstanceHandle instance; private final BoundMethod webSocketOpen; private final BoundMethod webSocketClose; private final BoundMethod webSocketError; private final BoundMethod textMessage; private final BoundMethod binaryMessage; private final BoundMethod pongMessage; private volatile boolean released; AnnotatedEndpoint(final InstanceHandle instance, final BoundMethod webSocketOpen, final BoundMethod webSocketClose, final BoundMethod webSocketError, final BoundMethod textMessage, final BoundMethod binaryMessage, final BoundMethod pongMessage) { this.instance = instance; this.webSocketOpen = webSocketOpen; this.webSocketClose = webSocketClose; this.webSocketError = webSocketError; this.textMessage = textMessage; this.binaryMessage = binaryMessage; this.pongMessage = pongMessage; } @Override public void onOpen(final Session session, final EndpointConfig endpointConfiguration) { this.released = false; final UndertowSession s = (UndertowSession) session; boolean partialText = textMessage == null || (textMessage.hasParameterType(boolean.class) && !textMessage.getMessageType().equals(boolean.class)); boolean partialBinary = binaryMessage == null || (binaryMessage.hasParameterType(boolean.class) && !binaryMessage.getMessageType().equals(boolean.class)); if(textMessage != null) { if(partialText) { addPartialHandler(s, textMessage); } else { if(textMessage.getMaxMessageSize() > 0) { s.setMaxTextMessageBufferSize((int) textMessage.getMaxMessageSize()); } addWholeHandler(s, textMessage); } } if(binaryMessage != null) { if(partialBinary) { addPartialHandler(s, binaryMessage); } else { if(binaryMessage.getMaxMessageSize() > 0) { s.setMaxBinaryMessageBufferSize((int) binaryMessage.getMaxMessageSize()); } addWholeHandler(s, binaryMessage); } } if(pongMessage != null) { addWholeHandler(s, pongMessage); } if (webSocketOpen != null) { final Map, Object> params = new HashMap<>(); params.put(Session.class, session); params.put(EndpointConfig.class, endpointConfiguration); params.put(Map.class, session.getPathParameters()); invokeMethod(params, webSocketOpen, s); } } private void addPartialHandler(final UndertowSession session, final BoundMethod method) { session.addMessageHandler((Class) method.getMessageType(), new MessageHandler.Partial() { @Override public void onMessage(Object partialMessage, boolean last) { final Map, Object> params = new HashMap<>(); params.put(Session.class, session); params.put(Map.class, session.getPathParameters()); params.put(method.getMessageType(), partialMessage); params.put(boolean.class, last); final Object result; try { result = method.invoke(instance.getInstance(), params); } catch (Throwable e) { AnnotatedEndpoint.this.onError(session, e); return; } sendResult(result, session); } }); } private void addWholeHandler(final UndertowSession session, final BoundMethod method) { session.addMessageHandler((Class) method.getMessageType(), new MessageHandler.Whole() { @Override public void onMessage(Object partialMessage) { final Map, Object> params = new HashMap<>(); params.put(Session.class, session); params.put(Map.class, session.getPathParameters()); params.put(method.getMessageType(), partialMessage); final Object result; try { result = method.invoke(instance.getInstance(), params); } catch (Exception e) { AnnotatedEndpoint.this.onError(session, e); return; } sendResult(result, session); } }); } private void invokeMethod(final Map, Object> params, final BoundMethod method, final UndertowSession session) { session.getContainer().invokeEndpointMethod(session.getExecutor(), new Runnable() { @Override public void run() { if(!released) { try { method.invoke(instance.getInstance(), params); } catch (Exception e) { onError(session, e); } } } }); } private void sendResult(final Object result, UndertowSession session) { if (result != null) { if (result instanceof String) { session.getAsyncRemote().sendText((String) result, new ErrorReportingSendHandler(session)); } else if (result instanceof byte[]) { session.getAsyncRemote().sendBinary(ByteBuffer.wrap((byte[]) result), new ErrorReportingSendHandler(session)); } else if (result instanceof ByteBuffer) { session.getAsyncRemote().sendBinary((ByteBuffer) result, new ErrorReportingSendHandler(session)); } else { session.getAsyncRemote().sendObject(result, new ErrorReportingSendHandler(session)); } if(session.getAsyncRemote().getBatchingAllowed()) { try { session.getAsyncRemote().flushBatch(); } catch (IOException e) { onError(session, e); } } } } @Override public void onClose(final Session session, final CloseReason closeReason) { if (webSocketClose != null) { final Map, Object> params = new HashMap<>(); params.put(Session.class, session); params.put(Map.class, session.getPathParameters()); params.put(CloseReason.class, closeReason); ((UndertowSession) session).getContainer().invokeEndpointMethod(((UndertowSession)session).getExecutor(), new Runnable() { @Override public void run() { if(!released) { try { webSocketClose.invoke(instance.getInstance(), params); } catch (Exception e) { onError(session, e); } finally { released = true; instance.release(); } } } } ); } } @Override public void onError(final Session session, final Throwable thr) { if (webSocketError != null) { final Map, Object> params = new HashMap<>(); params.put(Session.class, session); params.put(Throwable.class, thr); params.put(Map.class, session.getPathParameters()); ((UndertowSession) session).getContainer().invokeEndpointMethod(((UndertowSession)session).getExecutor(), new Runnable() { @Override public void run() { if(!released) { try { webSocketError.invoke(instance.getInstance(), params); } catch (Exception e) { if (e instanceof RuntimeException) { throw (RuntimeException) e; } throw new RuntimeException(e); //not much we can do here } } } }); } else if (thr instanceof IOException) { UndertowLogger.REQUEST_IO_LOGGER.ioException((IOException) thr); } else { WebSocketLogger.REQUEST_LOGGER.unhandledErrorInAnnotatedEndpoint(instance.getInstance(), thr); } } private final class ErrorReportingSendHandler implements SendHandler { private final Session session; private ErrorReportingSendHandler(Session session) { this.session = session; } @Override public void onResult(final SendResult result) { if (!result.isOK()) { AnnotatedEndpoint.this.onError(session, result.getException()); } } } } AnnotatedEndpointFactory.java000066400000000000000000000502611420065311100370330ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.annotated; import javax.websocket.CloseReason; import javax.websocket.DecodeException; import javax.websocket.DeploymentException; import javax.websocket.EndpointConfig; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.PongMessage; import javax.websocket.Session; import javax.websocket.server.PathParam; import java.io.InputStream; import java.io.Reader; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import io.undertow.servlet.api.InstanceHandle; import io.undertow.websockets.jsr.Encoding; import io.undertow.websockets.jsr.EncodingFactory; import io.undertow.websockets.jsr.JsrWebSocketLogger; import io.undertow.websockets.jsr.JsrWebSocketMessages; /** * Factory that creates annotated end points. * * @author Stuart Douglas */ public class AnnotatedEndpointFactory { private final Class endpointClass; private final BoundMethod OnOpen; private final BoundMethod OnClose; private final BoundMethod OnError; private final BoundMethod textMessage; private final BoundMethod binaryMessage; private final BoundMethod pongMessage; private AnnotatedEndpointFactory(final Class endpointClass, final BoundMethod OnOpen, final BoundMethod OnClose, final BoundMethod OnError, final BoundMethod textMessage, final BoundMethod binaryMessage, final BoundMethod pongMessage) { this.endpointClass = endpointClass; this.OnOpen = OnOpen; this.OnClose = OnClose; this.OnError = OnError; this.textMessage = textMessage; this.binaryMessage = binaryMessage; this.pongMessage = pongMessage; } public static AnnotatedEndpointFactory create(final Class endpointClass, final EncodingFactory encodingFactory, final Set paths) throws DeploymentException { final Set> found = new HashSet<>(); BoundMethod onOpen = null; BoundMethod onClose = null; BoundMethod onError = null; BoundMethod textMessage = null; BoundMethod binaryMessage = null; BoundMethod pongMessage = null; Class c = endpointClass; do { for (final Method method : c.getDeclaredMethods()) { if (method.isAnnotationPresent(OnOpen.class)) { if (found.contains(OnOpen.class)) { if(!onOpen.overrides(method)) { throw JsrWebSocketMessages.MESSAGES.moreThanOneAnnotation(OnOpen.class); } else { continue; } } found.add(OnOpen.class); onOpen = new BoundMethod(method, null, false, 0, new BoundSingleParameter(method, Session.class, true), new BoundSingleParameter(method, EndpointConfig.class, true), createBoundPathParameters(method, paths, endpointClass)); } if (method.isAnnotationPresent(OnClose.class)) { if (found.contains(OnClose.class)) { if(!onClose.overrides(method)) { throw JsrWebSocketMessages.MESSAGES.moreThanOneAnnotation(OnClose.class); } else { continue; } } found.add(OnClose.class); onClose = new BoundMethod(method, null, false, 0, new BoundSingleParameter(method, Session.class, true), new BoundSingleParameter(method, CloseReason.class, true), createBoundPathParameters(method, paths, endpointClass)); } if (method.isAnnotationPresent(OnError.class)) { if (found.contains(OnError.class)) { if(!onError.overrides(method)) { throw JsrWebSocketMessages.MESSAGES.moreThanOneAnnotation(OnError.class); } else { continue; } } found.add(OnError.class); onError = new BoundMethod(method, null, false, 0, new BoundSingleParameter(method, Session.class, true), new BoundSingleParameter(method, Throwable.class, false), createBoundPathParameters(method, paths, endpointClass)); } if (method.isAnnotationPresent(OnMessage.class) && ! method.isBridge()) { if(binaryMessage != null && binaryMessage.overrides(method)) { continue; } if(textMessage != null && textMessage.overrides(method)) { continue; } if(pongMessage != null && pongMessage.overrides(method)) { continue; } long maxMessageSize = method.getAnnotation(OnMessage.class).maxMessageSize(); boolean messageHandled = false; //this is a bit more complex Class[] parameterTypes = method.getParameterTypes(); int booleanLocation = -1; for (int i = 0; i < parameterTypes.length; ++i) { if (hasAnnotation(PathParam.class, method.getParameterAnnotations()[i])) { continue; } final Class param = parameterTypes[i]; if(param == boolean.class || param == Boolean.class) { booleanLocation = i; } else if (encodingFactory.canDecodeText(param)) { if (textMessage != null) { throw JsrWebSocketMessages.MESSAGES.moreThanOneAnnotation(OnMessage.class); } textMessage = new BoundMethod(method, param, true, maxMessageSize, new BoundSingleParameter(method, Session.class, true), new BoundSingleParameter(i, param), createBoundPathParameters(method, paths, endpointClass)); messageHandled = true; break; } else if (encodingFactory.canDecodeBinary(param)) { if (binaryMessage != null) { throw JsrWebSocketMessages.MESSAGES.moreThanOneAnnotation(OnMessage.class); } binaryMessage = new BoundMethod(method, param, true, maxMessageSize, new BoundSingleParameter(method, Session.class, true), new BoundSingleParameter(i, param), createBoundPathParameters(method, paths, endpointClass)); messageHandled = true; break; } else if (param.equals(byte[].class)) { if (binaryMessage != null) { throw JsrWebSocketMessages.MESSAGES.moreThanOneAnnotation(OnMessage.class); } binaryMessage = new BoundMethod(method, byte[].class, false, maxMessageSize, new BoundSingleParameter(method, Session.class, true), new BoundSingleParameter(method, boolean.class, true), new BoundSingleParameter(i, byte[].class), createBoundPathParameters(method, paths, endpointClass)); messageHandled = true; break; } else if (param.equals(ByteBuffer.class)) { if (binaryMessage != null) { throw JsrWebSocketMessages.MESSAGES.moreThanOneAnnotation(OnMessage.class); } binaryMessage = new BoundMethod(method, ByteBuffer.class, false, maxMessageSize, new BoundSingleParameter(method, Session.class, true), new BoundSingleParameter(method, boolean.class, true), new BoundSingleParameter(i, ByteBuffer.class), createBoundPathParameters(method, paths, endpointClass)); messageHandled = true; break; } else if (param.equals(InputStream.class)) { if (binaryMessage != null) { throw JsrWebSocketMessages.MESSAGES.moreThanOneAnnotation(OnMessage.class); } binaryMessage = new BoundMethod(method, InputStream.class, false, maxMessageSize, new BoundSingleParameter(method, Session.class, true), new BoundSingleParameter(i, InputStream.class), createBoundPathParameters(method, paths, endpointClass)); messageHandled = true; break; } else if (param.equals(String.class) && getPathParam(method, i) == null) { if (textMessage != null) { throw JsrWebSocketMessages.MESSAGES.moreThanOneAnnotation(OnMessage.class); } textMessage = new BoundMethod(method, String.class, false, maxMessageSize, new BoundSingleParameter(method, Session.class, true), new BoundSingleParameter(method, boolean.class, true), new BoundSingleParameter(i, String.class), createBoundPathParameters(method, paths, endpointClass)); messageHandled = true; break; } else if (param.equals(Reader.class) && getPathParam(method, i) == null) { if (textMessage != null) { throw JsrWebSocketMessages.MESSAGES.moreThanOneAnnotation(OnMessage.class); } textMessage = new BoundMethod(method, Reader.class, false, maxMessageSize, new BoundSingleParameter(method, Session.class, true), new BoundSingleParameter(i, Reader.class), createBoundPathParameters(method, paths, endpointClass)); messageHandled = true; break; } else if (param.equals(PongMessage.class)) { if (pongMessage != null) { throw JsrWebSocketMessages.MESSAGES.moreThanOneAnnotation(OnMessage.class); } pongMessage = new BoundMethod(method, PongMessage.class, false, maxMessageSize, new BoundSingleParameter(method, Session.class, true), new BoundSingleParameter(i, PongMessage.class), createBoundPathParameters(method, paths, endpointClass)); messageHandled = true; break; } } if (!messageHandled && booleanLocation != -1) { //so it turns out that the boolean was the message type and not a final fragement indicator if (textMessage != null) { throw JsrWebSocketMessages.MESSAGES.moreThanOneAnnotation(OnMessage.class); } Class boolClass = parameterTypes[booleanLocation]; textMessage = new BoundMethod(method, boolClass, true, maxMessageSize, new BoundSingleParameter(method, Session.class, true), new BoundSingleParameter(method, boolean.class, true), new BoundSingleParameter(booleanLocation, boolClass), createBoundPathParameters(method, paths, endpointClass)); messageHandled = true; } if (!messageHandled) { throw JsrWebSocketMessages.MESSAGES.couldNotFindMessageParameter(method); } } } c = c.getSuperclass(); } while (c != Object.class && c != null); return new AnnotatedEndpointFactory(endpointClass, onOpen, onClose, onError, textMessage, binaryMessage, pongMessage); } private static BoundPathParameters createBoundPathParameters(final Method method, Set paths, Class endpointClass) throws DeploymentException { return new BoundPathParameters(pathParams(method), method, endpointClass, paths); } private static String[] pathParams(final Method method) { String[] params = new String[method.getParameterCount()]; for (int i = 0; i < method.getParameterCount(); ++i) { PathParam param = getPathParam(method, i); if (param != null) { params[i] = param.value(); } } return params; } private static PathParam getPathParam(final Method method, final int parameter) { for (final Annotation annotation : method.getParameterAnnotations()[parameter]) { if (annotation.annotationType().equals(PathParam.class)) { return (PathParam) annotation; } } return null; } private static boolean hasAnnotation(Class annotationType, Annotation[] annotations) { for (Annotation annotation : annotations) { if (annotation.annotationType().equals(annotationType)) { return true; } } return false; } public AnnotatedEndpoint createInstance(InstanceHandle endpointInstance) { if(!endpointClass.isInstance(endpointInstance.getInstance())) { throw JsrWebSocketMessages.MESSAGES.endpointNotOfCorrectType(endpointInstance, endpointClass); } return new AnnotatedEndpoint(endpointInstance, OnOpen, OnClose, OnError, textMessage, binaryMessage, pongMessage); } /** * represents a parameter binding */ private static class BoundSingleParameter implements BoundParameter { private final int position; private final Class type; BoundSingleParameter(int position, final Class type) { this.position = position; this.type = type; } BoundSingleParameter(final Method method, final Class type, final boolean optional) { this.type = type; int pos = -1; for (int i = 0; i < method.getParameterCount(); ++i) { boolean pathParam = false; for (Annotation annotation : method.getParameterAnnotations()[i]) { if (annotation.annotationType().equals(PathParam.class)) { pathParam = true; break; } } if (pathParam) { continue; } if (method.getParameterTypes()[i].equals(type)) { if (pos != -1) { throw JsrWebSocketMessages.MESSAGES.moreThanOneParameterOfType(type, method); } pos = i; } } if (pos != -1) { position = pos; } else if (optional) { position = -1; } else { throw JsrWebSocketMessages.MESSAGES.parameterNotFound(type, method); } } public Set positions() { if (position == -1) { return Collections.emptySet(); } return Collections.singleton(position); } public void populate(final Object[] params, final Map, Object> value) { if (position == -1) { return; } params[position] = value.get(type); } @Override public Class getType() { return type; } } /** * represents a parameter binding */ private static class BoundPathParameters implements BoundParameter { private final Class endpointClass; private final Set paths; private final String[] positions; private final Encoding[] encoders; private final Class[] types; BoundPathParameters(final String[] positions, final Method method, Class endpointClass, Set paths) throws DeploymentException { this.positions = positions; this.endpointClass = endpointClass; this.paths = paths; this.encoders = new Encoding[positions.length]; this.types = new Class[positions.length]; for (int i = 0; i < positions.length; ++i) { Class type = method.getParameterTypes()[i]; Annotation[] annotations = method.getParameterAnnotations()[i]; for(int j = 0; j < annotations.length; ++j) { if(annotations[j] instanceof PathParam) { PathParam param = (PathParam) annotations[j]; if(!paths.contains(param.value())) { JsrWebSocketLogger.ROOT_LOGGER.pathTemplateNotFound(endpointClass, param, method, paths); } } } if (positions[i] == null || type == null || type == String.class) { continue; } if (EncodingFactory.DEFAULT.canEncodeText(type)) { encoders[i] = EncodingFactory.DEFAULT.createEncoding(EmptyEndpointConfig.INSTANCE); types[i] = type; } else { throw JsrWebSocketMessages.MESSAGES.couldNotFindDecoderForType(type, method); } } } public Set positions() { HashSet ret = new HashSet<>(); for (int i = 0; i < positions.length; ++i) { if (positions[i] != null) { ret.add(i); } } return ret; } public void populate(final Object[] params, final Map, Object> value) throws DecodeException { final Map data = (Map) value.get(Map.class); for (int i = 0; i < positions.length; ++i) { String name = positions[i]; if (name != null) { Encoding encoding = encoders[i]; if (encoding == null) { params[i] = data.get(name); } else { params[i] = encoding.decodeText(types[i], data.get(name)); } } } } @Override public Class getType() { return Map.class; } } } BoundMethod.java000066400000000000000000000116101420065311100342700ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.annotated; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.websocket.DeploymentException; import io.undertow.websockets.jsr.JsrWebSocketMessages; /** * A method with bound parameters */ final class BoundMethod { private final Method method; private final List parameters = new ArrayList<>(); private final Set paramTypes = new HashSet<>(); private final Class messageType; private final boolean decoderRequired; private final long maxMessageSize; BoundMethod(final Method method, final Class messageType, final boolean decoderRequired, long maxMessageSize, BoundParameter... params) throws DeploymentException { this.method = method; this.messageType = messageType; this.decoderRequired = decoderRequired; this.maxMessageSize = maxMessageSize; final Set allParams = new HashSet<>(); for (int i = 0; i < method.getParameterCount(); ++i) { allParams.add(i); paramTypes.add(method.getParameterTypes()[i]); } for (BoundParameter param : params) { parameters.add(param); allParams.removeAll(param.positions()); } if (!allParams.isEmpty()) { //first check to see if the user has accidentally used the wrong PathParam annotation //and if so throw a more informative error message boolean wrongAnnotation = false; for (int i = 0; i < method.getParameterAnnotations().length; ++i) { for (int j = 0; j < method.getParameterAnnotations()[i].length; ++j) { Annotation annotation = method.getParameterAnnotations()[i][j]; if (annotation.annotationType().getName().equals("javax.ws.rs.PathParam")) { wrongAnnotation = true; } } } if (wrongAnnotation) { throw JsrWebSocketMessages.MESSAGES.invalidParametersWithWrongAnnotation(method, allParams); } else { throw JsrWebSocketMessages.MESSAGES.invalidParameters(method, allParams); } } method.setAccessible(true); } public Object invoke(final Object instance, final Map, Object> values) throws Exception { final Object[] params = new Object[method.getParameterCount()]; for (BoundParameter param : parameters) { param.populate(params, values); } try { return method.invoke(instance, params); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { if(e.getCause() instanceof Exception) { throw (Exception)e.getCause(); } else { throw new RuntimeException(e.getCause()); } } } public boolean hasParameterType(final Class type) { return paramTypes.contains(type); } public Class getMessageType() { return messageType; } public boolean isDecoderRequired() { return decoderRequired; } public long getMaxMessageSize() { return maxMessageSize; } public boolean overrides(Method method) { if(!method.getName().equals(this.method.getName())) { return false; } if(!method.getReturnType().isAssignableFrom(this.method.getReturnType())) { return false; } if(method.getParameterCount() != this.method.getParameterCount()) { return false; } if(method.getParameterCount() == 0) { return true; } Class[] otherParameterTypes = this.method.getParameterTypes(); Class[] parameterTypes = method.getParameterTypes(); for(int i = 0; i < parameterTypes.length; ++i) { if(parameterTypes[i] != otherParameterTypes[i]) { return false; } } return true; } } BoundParameter.java000066400000000000000000000020601420065311100347670ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.annotated; import java.util.Map; import java.util.Set; import javax.websocket.DecodeException; /** * @author Stuart Douglas */ public interface BoundParameter { Set positions(); void populate(final Object[] params, final Map, Object> value) throws DecodeException; Class getType(); } DecoderUtils.java000066400000000000000000000033401420065311100344470ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.annotated; import java.util.List; import javax.websocket.Decoder; import javax.websocket.EndpointConfig; /** * @author Stuart Douglas */ public class DecoderUtils { /** * Gets a decoder for a given type. * * @param type The type * @param endpointConfiguration The endpoint configuration * @return A list of decoders, or null if no decoders exist */ public static List getDecodersForType(final Class type, final EndpointConfig endpointConfiguration) { // final List decoders = new ArrayList<>(); // for (final Decoder decoder : endpointConfiguration.getDecoders()) { // final Class clazz = ClassUtils.getDecoderType(decoder.getClass()); // if (type.isAssignableFrom(clazz)) { // decoders.add(decoder); // } // } // if (!decoders.isEmpty()) { // return decoders; // } return null; } private DecoderUtils() { } } EmptyEndpointConfig.java000066400000000000000000000027361420065311100360160ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.annotated; import java.util.Collections; import java.util.List; import java.util.Map; import javax.websocket.Decoder; import javax.websocket.Encoder; import javax.websocket.EndpointConfig; /** * @author Stuart Douglas */ class EmptyEndpointConfig implements EndpointConfig { public static EmptyEndpointConfig INSTANCE = new EmptyEndpointConfig(); private EmptyEndpointConfig() { } @Override public List> getEncoders() { return Collections.emptyList(); } @Override public List> getDecoders() { return Collections.emptyList(); } @Override public Map getUserProperties() { return Collections.emptyMap(); } } undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/handshake/000077500000000000000000000000001420065311100312465ustar00rootroot00000000000000ExchangeHandshakeRequest.java000066400000000000000000000062561420065311100367450ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/handshake/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.handshake; import java.net.URI; import java.security.Principal; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.websocket.server.HandshakeRequest; import io.undertow.websockets.spi.WebSocketHttpExchange; /** * {@link HandshakeRequest} which wraps a {@link io.undertow.websockets.spi.WebSocketHttpExchange} to act on it. * * @author Norman Maurer */ public final class ExchangeHandshakeRequest implements HandshakeRequest { private final WebSocketHttpExchange exchange; private Map> headers; public ExchangeHandshakeRequest(final WebSocketHttpExchange exchange) { this.exchange = exchange; } @Override public Map> getHeaders() { if (headers == null) { headers = exchange.getRequestHeaders(); } return headers; } @Override public Principal getUserPrincipal() { return exchange.getUserPrincipal(); } @Override public URI getRequestURI() { return URI.create(exchange.getRequestURI()); } @Override public boolean isUserInRole(String role) { return exchange.isUserInRole(role); } @Override public Object getHttpSession() { return exchange.getSession(); } @Override public Map> getParameterMap() { Map> requestParameters = new HashMap<>(); for(Map.Entry> e : exchange.getRequestParameters().entrySet()) { List list = requestParameters.get(e.getKey()); if(list == null) { requestParameters.put(e.getKey(), list = new ArrayList<>()); } list.addAll(e.getValue()); } Map pathParms = exchange.getAttachment(HandshakeUtil.PATH_PARAMS); if(pathParms != null) { for(Map.Entry e : pathParms.entrySet()) { List list = requestParameters.get(e.getKey()); if(list == null) { requestParameters.put(e.getKey(), list = new ArrayList<>()); } list.add(e.getValue()); } } return Collections.unmodifiableMap(requestParameters); } @Override public String getQueryString() { return exchange.getQueryString(); } } ExchangeHandshakeResponse.java000066400000000000000000000037501420065311100371070ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/handshake/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.handshake; import java.util.List; import java.util.Map; import java.util.TreeMap; import javax.websocket.HandshakeResponse; import io.undertow.websockets.spi.WebSocketHttpExchange; /** * {@link HandshakeResponse} which wraps a {@link io.undertow.websockets.spi.WebSocketHttpExchange} to act on it. * Once the processing of it is done {@link #update()} must be called to persist any changes * made. * * @author Norman Maurer */ public final class ExchangeHandshakeResponse implements HandshakeResponse { private final WebSocketHttpExchange exchange; private Map> headers; public ExchangeHandshakeResponse(final WebSocketHttpExchange exchange) { this.exchange = exchange; } @Override public Map> getHeaders() { if (headers == null) { headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); headers.putAll(exchange.getResponseHeaders()); } return headers; } /** * Persist all changes and update the wrapped {@link io.undertow.websockets.spi.WebSocketHttpExchange}. */ void update() { if (headers != null) { exchange.setResponseHeaders(headers); } } } HandshakeUtil.java000066400000000000000000000102251420065311100345560ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/handshake/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.handshake; import java.security.Principal; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import javax.websocket.Extension; import javax.websocket.server.ServerEndpointConfig; import io.undertow.util.AttachmentKey; import io.undertow.util.Headers; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.jsr.ConfiguredServerEndpoint; import io.undertow.websockets.spi.WebSocketHttpExchange; /** * Internal util class for handshaking * * @author Norman Maurer */ public final class HandshakeUtil { private static final String CONFIG_KEY = "ServerEndpointConfiguration"; public static final AttachmentKey> PATH_PARAMS = AttachmentKey.create(Map.class); public static final AttachmentKey PRINCIPAL = AttachmentKey.create(Principal.class); private HandshakeUtil() { } /** * Checks the orgin against the */ public static boolean checkOrigin(ServerEndpointConfig config, WebSocketHttpExchange exchange) { ServerEndpointConfig.Configurator c = config.getConfigurator(); return c.checkOrigin(exchange.getRequestHeader(Headers.ORIGIN_STRING)); } /** * Prepare for upgrade */ public static void prepareUpgrade(final ServerEndpointConfig config, final WebSocketHttpExchange exchange) { ExchangeHandshakeRequest request = new ExchangeHandshakeRequest(exchange); ExchangeHandshakeResponse response = new ExchangeHandshakeResponse(exchange); ServerEndpointConfig.Configurator c = config.getConfigurator(); c.modifyHandshake(config, request, response); response.update(); } /** * Set the {@link ConfiguredServerEndpoint} which is used to create the {@link WebSocketChannel}. */ public static void setConfig(WebSocketChannel channel, ConfiguredServerEndpoint config) { channel.setAttribute(CONFIG_KEY, config); } /** * Returns the {@link ConfiguredServerEndpoint} which was used while create the {@link WebSocketChannel}. */ public static ConfiguredServerEndpoint getConfig(WebSocketChannel channel) { return (ConfiguredServerEndpoint) channel.getAttribute(CONFIG_KEY); } static String selectSubProtocol(final ConfiguredServerEndpoint config, final String[] requestedSubprotocolArray) { if (config.getEndpointConfiguration().getConfigurator() != null) { return config.getEndpointConfiguration().getConfigurator().getNegotiatedSubprotocol(config.getEndpointConfiguration().getSubprotocols(), Arrays.asList(requestedSubprotocolArray)); } else { for (final String protocol : config.getEndpointConfiguration().getSubprotocols()) { for (String clientsupported : requestedSubprotocolArray) { if (protocol.equals(clientsupported)) { return protocol; } } } return null; } } static List selectExtensions(final ConfiguredServerEndpoint config, final List requestedExtensions) { if (config.getEndpointConfiguration().getConfigurator() != null) { return config.getEndpointConfiguration().getConfigurator().getNegotiatedExtensions(config.getExtensions(), requestedExtensions); } else { return Collections.emptyList(); } } } JsrHybi07Handshake.java000066400000000000000000000050111420065311100353570ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/handshake/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.handshake; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.protocol.version07.Hybi07Handshake; import io.undertow.websockets.jsr.ConfiguredServerEndpoint; import io.undertow.websockets.spi.WebSocketHttpExchange; import io.undertow.connector.ByteBufferPool; import org.xnio.StreamConnection; import java.util.Collections; /** * {@link Hybi07Handshake} sub-class which takes care of match against the {@link javax.websocket.server.ServerEndpointConfig} and * stored the config in the attributes for later usage. * * @author Norman Maurer */ public final class JsrHybi07Handshake extends Hybi07Handshake { private final ConfiguredServerEndpoint config; public JsrHybi07Handshake(ConfiguredServerEndpoint config) { super(Collections.emptySet(), false); this.config = config; } @Override protected void upgradeChannel(final WebSocketHttpExchange exchange, byte[] data) { HandshakeUtil.prepareUpgrade(config.getEndpointConfiguration(), exchange); super.upgradeChannel(exchange, data); } @Override public WebSocketChannel createChannel(WebSocketHttpExchange exchange, final StreamConnection c, final ByteBufferPool buffers) { WebSocketChannel channel = super.createChannel(exchange, c, buffers); HandshakeUtil.setConfig(channel, config); return channel; } @Override public boolean matches(WebSocketHttpExchange exchange) { return super.matches(exchange) && HandshakeUtil.checkOrigin(config.getEndpointConfiguration(), exchange); } @Override protected String supportedSubprotols(String[] requestedSubprotocolArray) { return HandshakeUtil.selectSubProtocol(config, requestedSubprotocolArray); } } JsrHybi08Handshake.java000066400000000000000000000050101420065311100353570ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/handshake/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.handshake; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.protocol.version08.Hybi08Handshake; import io.undertow.websockets.jsr.ConfiguredServerEndpoint; import io.undertow.websockets.spi.WebSocketHttpExchange; import io.undertow.connector.ByteBufferPool; import org.xnio.StreamConnection; import java.util.Collections; /** * {@link Hybi08Handshake} sub-class which takes care of match against the {@link javax.websocket.server.ServerEndpointConfig} and * stored the config in the attributes for later usage. * * @author Norman Maurer */ public final class JsrHybi08Handshake extends Hybi08Handshake { private final ConfiguredServerEndpoint config; public JsrHybi08Handshake(ConfiguredServerEndpoint config) { super(Collections.emptySet(), false); this.config = config; } @Override protected void upgradeChannel(final WebSocketHttpExchange exchange, byte[] data) { HandshakeUtil.prepareUpgrade(config.getEndpointConfiguration(), exchange); super.upgradeChannel(exchange, data); } @Override public WebSocketChannel createChannel(WebSocketHttpExchange exchange, final StreamConnection c, final ByteBufferPool buffers) { WebSocketChannel channel = super.createChannel(exchange, c, buffers); HandshakeUtil.setConfig(channel, config); return channel; } @Override public boolean matches(WebSocketHttpExchange exchange) { return super.matches(exchange) && HandshakeUtil.checkOrigin(config.getEndpointConfiguration(), exchange); } @Override protected String supportedSubprotols(String[] requestedSubprotocolArray) { return HandshakeUtil.selectSubProtocol(config, requestedSubprotocolArray); } } JsrHybi13Handshake.java000066400000000000000000000105251420065311100353620ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/handshake/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.handshake; import io.undertow.websockets.WebSocketExtension; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.protocol.version13.Hybi13Handshake; import io.undertow.websockets.extensions.ExtensionHandshake; import io.undertow.websockets.jsr.ConfiguredServerEndpoint; import io.undertow.websockets.jsr.ExtensionImpl; import io.undertow.websockets.spi.WebSocketHttpExchange; import io.undertow.connector.ByteBufferPool; import org.xnio.StreamConnection; import javax.websocket.Extension; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** * {@link Hybi13Handshake} sub-class which takes care of match against the {@link javax.websocket.server.ServerEndpointConfig} and * stored the config in the attributes for later usage. * * @author Norman Maurer */ public final class JsrHybi13Handshake extends Hybi13Handshake { private final ConfiguredServerEndpoint config; public JsrHybi13Handshake(ConfiguredServerEndpoint config) { super(Collections.emptySet(), false); this.config = config; } @Override protected void upgradeChannel(final WebSocketHttpExchange exchange, byte[] data) { HandshakeUtil.prepareUpgrade(config.getEndpointConfiguration(), exchange); super.upgradeChannel(exchange, data); } @Override public WebSocketChannel createChannel(WebSocketHttpExchange exchange, final StreamConnection c, final ByteBufferPool buffers) { WebSocketChannel channel = super.createChannel(exchange, c, buffers); HandshakeUtil.setConfig(channel, config); return channel; } @Override public boolean matches(WebSocketHttpExchange exchange) { return super.matches(exchange) && HandshakeUtil.checkOrigin(config.getEndpointConfiguration(), exchange); } @Override protected String supportedSubprotols(String[] requestedSubprotocolArray) { return HandshakeUtil.selectSubProtocol(config, requestedSubprotocolArray); } @Override protected List selectedExtension(List extensionList) { List ext = new ArrayList<>(); for(WebSocketExtension i : extensionList) { ext.add(ExtensionImpl.create(i)); } List selected = HandshakeUtil.selectExtensions(config, ext); if(selected == null) { return Collections.emptyList(); } Map extensionMap = new HashMap<>(); for(ExtensionHandshake availible : availableExtensions) { extensionMap.put(availible.getName(), availible); } List ret = new ArrayList<>(); List accepted = new ArrayList<>(); for(Extension i : selected) { ExtensionHandshake handshake = extensionMap.get(i.getName()); if(handshake == null) { continue; //should not happen } List parameters = new ArrayList<>(); for(Extension.Parameter p : i.getParameters()) { parameters.add(new WebSocketExtension.Parameter(p.getName(), p.getValue())); } if(!handshake.isIncompatible(accepted)) { WebSocketExtension accept = handshake.accept(new WebSocketExtension(i.getName(), parameters)); if (accept != null) { ret.add(accept); accepted.add(handshake); } } } return ret; } } undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/util/000077500000000000000000000000001420065311100302755ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/java/io/undertow/websockets/jsr/util/ClassUtils.java000066400000000000000000000136151420065311100332340ustar00rootroot00000000000000/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.util; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.ArrayList; import java.util.Collections; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import javax.websocket.Decoder; import javax.websocket.Encoder; import javax.websocket.MessageHandler; import io.undertow.websockets.jsr.JsrWebSocketMessages; /** * @author Norman Maurer */ public final class ClassUtils { private ClassUtils() { } /** * Returns a map of all supported message types by the given handler class. * The key of the map is the supported message type; the value indicates * whether it is a partial message handler or not. * * @return a map of all supported message types by the given handler class. */ public static Map, Boolean> getHandlerTypes(Class clazz) { Map, Boolean> types = new IdentityHashMap<>(2); for (Class c = clazz; c != Object.class; c = c.getSuperclass()) { exampleGenericInterfaces(types, c, clazz); } if (types.isEmpty()) { throw JsrWebSocketMessages.MESSAGES.unknownHandlerType(clazz); } return types; } private static void exampleGenericInterfaces(Map, Boolean> types, Class c, Class actualClass) { for (Type type : c.getGenericInterfaces()) { if (type instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) type; Type rawType = pt.getRawType(); if (rawType == MessageHandler.Whole.class) { Type messageType = pt.getActualTypeArguments()[0]; types.put(resolvePotentialTypeVariable(messageType, c, actualClass), Boolean.FALSE); } else if (rawType == MessageHandler.Partial.class) { Type messageType = pt.getActualTypeArguments()[0]; types.put(resolvePotentialTypeVariable(messageType, c, actualClass), Boolean.TRUE); } else if(rawType instanceof Class) { Class rawClass = (Class) rawType; if(rawClass.getGenericInterfaces() != null) { exampleGenericInterfaces(types, rawClass, actualClass); } } } else if(type instanceof Class) { exampleGenericInterfaces(types, (Class)type, actualClass); } } } private static Class resolvePotentialTypeVariable(Type messageType, Class c, Class actualClass) { if(messageType instanceof Class) { return (Class) messageType; } else if(messageType instanceof TypeVariable) { Type var = messageType; int tvpos = 0; List parents = new ArrayList<>(); Class i = actualClass; while (i != c) { parents.add(i); i = i.getSuperclass(); } Collections.reverse(parents); for(Class ptype : parents) { Type type = ptype.getGenericSuperclass(); if(!(type instanceof ParameterizedType)) { throw JsrWebSocketMessages.MESSAGES.unknownHandlerType(actualClass); } ParameterizedType pt = (ParameterizedType) type; if(tvpos == -1) { TypeVariable[] typeParameters = ((Class) pt.getRawType()).getTypeParameters(); for(int j = 0; j < typeParameters.length; ++j) { TypeVariable tp = typeParameters[j]; if(tp.getName().equals(((TypeVariable)var).getName())) { tvpos = j; break; } } } var = pt.getActualTypeArguments()[tvpos]; if(var instanceof Class) { return (Class) var; } tvpos = -1; } return (Class) var; } else { throw JsrWebSocketMessages.MESSAGES.unknownHandlerType(actualClass); } } /** * Returns the Object type for which the {@link Encoder} can be used. */ public static Class getEncoderType(Class clazz) { Method[] methods = clazz.getMethods(); for (Method m : methods) { if ("encode".equals(m.getName()) && !m.isBridge()) { return m.getParameterTypes()[0]; } } throw JsrWebSocketMessages.MESSAGES.unknownEncoderType(clazz); } /** * Returns the Object type for which the {@link Encoder} can be used. */ public static Class getDecoderType(Class clazz) { Method[] methods = clazz.getMethods(); for (Method m : methods) { if ("decode".equals(m.getName()) && !m.isBridge()) { return m.getReturnType(); } } throw JsrWebSocketMessages.MESSAGES.couldNotDetermineDecoderTypeFor(clazz); } } undertow-2.2.16.Final/websockets-jsr/src/main/resources/000077500000000000000000000000001420065311100231645ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/resources/META-INF/000077500000000000000000000000001420065311100243245ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/resources/META-INF/services/000077500000000000000000000000001420065311100261475ustar00rootroot00000000000000io.undertow.servlet.ServletExtension000066400000000000000000000000451420065311100352720ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/resources/META-INF/servicesio.undertow.websockets.jsr.Bootstrap io.undertow.websockets.jsr.WebsocketClientSslProvider000066400000000000000000000000741420065311100405170ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/resources/META-INF/servicesio.undertow.websockets.jsr.DefaultWebSocketClientSslProviderjavax.websocket.ContainerProvider000066400000000000000000000000651420065311100345460ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/resources/META-INF/servicesio.undertow.websockets.jsr.UndertowContainerProvider javax.websocket.server.ServerEndpointConfig$Configurator000066400000000000000000000000711420065311100411370ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/main/resources/META-INF/servicesio.undertow.websockets.jsr.DefaultContainerConfigurator undertow-2.2.16.Final/websockets-jsr/src/test/000077500000000000000000000000001420065311100212055ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/000077500000000000000000000000001420065311100221265ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/000077500000000000000000000000001420065311100225355ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/000077500000000000000000000000001420065311100244045ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/000077500000000000000000000000001420065311100265555ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/000077500000000000000000000000001420065311100273535ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/000077500000000000000000000000001420065311100303325ustar00rootroot00000000000000AddEndpointServlet.java000066400000000000000000000041131420065311100346530ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.websocket.DeploymentException; import javax.websocket.server.ServerContainer; import javax.websocket.server.ServerEndpointConfig; import java.io.IOException; /** * @author Stuart Douglas */ public class AddEndpointServlet implements Servlet { @Override public void init(ServletConfig c) throws ServletException { String websocketPath = "/foo"; ServerEndpointConfig config = ServerEndpointConfig.Builder.create(ProgramaticEndpoint.class, websocketPath).build(); ServerContainer serverContainer = (ServerContainer) c.getServletContext().getAttribute("javax.websocket.server.ServerContainer"); try { serverContainer.addEndpoint(config); } catch (DeploymentException ex) { throw new ServletException("Error deploying websocket endpoint:", ex); } } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { } @Override public String getServletInfo() { return null; } @Override public void destroy() { } } BinaryEndpointServlet.java000066400000000000000000000041561420065311100354160ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test; import java.io.IOException; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.websocket.DeploymentException; import javax.websocket.server.ServerContainer; import javax.websocket.server.ServerEndpointConfig; /** * @author Andrej Golovnin * @author Stuart Douglas */ public class BinaryEndpointServlet implements Servlet { @Override public void init(ServletConfig c) throws ServletException { String websocketPath = "/partial"; ServerEndpointConfig config = ServerEndpointConfig.Builder.create(BinaryPartialEndpoint.class, websocketPath).build(); ServerContainer serverContainer = (ServerContainer) c.getServletContext().getAttribute("javax.websocket.server.ServerContainer"); try { serverContainer.addEndpoint(config); } catch (DeploymentException ex) { throw new ServletException("Error deploying websocket endpoint:", ex); } } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { } @Override public String getServletInfo() { return null; } @Override public void destroy() { } } BinaryEndpointTest.java000066400000000000000000000136111420065311100347050ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test; import java.io.IOException; import java.net.URI; import java.nio.ByteBuffer; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLContext; import javax.servlet.ServletException; import javax.websocket.ClientEndpointConfig; import javax.websocket.CloseReason; import javax.websocket.ContainerProvider; import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.MessageHandler; import javax.websocket.Session; import io.undertow.server.handlers.RequestDumpingHandler; import io.undertow.servlet.Servlets; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.websockets.jsr.DefaultWebSocketClientSslProvider; import io.undertow.websockets.jsr.ServerWebSocketContainer; import io.undertow.websockets.jsr.WebSocketDeploymentInfo; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.runner.RunWith; import static org.junit.Assert.assertTrue; /** * @author Andrej Golovnin * @author Norman Maurer */ @RunWith(DefaultServer.class) @HttpOneOnly public class BinaryEndpointTest { private static ServerWebSocketContainer deployment; private static DeploymentManager deploymentManager; private static byte[] bytes; @BeforeClass public static void setup() throws Exception { bytes = new byte[256 * 1024]; new Random().nextBytes(bytes); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(BinaryEndpointTest.class.getClassLoader()) .setContextPath("/") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .addServlet(Servlets.servlet("bin", BinaryEndpointServlet.class).setLoadOnStartup(100)) .addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, new WebSocketDeploymentInfo() .setBuffers(DefaultServer.getBufferPool()) .setWorker(DefaultServer.getWorkerSupplier()) .addListener(serverContainer -> deployment = serverContainer) ) .setDeploymentName("servletContext.war"); deploymentManager = container.addDeployment(builder); deploymentManager.deploy(); DefaultServer.setRootHandler(new RequestDumpingHandler(deploymentManager.start())); DefaultServer.startSSLServer(); } @AfterClass public static void after() throws IOException, ServletException { if (deployment != null) { deployment.close(); deployment = null; } if (deploymentManager != null) { deploymentManager.stop(); deploymentManager.undeploy(); } DefaultServer.stopSSLServer(); } @org.junit.Test public void testBytesOnMessage() throws Exception { SSLContext context = DefaultServer.getClientSSLContext(); ProgramaticClientEndpoint endpoint = new ProgramaticClientEndpoint(); ClientEndpointConfig clientEndpointConfig = ClientEndpointConfig.Builder.create().build(); clientEndpointConfig.getUserProperties().put(DefaultWebSocketClientSslProvider.SSL_CONTEXT, context); ContainerProvider.getWebSocketContainer().connectToServer(endpoint, clientEndpointConfig, new URI("wss://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostSSLPort("default") + "/partial")); Assert.assertArrayEquals(bytes, endpoint.getResponses().poll(15, TimeUnit.SECONDS)); endpoint.session.close(); assertTrue(endpoint.closeLatch.await(10, TimeUnit.SECONDS)); } public static class ProgramaticClientEndpoint extends Endpoint { private final LinkedBlockingDeque responses = new LinkedBlockingDeque<>(); final CountDownLatch closeLatch = new CountDownLatch(1); volatile Session session; @Override public void onOpen(Session session, EndpointConfig config) { this.session = session; // Copy, because masking will modify this data byte[] mutableBytes = new byte[bytes.length]; System.arraycopy(bytes,0,mutableBytes,0,bytes.length); session.getAsyncRemote().sendBinary(ByteBuffer.wrap(mutableBytes)); session.addMessageHandler(new MessageHandler.Whole() { @Override public void onMessage(byte[] message) { responses.add(message); } }); } @Override public void onClose(Session session, CloseReason closeReason) { closeLatch.countDown(); } public LinkedBlockingDeque getResponses() { return responses; } } } BinaryPartialEndpoint.java000066400000000000000000000054221420065311100353630ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.MessageHandler; import javax.websocket.Session; import io.undertow.testutils.DefaultServer; /** * @author Andrej Golovnin */ public final class BinaryPartialEndpoint extends Endpoint { @Override public void onOpen(final Session session, EndpointConfig config) { session.addMessageHandler(new MessageHandler.Partial() { private ByteArrayOutputStream buffer; @Override public void onMessage(byte[] bytes, boolean last) { if (last) { if (buffer == null) { onRequest(bytes); } else { try { buffer(bytes); byte[] tmp = buffer.toByteArray(); onRequest(tmp); } finally { buffer = null; } } } else { buffer(bytes); } } private void onRequest(final byte[] bytes) { // Just return the received bytes for the test DefaultServer.getWorker().execute(new Runnable() { @Override public void run() { try { session.getBasicRemote().sendBinary( ByteBuffer.wrap(bytes)); } catch (IOException e) { throw new IllegalStateException(e); } } }); } private void buffer(byte[] data) { if (buffer == null) { buffer = new ByteArrayOutputStream(8096); } buffer.write(data, 0, data.length); } }); } } ClassUtilsTest.java000066400000000000000000000144131420065311100340470ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test; import io.undertow.testutils.category.UnitTest; import io.undertow.websockets.jsr.util.ClassUtils; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import java.io.IOException; import java.io.OutputStream; import java.io.Writer; import java.nio.ByteBuffer; import java.util.List; import java.util.Map; import javax.websocket.EncodeException; import javax.websocket.Encoder; import javax.websocket.EndpointConfig; import javax.websocket.MessageHandler; /** * @author Norman Maurer */ @Category(UnitTest.class) public class ClassUtilsTest { @Test public void testExtractHandlerType() { Map, Boolean> types = ClassUtils.getHandlerTypes(FinalIm.class); Assert.assertEquals(1, types.size()); Assert.assertTrue(types.containsKey(ByteBuffer.class)); types = ClassUtils.getHandlerTypes(ByteBufferFromSuperClassEncoder.class); Assert.assertEquals(1, types.size()); Assert.assertTrue(types.containsKey(ByteBuffer.class)); types = ClassUtils.getHandlerTypes(MessageHandlerImpl.class); Assert.assertEquals(1, types.size()); Assert.assertTrue(types.containsKey(ByteBuffer.class)); Assert.assertFalse(types.get(ByteBuffer.class)); types = ClassUtils.getHandlerTypes(AsyncMessageHandlerImpl.class); Assert.assertEquals(1, types.size()); Assert.assertTrue(types.containsKey(ByteBuffer.class)); Assert.assertTrue(types.get(ByteBuffer.class)); types = ClassUtils.getHandlerTypes(ComplexMessageHandlerImpl.class); Assert.assertEquals(2, types.size()); Assert.assertTrue(types.containsKey(ByteBuffer.class)); Assert.assertFalse(types.get(ByteBuffer.class)); Assert.assertTrue(types.containsKey(String.class)); Assert.assertTrue(types.get(String.class)); Assert.assertFalse(types.containsKey(byte[].class)); } @Test public void testExtractEncoderType() { Class clazz = ClassUtils.getEncoderType(BinaryEncoder.class); Assert.assertEquals(String.class, clazz); Class clazz2 = ClassUtils.getEncoderType(TextEncoder.class); Assert.assertEquals(String.class, clazz2); Class clazz3 = ClassUtils.getEncoderType(TextStreamEncoder.class); Assert.assertEquals(String.class, clazz3); Class clazz4 = ClassUtils.getEncoderType(BinaryStreamEncoder.class); Assert.assertEquals(String.class, clazz4); } private static class MessageHandlerImpl implements MessageHandler.Whole { @Override public void onMessage(ByteBuffer message) { // NOP } } private static final class AsyncMessageHandlerImpl implements MessageHandler.Partial { @Override public void onMessage(final ByteBuffer partialMessage, final boolean last) { } } private static class DummyHandlerImpl extends MessageHandlerImpl { // NOP } private static final class ComplexMessageHandlerImpl extends DummyHandlerImpl implements MessageHandler.Partial { @Override public void onMessage(String partialMessage, boolean last) { // NOP } public void onMessage(byte[] bytes, boolean last) { // NOP } } private static class ParamSuperclassEncoder implements MessageHandler.Partial { @Override public void onMessage(final T partialMessage, final boolean last) { } } private static final class ByteBufferFromSuperClassEncoder extends ParamSuperclassEncoder { } private static final class BinaryEncoder implements Encoder.Binary { @Override public ByteBuffer encode(String object) throws EncodeException { throw new UnsupportedOperationException(); } @Override public void init(final EndpointConfig config) { } @Override public void destroy() { } } private static class Im1 extends ParamSuperclassEncoder { } private static class Im2 extends Im1, Z, Y, Integer> { } private static final class FinalIm extends Im2 { } private static final class TextEncoder implements Encoder.Text { @Override public String encode(String object) throws EncodeException { throw new UnsupportedOperationException(); } @Override public void init(final EndpointConfig config) { } @Override public void destroy() { } } private static final class TextStreamEncoder implements Encoder.TextStream { @Override public void encode(String object, Writer writer) throws EncodeException, IOException { throw new UnsupportedOperationException(); } @Override public void init(final EndpointConfig config) { } @Override public void destroy() { } } private static final class BinaryStreamEncoder implements Encoder.BinaryStream { @Override public void encode(String object, OutputStream stream) throws EncodeException, IOException { throw new UnsupportedOperationException(); } @Override public void init(final EndpointConfig config) { } @Override public void destroy() { } } } JsrWebSocketServer07Test.java000066400000000000000000001054371420065311100356730ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test; import java.io.IOException; import java.io.OutputStream; import java.io.Writer; import java.net.URI; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import javax.websocket.CloseReason; import javax.websocket.ContainerProvider; import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.MessageHandler; import javax.websocket.SendHandler; import javax.websocket.SendResult; import javax.websocket.Session; import javax.websocket.server.ServerEndpointConfig; import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketVersion; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.AjpIgnore; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.util.NetworkUtils; import io.undertow.websockets.jsr.JsrWebSocketFilter; import io.undertow.websockets.jsr.ServerWebSocketContainer; import io.undertow.websockets.jsr.UndertowSession; import io.undertow.websockets.jsr.test.annotated.AnnotatedClientEndpoint; import io.undertow.websockets.utils.FrameChecker; import io.undertow.websockets.utils.WebSocketTestClient; import org.junit.After; import org.junit.Assert; import org.junit.runner.RunWith; import org.xnio.FutureResult; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.xnio.IoFuture.Status.DONE; /** * @author Norman Maurer */ @RunWith(DefaultServer.class) @AjpIgnore @HttpOneOnly public class JsrWebSocketServer07Test { private static DeploymentManager deploymentManager; @org.junit.Test public void testBinaryWithByteBuffer() throws Exception { final byte[] payload = "payload".getBytes(); final AtomicReference cause = new AtomicReference<>(); final AtomicBoolean connected = new AtomicBoolean(false); final FutureResult latch = new FutureResult<>(); class TestEndPoint extends Endpoint { @Override public void onOpen(final Session session, EndpointConfig config) { connected.set(true); session.addMessageHandler(new MessageHandler.Whole() { @Override public void onMessage(ByteBuffer message) { ByteBuffer buf = ByteBuffer.allocate(message.remaining()); buf.put(message); buf.flip(); session.getAsyncRemote().sendBinary(buf); } }); } } ServerWebSocketContainer builder = new ServerWebSocketContainer(TestClassIntrospector.INSTANCE, DefaultServer.getWorkerSupplier(), DefaultServer.getBufferPool(), Collections.emptyList(), false, false); builder.addEndpoint(ServerEndpointConfig.Builder.create(TestEndPoint.class, "/").configurator(new InstanceConfigurator(new TestEndPoint())).build()); deployServlet(builder); WebSocketTestClient client = new WebSocketTestClient(getVersion(), new URI("ws://" + NetworkUtils.formatPossibleIpv6Address(DefaultServer.getHostAddress("default")) + ":" + DefaultServer.getHostPort("default") + "/")); client.connect(); client.send(new BinaryWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(BinaryWebSocketFrame.class, payload, latch)); //FIXME UNDERTOW-1862 assertEquals(DONE, latch.getIoFuture().await()); latch.getIoFuture().await(); assertNull(cause.get()); client.destroy(); } @org.junit.Test public void testBinaryWithByteArray() throws Exception { final byte[] payload = "payload".getBytes(); final AtomicReference cause = new AtomicReference<>(); final AtomicBoolean connected = new AtomicBoolean(false); final FutureResult latch = new FutureResult<>(); class TestEndPoint extends Endpoint { @Override public void onOpen(final Session session, EndpointConfig config) { connected.set(true); session.addMessageHandler(new MessageHandler.Whole() { @Override public void onMessage(byte[] message) { session.getAsyncRemote().sendBinary(ByteBuffer.wrap(message.clone())); } }); } } ServerWebSocketContainer builder = new ServerWebSocketContainer(TestClassIntrospector.INSTANCE, DefaultServer.getWorkerSupplier(), DefaultServer.getBufferPool(), Collections.emptyList(), false, false); builder.addEndpoint(ServerEndpointConfig.Builder.create(TestEndPoint.class, "/").configurator(new InstanceConfigurator(new TestEndPoint())).build()); deployServlet(builder); WebSocketTestClient client = new WebSocketTestClient(getVersion(), new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/")); client.connect(); client.send(new BinaryWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(BinaryWebSocketFrame.class, payload, latch)); //FIXME UNDERTOW-1862 assertEquals(DONE, latch.getIoFuture().await()); latch.getIoFuture().await(); assertNull(cause.get()); client.destroy(); } @org.junit.Test public void testText() throws Exception { final byte[] payload = "payload".getBytes(); final AtomicReference cause = new AtomicReference<>(); final AtomicBoolean connected = new AtomicBoolean(false); final FutureResult latch = new FutureResult<>(); class TestEndPoint extends Endpoint { @Override public void onOpen(final Session session, EndpointConfig config) { connected.set(true); session.addMessageHandler(new MessageHandler.Whole() { @Override public void onMessage(String message) { session.getAsyncRemote().sendText(message); } }); } } ServerWebSocketContainer builder = new ServerWebSocketContainer(TestClassIntrospector.INSTANCE, DefaultServer.getWorkerSupplier(), DefaultServer.getBufferPool(), Collections.emptyList(), false, false); builder.addEndpoint(ServerEndpointConfig.Builder.create(TestEndPoint.class, "/").configurator(new InstanceConfigurator(new TestEndPoint())).build()); deployServlet(builder); WebSocketTestClient client = new WebSocketTestClient(getVersion(), new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/")); client.connect(); client.send(new TextWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(TextWebSocketFrame.class, payload, latch)); //FIXME UNDERTOW-1862 assertEquals(DONE, latch.getIoFuture().await()); latch.getIoFuture().await(); assertNull(cause.get()); client.destroy(); } @org.junit.Test public void testBinaryWithByteBufferByCompletion() throws Exception { final byte[] payload = "payload".getBytes(); final AtomicReference sendResult = new AtomicReference<>(); final AtomicBoolean connected = new AtomicBoolean(false); final FutureResult latch = new FutureResult<>(); final FutureResult latch2 = new FutureResult<>(); class TestEndPoint extends Endpoint { @Override public void onOpen(final Session session, EndpointConfig config) { connected.set(true); session.addMessageHandler(new MessageHandler.Whole() { @Override public void onMessage(ByteBuffer message) { ByteBuffer buf = ByteBuffer.allocate(message.remaining()); buf.put(message); buf.flip(); session.getAsyncRemote().sendBinary(buf, new SendHandler() { @Override public void onResult(SendResult result) { sendResult.set(result); if (result.getException() != null) { latch2.setException(new IOException(result.getException())); } else { latch2.setResult(null); } } }); } }); } } ServerWebSocketContainer builder = new ServerWebSocketContainer(TestClassIntrospector.INSTANCE, DefaultServer.getWorkerSupplier(), DefaultServer.getBufferPool(), Collections.emptyList(), false, false); builder.addEndpoint(ServerEndpointConfig.Builder.create(TestEndPoint.class, "/").configurator(new InstanceConfigurator(new TestEndPoint())).build()); deployServlet(builder); WebSocketTestClient client = new WebSocketTestClient(getVersion(), new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/")); client.connect(); client.send(new BinaryWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(BinaryWebSocketFrame.class, payload, latch)); //FIXME UNDERTOW-1862 assertEquals(DONE, latch.getIoFuture().await()); latch.getIoFuture().await(); assertEquals(DONE, latch2.getIoFuture().await()); SendResult result = sendResult.get(); Assert.assertNotNull(result); assertNull(result.getException()); client.destroy(); } @org.junit.Test public void testTextByCompletion() throws Exception { final byte[] payload = "payload".getBytes(); final AtomicReference sendResult = new AtomicReference<>(); final AtomicBoolean connected = new AtomicBoolean(false); final FutureResult latch = new FutureResult<>(); final FutureResult latch2 = new FutureResult<>(); class TestEndPoint extends Endpoint { @Override public void onOpen(final Session session, EndpointConfig config) { connected.set(true); session.addMessageHandler(new MessageHandler.Whole() { @Override public void onMessage(String message) { session.getAsyncRemote().sendText(message, result -> { sendResult.set(result); if (result.getException() != null) { latch2.setException(new IOException(result.getException())); } else { latch2.setResult(null); } }); } }); } } ServerWebSocketContainer builder = new ServerWebSocketContainer(TestClassIntrospector.INSTANCE, DefaultServer.getWorkerSupplier(), DefaultServer.getBufferPool(), Collections.emptyList(), false, false); builder.addEndpoint(ServerEndpointConfig.Builder.create(TestEndPoint.class, "/").configurator(new InstanceConfigurator(new TestEndPoint())).build()); deployServlet(builder); WebSocketTestClient client = new WebSocketTestClient(getVersion(), new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/")); client.connect(); client.send(new TextWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(TextWebSocketFrame.class, payload, latch)); //FIXME UNDERTOW-1862 assertEquals(DONE, latch.getIoFuture().await()); latch.getIoFuture().await(); assertEquals(DONE, latch2.getIoFuture().await()); SendResult result = sendResult.get(); Assert.assertNotNull(result); assertNull(result.getException()); client.destroy(); } @org.junit.Test public void testBinaryWithByteBufferByFuture() throws Exception { final byte[] payload = "payload".getBytes(); final AtomicReference> sendResult = new AtomicReference<>(); final AtomicBoolean connected = new AtomicBoolean(false); final FutureResult latch = new FutureResult<>(); class TestEndPoint extends Endpoint { @Override public void onOpen(final Session session, EndpointConfig config) { connected.set(true); session.addMessageHandler(new MessageHandler.Whole() { @Override public void onMessage(ByteBuffer message) { ByteBuffer buf = ByteBuffer.allocate(message.remaining()); buf.put(message); buf.flip(); sendResult.set(session.getAsyncRemote().sendBinary(buf)); } }); } } ServerWebSocketContainer builder = new ServerWebSocketContainer(TestClassIntrospector.INSTANCE, DefaultServer.getWorkerSupplier(), DefaultServer.getBufferPool(), Collections.emptyList(), false, false); builder.addEndpoint(ServerEndpointConfig.Builder.create(TestEndPoint.class, "/").configurator(new InstanceConfigurator(new TestEndPoint())).build()); deployServlet(builder); WebSocketTestClient client = new WebSocketTestClient(getVersion(), new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/")); client.connect(); client.send(new BinaryWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(BinaryWebSocketFrame.class, payload, latch)); //FIXME UNDERTOW-1862 assertEquals(DONE, latch.getIoFuture().await()); latch.getIoFuture().get(); Future result = sendResult.get(); /* FIXME result.wait(4000); assertTrue(result.isDone());*/ client.destroy(); } @org.junit.Test public void testTextByFuture() throws Exception { final byte[] payload = "payload".getBytes(); final AtomicReference> sendResult = new AtomicReference<>(); final AtomicBoolean connected = new AtomicBoolean(false); final FutureResult latch = new FutureResult<>(); class TestEndPoint extends Endpoint { @Override public void onOpen(final Session session, EndpointConfig config) { connected.set(true); session.addMessageHandler(new MessageHandler.Whole() { @Override public void onMessage(String message) { sendResult.set(session.getAsyncRemote().sendText(message)); } }); } } ServerWebSocketContainer builder = new ServerWebSocketContainer(TestClassIntrospector.INSTANCE, DefaultServer.getWorkerSupplier(), DefaultServer.getBufferPool(), Collections.emptyList(), false, false); builder.addEndpoint(ServerEndpointConfig.Builder.create(TestEndPoint.class, "/").configurator(new InstanceConfigurator(new TestEndPoint())).build()); deployServlet(builder); WebSocketTestClient client = new WebSocketTestClient(getVersion(), new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/")); client.connect(); client.send(new TextWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(TextWebSocketFrame.class, payload, latch)); //FIXME UNDERTOW-1862 assertEquals(DONE, latch.getIoFuture().await()); latch.getIoFuture().await(); sendResult.get(); /* FIXME .wait(4000); assertTrue(sendResult.get().isDone());*/ client.destroy(); } @org.junit.Test public void testBinaryWithByteArrayUsingStream() throws Exception { final byte[] payload = "payload".getBytes(); final AtomicReference cause = new AtomicReference<>(); final AtomicBoolean connected = new AtomicBoolean(false); final FutureResult latch = new FutureResult<>(); class TestEndPoint extends Endpoint { @Override public void onOpen(final Session session, EndpointConfig config) { connected.set(true); session.addMessageHandler(new MessageHandler.Whole() { @Override public void onMessage(final byte[] message) { DefaultServer.getWorker().execute(() -> { try { OutputStream out = session.getBasicRemote().getSendStream(); out.write(message); out.flush(); out.close(); } catch (IOException e) { e.printStackTrace(); cause.set(e); latch.setException(e); } }); } }); } } ServerWebSocketContainer builder = new ServerWebSocketContainer(TestClassIntrospector.INSTANCE, DefaultServer.getWorkerSupplier(), DefaultServer.getBufferPool(), Collections.emptyList(), false, false); builder.addEndpoint(ServerEndpointConfig.Builder.create(TestEndPoint.class, "/").configurator(new InstanceConfigurator(new TestEndPoint())).build()); deployServlet(builder); WebSocketTestClient client = new WebSocketTestClient(getVersion(), new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/")); client.connect(); client.send(new BinaryWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(BinaryWebSocketFrame.class, payload, latch)); //FIXME UNDERTOW-1862 assertEquals(DONE, latch.getIoFuture().await()); latch.getIoFuture().await(); assertNull(cause.get()); client.destroy(); } @org.junit.Test public void testTextUsingWriter() throws Exception { final byte[] payload = "payload".getBytes(); final AtomicReference cause = new AtomicReference<>(); final AtomicBoolean connected = new AtomicBoolean(false); final FutureResult latch = new FutureResult<>(); class TestEndPoint extends Endpoint { @Override public void onOpen(final Session session, EndpointConfig config) { connected.set(true); session.addMessageHandler(new MessageHandler.Whole() { @Override public void onMessage(final String message) { DefaultServer.getWorker().execute(() -> { try { Writer writer = session.getBasicRemote().getSendWriter(); writer.write(message); writer.close(); } catch (IOException e) { e.printStackTrace(); cause.set(e); latch.setException(e); } }); } }); } } ServerWebSocketContainer builder = new ServerWebSocketContainer(TestClassIntrospector.INSTANCE, DefaultServer.getWorkerSupplier(), DefaultServer.getBufferPool(), Collections.emptyList(), false, false); builder.addEndpoint(ServerEndpointConfig.Builder.create(TestEndPoint.class, "/").configurator(new InstanceConfigurator(new TestEndPoint())).build()); deployServlet(builder); WebSocketTestClient client = new WebSocketTestClient(getVersion(), new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/")); client.connect(); client.send(new TextWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(TextWebSocketFrame.class, payload, latch)); //FIXME UNDERTOW-1862 assertEquals(DONE, latch.getIoFuture().await()); latch.getIoFuture().await(); assertNull(cause.get()); client.destroy(); } @org.junit.Test public void testPingPong() throws Exception { final byte[] payload = "payload".getBytes(); final AtomicReference cause = new AtomicReference<>(); final AtomicBoolean connected = new AtomicBoolean(false); final FutureResult latch = new FutureResult<>(); class TestEndPoint extends Endpoint { @Override public void onOpen(final Session session, EndpointConfig config) { connected.set(true); } } ServerWebSocketContainer builder = new ServerWebSocketContainer(TestClassIntrospector.INSTANCE, DefaultServer.getWorkerSupplier(), DefaultServer.getBufferPool(), Collections.emptyList(), false, false); builder.addEndpoint(ServerEndpointConfig.Builder.create(TestEndPoint.class, "/").configurator(new InstanceConfigurator(new TestEndPoint())).build()); deployServlet(builder); WebSocketTestClient client = new WebSocketTestClient(getVersion(), new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/")); client.connect(); client.send(new PingWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(PongWebSocketFrame.class, payload, latch)); //FIXME UNDERTOW-1862 assertEquals(DONE, latch.getIoFuture().await()); latch.getIoFuture().await(); assertNull(cause.get()); client.destroy(); } @org.junit.Test public void testCloseFrame() throws Exception { final int code = 1000; final String reasonText = "TEST"; final AtomicReference reason = new AtomicReference<>(); ByteBuffer payload = ByteBuffer.allocate(reasonText.length() + 2); payload.putShort((short) code); payload.put(reasonText.getBytes(StandardCharsets.UTF_8)); payload.flip(); final AtomicBoolean connected = new AtomicBoolean(false); final FutureResult latch = new FutureResult<>(); final CountDownLatch clientLatch = new CountDownLatch(1); final AtomicInteger closeCount = new AtomicInteger(); class TestEndPoint extends Endpoint { @Override public void onOpen(final Session session, EndpointConfig config) { connected.set(true); } @Override public void onClose(Session session, CloseReason closeReason) { closeCount.incrementAndGet(); reason.set(closeReason); clientLatch.countDown(); } } ServerWebSocketContainer builder = new ServerWebSocketContainer(TestClassIntrospector.INSTANCE, DefaultServer.getWorkerSupplier(), DefaultServer.getBufferPool(), Collections.emptyList(), false, false); builder.addEndpoint(ServerEndpointConfig.Builder.create(TestEndPoint.class, "/").configurator(new InstanceConfigurator(new TestEndPoint())).build()); deployServlet(builder); WebSocketTestClient client = new WebSocketTestClient(getVersion(), new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/")); client.connect(); client.send(new CloseWebSocketFrame(code, reasonText), new FrameChecker(CloseWebSocketFrame.class, payload.array(), latch)); //FIXME UNDERTOW-1862 assertEquals(DONE, latch.getIoFuture().await()); latch.getIoFuture().await(); clientLatch.await(); assertEquals(code, reason.get().getCloseCode().getCode()); assertEquals(reasonText, reason.get().getReasonPhrase()); assertEquals(1, closeCount.get()); client.destroy(); } /** * Section 5.5.1 of RFC 6455 says the reason body is optional */ @org.junit.Test public void testCloseFrameWithoutReasonBody() throws Exception { final int code = 1000; final AtomicReference reason = new AtomicReference<>(); ByteBuffer payload = ByteBuffer.allocate(2); payload.putShort((short) code); payload.flip(); final AtomicBoolean connected = new AtomicBoolean(false); final FutureResult latch = new FutureResult<>(); final CountDownLatch clientLatch = new CountDownLatch(1); final AtomicInteger closeCount = new AtomicInteger(); class TestEndPoint extends Endpoint { @Override public void onOpen(final Session session, EndpointConfig config) { connected.set(true); } @Override public void onClose(Session session, CloseReason closeReason) { closeCount.incrementAndGet(); reason.set(closeReason); clientLatch.countDown(); } } ServerWebSocketContainer builder = new ServerWebSocketContainer(TestClassIntrospector.INSTANCE, DefaultServer.getWorkerSupplier(), DefaultServer.getBufferPool(), Collections.emptyList(), false, false); builder.addEndpoint(ServerEndpointConfig.Builder.create(TestEndPoint.class, "/").configurator(new InstanceConfigurator(new TestEndPoint())).build()); deployServlet(builder); WebSocketTestClient client = new WebSocketTestClient(getVersion(), new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/")); client.connect(); client.send(new CloseWebSocketFrame(code, null), new FrameChecker(CloseWebSocketFrame.class, payload.array(), latch)); assertEquals(DONE, latch.getIoFuture().await(10, TimeUnit.SECONDS)); clientLatch.await(); assertEquals(code, reason.get().getCloseCode().getCode()); assertEquals("", reason.get().getReasonPhrase()); assertEquals(1, closeCount.get()); client.destroy(); } @org.junit.Test public void testBinaryWithByteBufferAsync() throws Exception { final byte[] payload = "payload".getBytes(); final AtomicReference cause = new AtomicReference<>(); final AtomicBoolean connected = new AtomicBoolean(false); final FutureResult latch = new FutureResult<>(); class TestEndPoint extends Endpoint { @Override public void onOpen(final Session session, EndpointConfig config) { connected.set(true); session.addMessageHandler(new MessageHandler.Partial() { @Override public void onMessage(ByteBuffer message, boolean last) { Assert.assertTrue(last); ByteBuffer buf = ByteBuffer.allocate(message.remaining()); buf.put(message); buf.flip(); session.getAsyncRemote().sendBinary(buf); } }); } } ServerWebSocketContainer builder = new ServerWebSocketContainer(TestClassIntrospector.INSTANCE, DefaultServer.getWorkerSupplier(), DefaultServer.getBufferPool(), Collections.emptyList(), false, false); builder.addEndpoint(ServerEndpointConfig.Builder.create(TestEndPoint.class, "/").configurator(new InstanceConfigurator(new TestEndPoint())).build()); deployServlet(builder); WebSocketTestClient client = new WebSocketTestClient(getVersion(), new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/")); client.connect(); client.send(new BinaryWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(BinaryWebSocketFrame.class, payload, latch)); //FIXME UNDERTOW-1862 assertEquals(DONE, latch.getIoFuture().await()); latch.getIoFuture().await(); assertNull(cause.get()); client.destroy(); } @org.junit.Test public void testTextAsync() throws Exception { final byte[] payload = "payload".getBytes(); final AtomicReference cause = new AtomicReference<>(); final AtomicBoolean connected = new AtomicBoolean(false); final FutureResult latch = new FutureResult<>(); class TestEndPoint extends Endpoint { @Override public void onOpen(final Session session, EndpointConfig config) { connected.set(true); session.addMessageHandler(new MessageHandler.Partial() { final StringBuilder sb = new StringBuilder(); @Override public void onMessage(String message, boolean last) { sb.append(message); if (!last) { return; } session.getAsyncRemote().sendText(sb.toString()); } }); } } ServerWebSocketContainer builder = new ServerWebSocketContainer(TestClassIntrospector.INSTANCE, DefaultServer.getWorkerSupplier(), DefaultServer.getBufferPool(), Collections.emptyList(), false, false); builder.addEndpoint(ServerEndpointConfig.Builder.create(TestEndPoint.class, "/").configurator(new InstanceConfigurator(new TestEndPoint())).build()); deployServlet(builder); WebSocketTestClient client = new WebSocketTestClient(getVersion(), new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/")); client.connect(); client.send(new TextWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(TextWebSocketFrame.class, payload, latch)); //FIXME UNDERTOW-1862 assertEquals(DONE, latch.getIoFuture().await()); latch.getIoFuture().await(); assertNull(cause.get()); client.destroy(); } // FIXME UNDERTOW-1862 @Test public void testErrorHandling() throws Exception { ServerWebSocketContainer builder = new ServerWebSocketContainer(TestClassIntrospector.INSTANCE, DefaultServer.getWorkerSupplier(), DefaultServer.getBufferPool(), Collections.emptyList(), false, false); builder.addEndpoint(ServerEndpointConfig.Builder.create(ProgramaticErrorEndpoint.class, "/").configurator(new InstanceConfigurator(new ProgramaticErrorEndpoint())).build()); deployServlet(builder); AnnotatedClientEndpoint c = new AnnotatedClientEndpoint(); Session session = ContainerProvider.getWebSocketContainer().connectToServer(c, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/")); assertEquals("hi", ProgramaticErrorEndpoint.getMessage()); session.getAsyncRemote().sendText("app-error"); assertEquals("app-error", ProgramaticErrorEndpoint.getMessage()); assertEquals("ERROR: java.lang.RuntimeException", ProgramaticErrorEndpoint.getMessage()); assertTrue(c.isOpen()); session.getBasicRemote().sendText("io-error"); assertEquals("io-error", ProgramaticErrorEndpoint.getMessage()); assertEquals("ERROR: java.lang.RuntimeException", ProgramaticErrorEndpoint.getMessage()); assertTrue(c.isOpen()); ((UndertowSession) session).forceClose(); assertEquals("CLOSED", ProgramaticErrorEndpoint.getMessage()); } protected WebSocketVersion getVersion() { return WebSocketVersion.V07; } private void deployServlet(final ServerWebSocketContainer deployment) throws ServletException { assertNull("Can only be invoked once per method test", deploymentManager); final DeploymentInfo builder; builder = new DeploymentInfo() .setClassLoader(getClass().getClassLoader()) .setContextPath("/") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("websocket.war") .addFilter(new FilterInfo("filter", JsrWebSocketFilter.class)) .addFilterUrlMapping("filter", "/*", DispatcherType.REQUEST) .addServletContextAttribute(javax.websocket.server.ServerContainer.class.getName(), deployment); final PathHandler root = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); deploymentManager = container.addDeployment(builder); deploymentManager.deploy(); root.addPrefixPath(builder.getContextPath(), deploymentManager.start()); DefaultServer.setRootHandler(root); } @After public void cleanup() throws ServletException { if (deploymentManager != null) { deploymentManager.stop(); deploymentManager.undeploy(); deploymentManager = null; } } private static class InstanceConfigurator extends ServerEndpointConfig.Configurator { private final Object endpoint; private InstanceConfigurator(final Object endpoint) { this.endpoint = endpoint; } @SuppressWarnings("unchecked") @Override public T getEndpointInstance(final Class endpointClass) { return (T) endpoint; } } } JsrWebSocketServer08Test.java000066400000000000000000000020551420065311100356640ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test; import io.netty.handler.codec.http.websocketx.WebSocketVersion; /** * @author Norman Maurer */ public class JsrWebSocketServer08Test extends JsrWebSocketServer07Test{ @Override protected WebSocketVersion getVersion() { return WebSocketVersion.V08; } } JsrWebSocketServer13Test.java000066400000000000000000000020561420065311100356610ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test; import io.netty.handler.codec.http.websocketx.WebSocketVersion; /** * @author Norman Maurer */ public class JsrWebSocketServer13Test extends JsrWebSocketServer08Test { @Override protected WebSocketVersion getVersion() { return WebSocketVersion.V13; } } ProgramaticEndpoint.java000066400000000000000000000024511420065311100350710ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test; import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.MessageHandler; import javax.websocket.Session; /** * @author Stuart Douglas */ public class ProgramaticEndpoint extends Endpoint { @Override public void onOpen(final Session session, EndpointConfig config) { session.addMessageHandler(new MessageHandler.Whole() { @Override public void onMessage(String message) { session.getAsyncRemote().sendText("Hello " + message); } }); } } ProgramaticErrorEndpoint.java000066400000000000000000000044201420065311100361010ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test; import java.io.IOException; import java.util.concurrent.BlockingDeque; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import javax.websocket.CloseReason; import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.MessageHandler; import javax.websocket.Session; /** * Test error handling behaviour * * @author Stuart Douglas */ public class ProgramaticErrorEndpoint extends Endpoint { private static final BlockingDeque QUEUE = new LinkedBlockingDeque<>(); public static String getMessage() { try { return QUEUE.poll(10, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); } } @Override public void onOpen(Session session, EndpointConfig config) { session.addMessageHandler(new MessageHandler.Whole() { @Override public void onMessage(String message) { QUEUE.add(message); if (message.equals("app-error")) { throw new RuntimeException("an error"); } else if (message.equals("io-error")) { throw new RuntimeException(new IOException()); } } }); } @Override public void onError(Session session, Throwable thr) { QUEUE.add("ERROR: " + thr.getClass().getName()); } @Override public void onClose(Session session, CloseReason closeReason) { QUEUE.add("CLOSED"); } } ProgramaticLazyEndpointTest.java000066400000000000000000000126421420065311100365740ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test; import io.undertow.servlet.Servlets; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.websockets.jsr.DefaultWebSocketClientSslProvider; import io.undertow.websockets.jsr.ServerWebSocketContainer; import io.undertow.websockets.jsr.WebSocketDeploymentInfo; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.runner.RunWith; import javax.net.ssl.SSLContext; import javax.servlet.ServletException; import javax.websocket.ClientEndpointConfig; import javax.websocket.CloseReason; import javax.websocket.ContainerProvider; import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.MessageHandler; import javax.websocket.Session; import java.io.IOException; import java.net.URI; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertTrue; /** * @author Norman Maurer */ @RunWith(DefaultServer.class) @HttpOneOnly public class ProgramaticLazyEndpointTest { private static ServerWebSocketContainer deployment; private static DeploymentManager deploymentManager; @BeforeClass public static void setup() throws Exception { final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(ProgramaticLazyEndpointTest.class.getClassLoader()) .setContextPath("/") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .addServlet(Servlets.servlet("add", AddEndpointServlet.class).setLoadOnStartup(100)) .addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, new WebSocketDeploymentInfo() .setBuffers(DefaultServer.getBufferPool()) .setWorker(DefaultServer.getWorker()) .addListener(container1 -> deployment = container1) ) .setDeploymentName("servletContext.war"); deploymentManager = container.addDeployment(builder); deploymentManager.deploy(); DefaultServer.setRootHandler(deploymentManager.start()); DefaultServer.startSSLServer(); } @AfterClass public static void after() throws IOException, ServletException { if (deployment != null) { deployment.close(); deployment = null; } if (deploymentManager != null) { deploymentManager.stop(); deploymentManager.undeploy(); } DefaultServer.stopSSLServer(); } @org.junit.Test public void testStringOnMessage() throws Exception { SSLContext context = DefaultServer.getClientSSLContext(); ProgramaticClientEndpoint endpoint = new ProgramaticClientEndpoint(); ClientEndpointConfig clientEndpointConfig = ClientEndpointConfig.Builder.create().build(); clientEndpointConfig.getUserProperties().put(DefaultWebSocketClientSslProvider.SSL_CONTEXT, context); ContainerProvider.getWebSocketContainer().connectToServer(endpoint, clientEndpointConfig, new URI("wss://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostSSLPort("default") + "/foo")); Assert.assertEquals("Hello Stuart", endpoint.getResponses().poll(15, TimeUnit.SECONDS)); endpoint.session.close(); assertTrue(endpoint.closeLatch.await(10, TimeUnit.SECONDS)); } public static class ProgramaticClientEndpoint extends Endpoint { private final LinkedBlockingDeque responses = new LinkedBlockingDeque<>(); final CountDownLatch closeLatch = new CountDownLatch(1); volatile Session session; @Override public void onOpen(Session session, EndpointConfig config) { this.session = session; session.getAsyncRemote().sendText("Stuart"); session.addMessageHandler(new MessageHandler.Whole() { @Override public void onMessage(String message) { responses.add(message); } }); } @Override public void onClose(Session session, CloseReason closeReason) { closeLatch.countDown(); } public LinkedBlockingDeque getResponses() { return responses; } } } TestMessagesReceivedInOrder.java000066400000000000000000000202661420065311100364650ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test; import io.undertow.Handlers; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestResourceLoader; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.util.FlexBase64; import io.undertow.websockets.jsr.WebSocketDeploymentInfo; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.FutureResult; import javax.servlet.ServletException; import javax.websocket.ClientEndpointConfig; import javax.websocket.ContainerProvider; import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.OnMessage; import javax.websocket.RemoteEndpoint; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.net.URI; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import static org.junit.Assert.assertTrue; @RunWith(DefaultServer.class) @HttpOneOnly public class TestMessagesReceivedInOrder { private static final int MESSAGES = 1000; private static final List stacks = new CopyOnWriteArrayList<>(); private static DeploymentManager deploymentManager; @BeforeClass public static void setup() throws ServletException { final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(TestMessagesReceivedInOrder.class.getClassLoader()) .setContextPath("/") .setResourceManager(new TestResourceLoader(TestMessagesReceivedInOrder.class)) .setClassIntrospecter(TestClassIntrospector.INSTANCE) .addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, new WebSocketDeploymentInfo() .setBuffers(DefaultServer.getBufferPool()) .setWorker(DefaultServer.getWorkerSupplier()) .addEndpoint(EchoSocket.class) ) .setDeploymentName("servletContext.war"); deploymentManager = container.addDeployment(builder); deploymentManager.deploy(); DefaultServer.setRootHandler(Handlers.path().addPrefixPath("/", deploymentManager.start())); } @AfterClass public static void cleanup() throws ServletException { if (deploymentManager != null) { deploymentManager.stop(); deploymentManager.undeploy(); } } @Test public void testMessagesReceivedInOrder() throws Exception { stacks.clear(); EchoSocket.receivedEchos = new FutureResult<>(); final ClientEndpointConfig clientEndpointConfig = ClientEndpointConfig.Builder.create().build(); final CountDownLatch done = new CountDownLatch(1); final AtomicReference error = new AtomicReference<>(); ContainerProvider.getWebSocketContainer() .connectToServer(new Endpoint() { @Override public void onOpen(final Session session, EndpointConfig endpointConfig) { try { RemoteEndpoint.Basic rem = session.getBasicRemote(); List messages = new ArrayList<>(); for (int i = 0; i < MESSAGES; i++) { byte[] data = new byte[2048]; (new Random()).nextBytes(data); String crc = md5(data); rem.sendBinary(ByteBuffer.wrap(data)); messages.add(crc); } List received = EchoSocket.receivedEchos.getIoFuture().get(); StringBuilder sb = new StringBuilder(); boolean fail = false; for (int i = 0; i < messages.size(); i++) { if (received.size() <= i) { fail = true; sb.append(i + ": should be " + messages.get(i) + " but is empty."); } else { if (!messages.get(i).equals(received.get(i))) { fail = true; sb.append(i + ": should be " + messages.get(i) + " but is " + received.get(i) + " (but found at " + received.indexOf(messages.get(i)) + ")."); } } } if(fail) { error.set(sb.toString()); } done.countDown(); } catch (Throwable t) { t.printStackTrace(); } } }, clientEndpointConfig, new URI(DefaultServer.getDefaultServerURL() + "/webSocket") ); assertTrue(done.await(30, TimeUnit.SECONDS)); if(error.get() != null) { Assert.fail(error.get()); } } @ServerEndpoint("/webSocket") public static class EchoSocket { private final List echos = new CopyOnWriteArrayList<>(); public static volatile FutureResult> receivedEchos = new FutureResult<>(); @OnMessage public void onMessage(ByteBuffer dataBuffer, Session session) throws IOException { byte[] hd = new byte[dataBuffer.remaining()]; dataBuffer.get(hd); String hash = md5(hd); echos.add(hash); stacks.add(new RuntimeException()); if (echos.size() == MESSAGES) { receivedEchos.setResult(echos); } session.getBasicRemote().sendBinary(dataBuffer); } } private static String md5(byte[] buffer) { try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(buffer); byte[] digest = md.digest(); return new String(FlexBase64.encodeBytes(digest, 0, digest.length, false)); } catch (NoSuchAlgorithmException e) { // Should never happen throw new InternalError("MD5 not supported on this platform"); } } } undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotated/000077500000000000000000000000001420065311100323075ustar00rootroot00000000000000AnnotatedAddedProgrammaticallyEndpoint.java000066400000000000000000000010421420065311100426600ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotatedpackage io.undertow.websockets.jsr.test.annotated; import javax.websocket.OnMessage; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; @ServerEndpoint(AnnotatedAddedProgrammaticallyEndpoint.PATH) public class AnnotatedAddedProgrammaticallyEndpoint { static final String PATH = "/programmatic"; @OnMessage public String handleMessage(String message, Session session) { StringBuilder reversed = new StringBuilder(message); reversed.reverse(); return reversed.toString(); } } AnnotatedClientEndpoint.java000066400000000000000000000036031420065311100376520ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.annotated; import java.util.concurrent.BlockingDeque; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import javax.websocket.ClientEndpoint; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; /** * @author Stuart Douglas */ @ClientEndpoint(subprotocols = {"foo", "bar"}) public class AnnotatedClientEndpoint { private static final BlockingDeque MESSAGES = new LinkedBlockingDeque<>(); private volatile boolean open = false; public static String message() throws InterruptedException { return MESSAGES.pollFirst(3, TimeUnit.SECONDS); } @OnOpen public void onOpen(final Session session) { session.getAsyncRemote().sendText("hi"); this.open = true; } @OnMessage public void onMessage(final String message) { MESSAGES.add(message); } @OnClose public void onClose() { this.open = false; MESSAGES.add("CLOSED"); } public boolean isOpen() { return open; } public static void reset() { MESSAGES.clear(); } } AnnotatedClientEndpointWithConfigurator.java000066400000000000000000000033631420065311100430740ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.annotated; import javax.websocket.ClientEndpoint; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import java.util.concurrent.BlockingDeque; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; /** * @author Stuart Douglas */ @ClientEndpoint(subprotocols = {"foo", "bar", "configured-proto"}, configurator = ClientConfigurator.class) public class AnnotatedClientEndpointWithConfigurator { private static final BlockingDeque MESSAGES = new LinkedBlockingDeque<>(); public static String message() throws InterruptedException { return MESSAGES.pollFirst(3, TimeUnit.SECONDS); } @OnOpen public void onOpen(final Session session) { session.getAsyncRemote().sendText("hi"); } @OnMessage public void onMessage(final String message) { MESSAGES.add(message); } @OnClose public void onClose() { MESSAGES.add("CLOSED"); } } AnnotatedEndpointTest.java000066400000000000000000000434761420065311100373670ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.annotated; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.websocket.ClientEndpoint; import javax.websocket.CloseReason; import javax.websocket.OnClose; import javax.websocket.Session; import javax.websocket.server.ServerEndpointConfig; import java.io.IOException; import java.net.URI; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.FutureResult; import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketVersion; import io.undertow.Handlers; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestResourceLoader; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.testutils.HttpsIgnore; import io.undertow.websockets.jsr.ServerWebSocketContainer; import io.undertow.websockets.jsr.UndertowSession; import io.undertow.websockets.jsr.WebSocketDeploymentInfo; import io.undertow.websockets.utils.FrameChecker; import io.undertow.websockets.utils.WebSocketTestClient; /** * @author Norman Maurer */ @RunWith(DefaultServer.class) @HttpOneOnly public class AnnotatedEndpointTest { private static ServerWebSocketContainer deployment; private static DeploymentManager deploymentManager; @BeforeClass public static void setup() throws Exception { final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(AnnotatedEndpointTest.class.getClassLoader()) .setContextPath("/ws") .setResourceManager(new TestResourceLoader(AnnotatedEndpointTest.class)) .setClassIntrospecter(TestClassIntrospector.INSTANCE) .addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, new WebSocketDeploymentInfo() .setBuffers(DefaultServer.getBufferPool()) .setWorker(DefaultServer.getWorkerSupplier()) .addEndpoint(MessageEndpoint.class) .addEndpoint(AnnotatedClientEndpoint.class) .addEndpoint(AnnotatedClientEndpointWithConfigurator.class) .addEndpoint(IncrementEndpoint.class) .addEndpoint(EncodingEndpoint.class) .addEndpoint(EncodingGenericsEndpoint.class) .addEndpoint(TimeoutEndpoint.class) .addEndpoint(ErrorEndpoint.class) .addEndpoint(RootContextEndpoint.class) .addEndpoint(ThreadSafetyEndpoint.class) .addEndpoint(RequestUriEndpoint.class) .addListener(readyContainer -> deployment = readyContainer) .addEndpoint(ServerEndpointConfig.Builder.create( AnnotatedAddedProgrammaticallyEndpoint.class, AnnotatedAddedProgrammaticallyEndpoint.PATH) .build()) ) .addServlet(new ServletInfo("redirect", RedirectServlet.class) .addMapping("/redirect")) .setDeploymentName("servletContext.war"); deploymentManager = container.addDeployment(builder); deploymentManager.deploy(); DefaultServer.setRootHandler(Handlers.path().addPrefixPath("/ws", deploymentManager.start())); } @AfterClass public static void after() throws ServletException { if (deployment != null) { deployment.close(); deployment = null; } if (deploymentManager != null) { deploymentManager.stop(); deploymentManager.undeploy(); } } @Test public void testStringOnMessage() throws Exception { final byte[] payload = "hello".getBytes(); final FutureResult latch = new FutureResult<>(); WebSocketTestClient client = new WebSocketTestClient(WebSocketVersion.V13, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/chat/Stuart")); client.connect(); client.send(new TextWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(TextWebSocketFrame.class, "hello Stuart".getBytes(), latch)); latch.getIoFuture().get(); client.destroy(); } @Test public void testStringOnMessageAddedProgramatically() throws Exception { final byte[] payload = "foo".getBytes(); final FutureResult latch = new FutureResult<>(); WebSocketTestClient client = new WebSocketTestClient(WebSocketVersion.V13, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/programmatic")); client.connect(); client.send(new TextWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(TextWebSocketFrame.class, "oof".getBytes(), latch)); latch.getIoFuture().get(); client.destroy(); } @Test public void testRedirectHandling() throws Exception { AnnotatedClientEndpoint.reset(); Session session = deployment.connectToServer(AnnotatedClientEndpoint.class, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/redirect")); Assert.assertEquals("hi Stuart (protocol=foo)", AnnotatedClientEndpoint.message()); session.close(); Assert.assertEquals("CLOSED", AnnotatedClientEndpoint.message()); } @Test public void testWebSocketInRootContext() throws Exception { final byte[] payload = "hello".getBytes(); final FutureResult latch = new FutureResult<>(); WebSocketTestClient client = new WebSocketTestClient(WebSocketVersion.V13, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws")); client.connect(); client.send(new TextWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(TextWebSocketFrame.class, "hello".getBytes(), latch)); latch.getIoFuture().get(); client.destroy(); } @Test public void testAnnotatedClientEndpoint() throws Exception { AnnotatedClientEndpoint.reset(); Session session = deployment.connectToServer(AnnotatedClientEndpoint.class, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/chat/Bob")); Assert.assertEquals("hi Bob (protocol=foo)", AnnotatedClientEndpoint.message()); session.close(); Assert.assertEquals("CLOSED", AnnotatedClientEndpoint.message()); } @Test public void testIdleTimeout() throws Exception { AnnotatedClientEndpoint.reset(); Session session = deployment.connectToServer(AnnotatedClientEndpoint.class, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/chat/Bob")); Assert.assertEquals("hi Bob (protocol=foo)", AnnotatedClientEndpoint.message()); session.close(); Assert.assertEquals("CLOSED", AnnotatedClientEndpoint.message()); } @Test public void testCloseReason() throws Exception { AnnotatedClientEndpoint.reset(); MessageEndpoint.reset(); Session session = deployment.connectToServer(AnnotatedClientEndpoint.class, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/chat/Bob")); Assert.assertEquals("hi Bob (protocol=foo)", AnnotatedClientEndpoint.message()); session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, "Foo!")); Assert.assertEquals("CLOSED", AnnotatedClientEndpoint.message()); CloseReason cr = MessageEndpoint.getReason(); Assert.assertEquals(CloseReason.CloseCodes.VIOLATED_POLICY.getCode(), cr.getCloseCode().getCode()); Assert.assertEquals("Foo!", cr.getReasonPhrase()); } @Test public void testAnnotatedClientEndpointWithConfigurator() throws Exception { Session session = deployment.connectToServer(AnnotatedClientEndpointWithConfigurator.class, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/chat/Bob")); Assert.assertEquals("hi Bob (protocol=configured-proto)", AnnotatedClientEndpointWithConfigurator.message()); Assert.assertEquals("foo, bar, configured-proto", ClientConfigurator.sentSubProtocol); Assert.assertEquals("configured-proto", ClientConfigurator.receivedSubProtocol()); session.close(); Assert.assertEquals("CLOSED", AnnotatedClientEndpointWithConfigurator.message()); } @Test public void testErrorHandling() throws Exception { //make a sub class AnnotatedClientEndpoint c = new AnnotatedClientEndpoint() { }; Session session = deployment.connectToServer(c, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/error")); Assert.assertEquals("hi", ErrorEndpoint.getMessage()); session.getAsyncRemote().sendText("app-error"); Assert.assertEquals("app-error", ErrorEndpoint.getMessage()); Assert.assertEquals("ERROR: java.lang.RuntimeException", ErrorEndpoint.getMessage()); Assert.assertTrue(c.isOpen()); session.getBasicRemote().sendText("io-error"); Assert.assertEquals("io-error", ErrorEndpoint.getMessage()); Assert.assertEquals("ERROR: java.io.IOException", ErrorEndpoint.getMessage()); Assert.assertTrue(c.isOpen()); ((UndertowSession)session).forceClose(); Assert.assertEquals("CLOSED", ErrorEndpoint.getMessage()); } @Test public void testClientSideIdleTimeout() throws Exception { //make a sub class CountDownLatch latch = new CountDownLatch(1); CloseCountdownEndpoint c = new CloseCountdownEndpoint(latch); Session session = deployment.connectToServer(c, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/chat/Bob")); session.setMaxIdleTimeout(100); Assert.assertTrue(latch.await(2000, TimeUnit.MILLISECONDS)); Assert.assertFalse(session.isOpen()); } @Test public void testGenericMessageHandling() throws Exception { //make a sub class AnnotatedGenericClientEndpoint c = new AnnotatedGenericClientEndpoint() { }; Session session = deployment.connectToServer(c, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/error")); Assert.assertEquals("hi", ErrorEndpoint.getMessage()); session.getAsyncRemote().sendText("app-error"); Assert.assertEquals("app-error", ErrorEndpoint.getMessage()); Assert.assertEquals("ERROR: java.lang.RuntimeException", ErrorEndpoint.getMessage()); Assert.assertTrue(c.isOpen()); session.getBasicRemote().sendText("io-error"); Assert.assertEquals("io-error", ErrorEndpoint.getMessage()); Assert.assertEquals("ERROR: java.io.IOException", ErrorEndpoint.getMessage()); Assert.assertTrue(c.isOpen()); ((UndertowSession)session).forceClose(); Assert.assertEquals("CLOSED", ErrorEndpoint.getMessage()); } @Test public void testImplicitIntegerConversion() throws Exception { final byte[] payload = "12".getBytes(); final FutureResult latch = new FutureResult<>(); WebSocketTestClient client = new WebSocketTestClient(WebSocketVersion.V13, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/increment/2")); client.connect(); client.send(new TextWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(TextWebSocketFrame.class, "14".getBytes(), latch)); latch.getIoFuture().get(); client.destroy(); } @Test public void testEncodingAndDecodingText() throws Exception { final byte[] payload = "hello".getBytes(); final FutureResult latch = new FutureResult<>(); WebSocketTestClient client = new WebSocketTestClient(WebSocketVersion.V13, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/encoding/Stuart")); client.connect(); client.send(new TextWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(TextWebSocketFrame.class, "hello Stuart".getBytes(), latch)); latch.getIoFuture().get(); client.destroy(); } @Test public void testEncodingAndDecodingBinary() throws Exception { final byte[] payload = "hello".getBytes(); final FutureResult latch = new FutureResult<>(); WebSocketTestClient client = new WebSocketTestClient(WebSocketVersion.V13, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/encoding/Stuart")); client.connect(); client.send(new BinaryWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(TextWebSocketFrame.class, "hello Stuart".getBytes(), latch)); latch.getIoFuture().get(); client.destroy(); } @Test public void testEncodingWithGenericSuperclass() throws Exception { final byte[] payload = "hello".getBytes(); final FutureResult latch = new FutureResult<>(); WebSocketTestClient client = new WebSocketTestClient(WebSocketVersion.V13, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/encodingGenerics/Stuart")); client.connect(); client.send(new TextWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(TextWebSocketFrame.class, "hello Stuart".getBytes(), latch)); latch.getIoFuture().get(); client.destroy(); } @Test public void testRequestUri() throws Exception { final byte[] payload = "hello".getBytes(); final FutureResult latch = new FutureResult<>(); WebSocketTestClient client = new WebSocketTestClient(WebSocketVersion.V13, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/request?a=b")); client.connect(); client.send(new TextWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(TextWebSocketFrame.class, "/ws/request?a=b".getBytes(), latch)); latch.getIoFuture().get(); client.destroy(); } @Test @HttpsIgnore("The SSL engine closes when it receives the first FIN, and as a result the web socket close frame can't be properly echoed over the proxy when the server initates the close") public void testTimeoutCloseReason() throws Exception { TimeoutEndpoint.reset(); deployment.connectToServer(DoNothingEndpoint.class, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/timeout")); Assert.assertEquals(CloseReason.CloseCodes.CLOSED_ABNORMALLY, TimeoutEndpoint.getReason().getCloseCode()); } @Test public void testThreadSafeSend() throws Exception { AnnotatedClientEndpoint.reset(); Session session = deployment.connectToServer(AnnotatedClientEndpoint.class, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/threads")); Set expected = ThreadSafetyEndpoint.expected(); long end = System.currentTimeMillis() + 10000; while (!expected.isEmpty() && System.currentTimeMillis() < end) { expected.remove(AnnotatedClientEndpoint.message()); } session.close(); Assert.assertEquals(0, expected.size()); } @ClientEndpoint public static class DoNothingEndpoint {} @ClientEndpoint public static class CloseCountdownEndpoint { private final CountDownLatch latch; public CloseCountdownEndpoint(CountDownLatch latch) { this.latch = latch; } @OnClose public void close() { latch.countDown(); } } public static final class RedirectServlet extends HttpServlet{ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.sendRedirect("/ws/chat/Stuart"); } } } AnnotatedGenericClientEndpoint.java000066400000000000000000000033161420065311100411500ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.annotated; import javax.websocket.ClientEndpoint; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import java.util.concurrent.BlockingDeque; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; @ClientEndpoint(subprotocols = {"foo", "bar"}) public class AnnotatedGenericClientEndpoint implements GenericWebSocketClientEndpoint { private static final BlockingDeque MESSAGES = new LinkedBlockingDeque<>(); private volatile boolean open = false; public static String message() throws InterruptedException { return MESSAGES.pollFirst(3, TimeUnit.SECONDS); } @OnOpen public void onOpen(final Session session) { session.getAsyncRemote().sendText("hi"); this.open = true; } @OnMessage public void onMessage(final String message) { MESSAGES.add(message); } public boolean isOpen() { return open; } } ClientConfigurator.java000066400000000000000000000047211420065311100367000ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.annotated; import javax.websocket.ClientEndpointConfig; import javax.websocket.HandshakeResponse; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static io.undertow.util.Headers.SEC_WEB_SOCKET_PROTOCOL_STRING; /** * @author Stuart Douglas */ public class ClientConfigurator extends ClientEndpointConfig.Configurator { public static volatile String sentSubProtocol; private static volatile String receivedSubProtocol; private static volatile CountDownLatch receiveLatch = new CountDownLatch(1); @Override public void beforeRequest(Map> headers) { if (headers.containsKey(SEC_WEB_SOCKET_PROTOCOL_STRING)) { sentSubProtocol = headers.get(SEC_WEB_SOCKET_PROTOCOL_STRING).get(0); headers.put(SEC_WEB_SOCKET_PROTOCOL_STRING, Collections.singletonList("configured-proto")); } else { sentSubProtocol = null; } } @Override public void afterResponse(HandshakeResponse hr) { Map> headers = hr.getHeaders(); if (headers.containsKey(SEC_WEB_SOCKET_PROTOCOL_STRING.toLowerCase(Locale.ENGLISH))) { receivedSubProtocol = headers.get(SEC_WEB_SOCKET_PROTOCOL_STRING.toLowerCase(Locale.ENGLISH)).get(0); } else { receivedSubProtocol = null; } receiveLatch.countDown(); } public static String receivedSubProtocol() { try { receiveLatch.await(5, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); } return receivedSubProtocol; } } EncodableObject.java000066400000000000000000000066261420065311100361100ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.annotated; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import javax.websocket.DecodeException; import javax.websocket.EncodeException; import javax.websocket.EndpointConfig; /** * @author Stuart Douglas */ public class EncodableObject { private final String value; public EncodableObject(final String value) { this.value = value; } public String getValue() { return value; } public static class TextEncoder implements javax.websocket.Encoder.Text { boolean initalized = false; public static volatile boolean destroyed = false; @Override public String encode(final EncodableObject object) throws EncodeException { if (!initalized) { return "not initialized"; } return object.value; } @Override public void init(final EndpointConfig config) { initalized = true; } @Override public void destroy() { } } public static class TextDecoder implements javax.websocket.Decoder.Text { boolean initalized = false; public static volatile boolean destroyed = false; @Override public void init(final EndpointConfig config) { initalized = true; } @Override public void destroy() { destroyed = true; } @Override public EncodableObject decode(final String s) throws DecodeException { if(!initalized) { throw new DecodeException(s, "not initialized"); } return new EncodableObject(s); } @Override public boolean willDecode(final String s) { return true; } } public static class BinaryDecoder implements javax.websocket.Decoder.Binary { boolean initalized = false; public static volatile boolean destroyed = false; @Override public void init(final EndpointConfig config) { initalized = true; } @Override public void destroy() { destroyed = true; } @Override public EncodableObject decode(final ByteBuffer s) throws DecodeException { if(!initalized) { throw new DecodeException(s, "not initialized"); } byte[] data = new byte[s.remaining()]; s.get(data); return new EncodableObject(new String(data, StandardCharsets.US_ASCII)); } @Override public boolean willDecode(final ByteBuffer s) { return true; } } } EncodableObjectSubClass.java000066400000000000000000000016571420065311100375470ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.annotated; /** * @author Stuart Douglas */ public class EncodableObjectSubClass extends EncodableObject { public EncodableObjectSubClass(String value) { super(value); } } EncodingEndpoint.java000066400000000000000000000024721420065311100363270ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.annotated; import javax.websocket.OnMessage; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; /** * @author Stuart Douglas */ @ServerEndpoint(value = "/encoding/{user}", encoders = {EncodableObject.TextEncoder.class}, decoders = {EncodableObject.TextDecoder.class, EncodableObject.BinaryDecoder.class}) public class EncodingEndpoint { @OnMessage public EncodableObject handleMessage(final EncodableObject message, @PathParam("user") String user) { return new EncodableObjectSubClass(message.getValue() + " " + user); } } EncodingGenericsEndpoint.java000066400000000000000000000023321420065311100400020ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.annotated; import javax.websocket.OnMessage; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; /** * UNDERTOW-287 * * * * @author Stuart Douglas */ @ServerEndpoint(value = "/encodingGenerics/{user}", decoders = SubclassDecoder.class) public class EncodingGenericsEndpoint { @OnMessage public String handleMessage(final EncodableObject message, @PathParam("user") String user) { return message.getValue() + " " + user; } } ErrorEndpoint.java000066400000000000000000000037071420065311100356740ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.annotated; import java.io.IOException; import java.util.concurrent.BlockingDeque; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.server.ServerEndpoint; /** * Test error handling behaviour * * @author Stuart Douglas */ @ServerEndpoint("/error") public class ErrorEndpoint { private static final BlockingDeque QUEUE = new LinkedBlockingDeque<>(); @OnMessage public void handleMessage(final String message) throws IOException { QUEUE.add(message); if(message.equals("app-error")) { throw new RuntimeException("an error"); } else if(message.equals("io-error")) { throw new IOException(); } } @OnError public void error(Throwable t) { QUEUE.add("ERROR: " + t.getClass().getName()); } @OnClose public void closed() { QUEUE.add("CLOSED"); } public static String getMessage() { try { return QUEUE.poll(10, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); } } } GenericSuperclassDecoder.java000066400000000000000000000025071420065311100400060ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.annotated; import javax.websocket.DecodeException; import javax.websocket.Decoder; import javax.websocket.EndpointConfig; /** * @author Stuart Douglas */ public abstract class GenericSuperclassDecoder implements Decoder.Text { @Override public T decode(String s) throws DecodeException { return doRealDecode(s); } protected abstract T doRealDecode(String s); @Override public boolean willDecode(String s) { return true; } @Override public void init(EndpointConfig config) { } @Override public void destroy() { } } GenericWebSocketClientEndpoint.java000066400000000000000000000002211420065311100411110ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotatedpackage io.undertow.websockets.jsr.test.annotated; public interface GenericWebSocketClientEndpoint { void onMessage(final M message); } IncrementEndpoint.java000066400000000000000000000025661420065311100365310ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.annotated; import javax.websocket.EndpointConfig; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; /** * @author Stuart Douglas */ @ServerEndpoint("/increment/{increment}") public class IncrementEndpoint { int increment; @OnOpen public void open(final Session session, final EndpointConfig config, @PathParam("increment") int increment) { this.increment = increment; } @OnMessage public int handleMessage(final int message) { return message + increment; } } MessageEndpoint.java000066400000000000000000000037461420065311100361720ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.annotated; import javax.websocket.CloseReason; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.Session; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * @author Stuart Douglas */ @ServerEndpoint(value = "/chat/{user}", subprotocols = {"foo", "bar", "configured-proto"}) public class MessageEndpoint { public static volatile CloseReason closeReason; private static volatile CountDownLatch closeLatch = new CountDownLatch(1); @OnMessage public String handleMessage(Session session, final String message, @PathParam("user") String user) { String proto = session.getNegotiatedSubprotocol(); return message + " " + user + (proto.isEmpty() ? "" : " (protocol=" + proto + ")"); } @OnClose public void close(CloseReason c) { closeReason = c; closeLatch.countDown(); } public static CloseReason getReason() throws InterruptedException { closeLatch.await(10, TimeUnit.SECONDS); return closeReason; } public static void reset() { closeLatch = new CountDownLatch(1); closeReason = null; } } MiddleClassDecoder.java000066400000000000000000000015671420065311100365560ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.annotated; /** * @author Stuart Douglas */ public abstract class MiddleClassDecoder extends GenericSuperclassDecoder { } RequestUriEndpoint.java000066400000000000000000000021351420065311100367050ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.annotated; import javax.websocket.OnMessage; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; /** * @author Stuart Douglas */ @ServerEndpoint("/request") public class RequestUriEndpoint { @OnMessage public String handleMessage(String message, Session session) { return session.getRequestURI().toString(); } } RootContextEndpoint.java000066400000000000000000000027021420065311100370650ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.annotated; import javax.websocket.OnMessage; import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpointConfig; /** * @author Stuart Douglas */ @ServerEndpoint(value = "/", configurator = RootContextEndpoint.TestServerConfigurator.class) public class RootContextEndpoint { public RootContextEndpoint(String ignored) { } @OnMessage public String echo(String msg) { return msg; } public static class TestServerConfigurator extends ServerEndpointConfig.Configurator { @Override public T getEndpointInstance(Class endpointClass) throws InstantiationException { return (T) new RootContextEndpoint(""); } } } SubclassDecoder.java000066400000000000000000000017571420065311100361520ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.annotated; /** * @author Stuart Douglas */ public class SubclassDecoder extends MiddleClassDecoder { @Override protected EncodableObject doRealDecode(String s) { return new EncodableObject(s); } } ThreadSafetyEndpoint.java000066400000000000000000000036621420065311100371660ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.annotated; import java.util.HashSet; import java.util.Set; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; /** * @author Stuart Douglas */ @ServerEndpoint(value = "/threads") public class ThreadSafetyEndpoint { public static volatile Session s; public static final int NUM_THREADS = 100; public static final int NUM_MESSAGES = 100; public static final Set expected() { Set ret = new HashSet<>(); for (int i = 0; i < NUM_THREADS; ++i) { for (int j = 0; j < NUM_MESSAGES; ++j) { ret.add("t" + i + "-m" + j); } } return ret; } @OnOpen public void onOpen(final Session session) { s = session; for (int i = 0; i < NUM_THREADS; ++i) { final int tnum = i; Thread t = new Thread(new Runnable() { @Override public void run() { for (int j = 0; j < NUM_MESSAGES; ++j) { session.getAsyncRemote().sendText("t" + tnum + "-m" + j); } } }); t.start(); } } } TimeoutEndpoint.java000066400000000000000000000035511420065311100362260ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/annotated/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.annotated; import javax.websocket.CloseReason; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * @author Stuart Douglas */ @ServerEndpoint(value = "/timeout") public class TimeoutEndpoint { public static volatile CloseReason closeReason; private static volatile CountDownLatch closeLatch = new CountDownLatch(1); @OnOpen public void open(Session session) { session.setMaxIdleTimeout(100); } @OnMessage public String handleMessage(Session session, final String message) { return message; } @OnClose public void close(CloseReason c) { closeReason = c; closeLatch.countDown(); } public static CloseReason getReason() throws InterruptedException { closeLatch.await(10, TimeUnit.SECONDS); return closeReason; } public static void reset() { closeLatch = new CountDownLatch(1); closeReason = null; } } undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/autobahn/000077500000000000000000000000001420065311100321335ustar00rootroot00000000000000AnnotatedAutobahnServer.java000066400000000000000000000120261420065311100375060ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/autobahn/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.autobahn; import java.net.InetSocketAddress; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import org.jboss.logging.Logger; import io.undertow.server.DefaultByteBufferPool; import io.undertow.server.protocol.http.HttpOpenListener; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.websockets.extensions.PerMessageDeflateHandshake; import io.undertow.websockets.jsr.JsrWebSocketFilter; import io.undertow.websockets.jsr.WebSocketDeploymentInfo; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.StreamConnection; import org.xnio.Xnio; import org.xnio.XnioWorker; import org.xnio.channels.AcceptingChannel; /** * @author Norman Maurer */ public class AnnotatedAutobahnServer implements Runnable { private static final Logger log = Logger.getLogger(AnnotatedAutobahnServer.class); private final int port; public AnnotatedAutobahnServer(final int port) { this.port = port; } public void run() { Xnio xnio = Xnio.getInstance(); DeploymentManager deploymentManager = null; try { XnioWorker worker = xnio.createWorker(OptionMap.builder() .set(Options.WORKER_WRITE_THREADS, 4) .set(Options.WORKER_READ_THREADS, 4) .set(Options.CONNECTION_HIGH_WATER, 1000000) .set(Options.CONNECTION_LOW_WATER, 1000000) .set(Options.WORKER_TASK_CORE_THREADS, 10) .set(Options.WORKER_TASK_MAX_THREADS, 12) .set(Options.TCP_NODELAY, true) .set(Options.CORK, true) .getMap()); OptionMap serverOptions = OptionMap.builder() .set(Options.TCP_NODELAY, true) .set(Options.REUSE_ADDRESSES, true) .getMap(); DefaultByteBufferPool pool = new DefaultByteBufferPool(true, 8024); HttpOpenListener openListener = new HttpOpenListener(pool); ChannelListener acceptListener = ChannelListeners.openListenerAdapter(openListener); AcceptingChannel server = worker.createStreamConnectionServer(new InetSocketAddress(port), acceptListener, serverOptions); server.resumeAccepts(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(AnnotatedAutobahnServer.class.getClassLoader()) .setContextPath("/") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, new WebSocketDeploymentInfo() .setBuffers(pool) .setWorker(worker) .addEndpoint(AutobahnAnnotatedEndpoint.class) .setDispatchToWorkerThread(true) .addExtension(new PerMessageDeflateHandshake()) ) .addFilter(new FilterInfo("filter", JsrWebSocketFilter.class)) .addFilterUrlMapping("filter", "/*", DispatcherType.REQUEST); deploymentManager = container.addDeployment(builder); deploymentManager.deploy(); openListener.setRootHandler(deploymentManager.start()); } catch (Exception e) { log.error("failed to start server", e); } finally { if (deploymentManager != null) { deploymentManager.undeploy(); try { deploymentManager.stop(); } catch (ServletException e) { throw new RuntimeException(e); } } } } public static void main(String[] args) { new AnnotatedAutobahnServer(7777).run(); } } AutobahnAnnotatedEndpoint.java000066400000000000000000000034271420065311100400250ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/autobahn/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.autobahn; import java.io.IOException; import java.io.OutputStream; import java.io.Writer; import javax.websocket.OnMessage; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; /** * @author Stuart Douglas */ @ServerEndpoint("/") public class AutobahnAnnotatedEndpoint { Writer writer; OutputStream stream; @OnMessage public void handleMessage(final String message, Session session, boolean last) throws IOException { if (writer == null) { writer = session.getBasicRemote().getSendWriter(); } writer.write(message); if (last) { writer.close(); writer = null; } } @OnMessage public void handleMessage(final byte[] message, Session session, boolean last) throws IOException { if (stream == null) { stream = session.getBasicRemote().getSendStream(); } stream.write(message); stream.flush(); if (last) { stream.close(); stream = null; } } } ProgramaticAutobahnEndpoint.java000066400000000000000000000037471420065311100403650ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/autobahn/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.autobahn; import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.MessageHandler; import javax.websocket.Session; import java.nio.ByteBuffer; /** * @author Stuart Douglas */ public class ProgramaticAutobahnEndpoint extends Endpoint { @Override public void onOpen(Session session, EndpointConfig endpointConfig) { session.addMessageHandler(new BinaryMessageHandler(session)); session.addMessageHandler(new TextMessageHandler(session)); } private static class BinaryMessageHandler implements MessageHandler.Whole { private final Session session; private BinaryMessageHandler(Session session) { this.session = session; } @Override public void onMessage(ByteBuffer byteBuffer) { session.getAsyncRemote().sendBinary(byteBuffer); } } private static class TextMessageHandler implements MessageHandler.Whole { private final Session session; private TextMessageHandler(Session session) { this.session = session; } @Override public void onMessage(String byteBuffer) { session.getAsyncRemote().sendText(byteBuffer); } } } ProgramaticAutobahnServer.java000066400000000000000000000116211420065311100400410ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/autobahn/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.autobahn; import io.undertow.server.DefaultByteBufferPool; import io.undertow.server.protocol.http.HttpOpenListener; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.websockets.extensions.PerMessageDeflateHandshake; import io.undertow.websockets.jsr.JsrWebSocketFilter; import io.undertow.websockets.jsr.ServerEndpointConfigImpl; import io.undertow.websockets.jsr.WebSocketDeploymentInfo; import org.junit.AfterClass; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.StreamConnection; import org.xnio.Xnio; import org.xnio.XnioWorker; import org.xnio.channels.AcceptingChannel; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import java.net.InetSocketAddress; /** * @author Norman Maurer */ public class ProgramaticAutobahnServer implements Runnable { private static DeploymentManager deploymentManager; private final int port; public ProgramaticAutobahnServer(final int port) { this.port = port; } public void run() { Xnio xnio = Xnio.getInstance(); try { XnioWorker worker = xnio.createWorker(OptionMap.builder() .set(Options.CONNECTION_HIGH_WATER, 1000000) .set(Options.CONNECTION_LOW_WATER, 1000000) .set(Options.WORKER_TASK_CORE_THREADS, 10) .set(Options.WORKER_TASK_MAX_THREADS, 12) .set(Options.TCP_NODELAY, true) .set(Options.CORK, true) .getMap()); OptionMap serverOptions = OptionMap.builder() .set(Options.TCP_NODELAY, true) .set(Options.REUSE_ADDRESSES, true) .getMap(); DefaultByteBufferPool pool = new DefaultByteBufferPool(true, 8192); HttpOpenListener openListener = new HttpOpenListener(pool); ChannelListener acceptListener = ChannelListeners.openListenerAdapter(openListener); AcceptingChannel server = worker.createStreamConnectionServer(new InetSocketAddress(port), acceptListener, serverOptions); server.resumeAccepts(); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(ProgramaticAutobahnServer.class.getClassLoader()) .setContextPath("/") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .addFilter(new FilterInfo("filter", JsrWebSocketFilter.class)) .addFilterUrlMapping("filter", "/*", DispatcherType.REQUEST) .addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, new WebSocketDeploymentInfo() .setBuffers(pool) .setWorker(worker) .setDispatchToWorkerThread(true) .addEndpoint(new ServerEndpointConfigImpl(ProgramaticAutobahnEndpoint.class, "/")) .addExtension(new PerMessageDeflateHandshake()) ); deploymentManager = container.addDeployment(builder); deploymentManager.deploy(); openListener.setRootHandler(deploymentManager.start()); } catch (Exception e) { throw new RuntimeException(e); } } @AfterClass public static void cleanup() throws ServletException { if (deploymentManager != null) { deploymentManager.stop(); deploymentManager.undeploy(); } } public static void main(String[] args) { new ProgramaticAutobahnServer(7777).run(); } } undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/dynamicupgrade/000077500000000000000000000000001420065311100333265ustar00rootroot00000000000000DoUpgradeServlet.java000066400000000000000000000056371420065311100373440ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/dynamicupgrade/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.dynamicupgrade; import io.undertow.websockets.jsr.ServerWebSocketContainer; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.websocket.ContainerProvider; import javax.websocket.Decoder; import javax.websocket.Encoder; import javax.websocket.Extension; import javax.websocket.server.ServerEndpointConfig; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Map; /** * @author Stuart Douglas */ public class DoUpgradeServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { ((ServerWebSocketContainer)ContainerProvider.getWebSocketContainer()).doUpgrade(req, resp, new ServerEndpointConfig() { @Override public Class getEndpointClass() { if(req.getParameter("annotated") != null) { return EchoEndpoint.class; } else { return EchoProgramaticEndpoint.class; } } @Override public String getPath() { return req.getPathInfo(); } @Override public List getSubprotocols() { return Collections.emptyList(); } @Override public List getExtensions() { return Collections.emptyList(); } @Override public Configurator getConfigurator() { return null; } @Override public List> getEncoders() { return Collections.emptyList(); } @Override public List> getDecoders() { return Collections.emptyList(); } @Override public Map getUserProperties() { return Collections.emptyMap(); } }, Collections.singletonMap("foo", req.getPathInfo())); } } DynamicEndpointTest.java000066400000000000000000000115661420065311100400500ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/dynamicupgrade/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.dynamicupgrade; import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketVersion; import io.undertow.Handlers; import io.undertow.servlet.Servlets; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestResourceLoader; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.websockets.jsr.ServerWebSocketContainer; import io.undertow.websockets.jsr.WebSocketDeploymentInfo; import io.undertow.websockets.utils.FrameChecker; import io.undertow.websockets.utils.WebSocketTestClient; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.FutureResult; import java.net.URI; import javax.servlet.ServletException; /** * @author Norman Maurer */ @RunWith(DefaultServer.class) @HttpOneOnly public class DynamicEndpointTest { private static ServerWebSocketContainer deployment; private static DeploymentManager deploymentManager; @BeforeClass public static void setup() throws Exception { final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(DynamicEndpointTest.class.getClassLoader()) .setContextPath("/ws") .setResourceManager(new TestResourceLoader(DynamicEndpointTest.class)) .setClassIntrospecter(TestClassIntrospector.INSTANCE) .addServlet(Servlets.servlet("upgrade", DoUpgradeServlet.class).addMapping("/*")) .addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, new WebSocketDeploymentInfo() .setBuffers(DefaultServer.getBufferPool()) .setWorker(DefaultServer.getWorkerSupplier()) .addListener(containerReady -> deployment = containerReady) ) .setDeploymentName("servletContext.war"); deploymentManager = container.addDeployment(builder); deploymentManager.deploy(); DefaultServer.setRootHandler(Handlers.path().addPrefixPath("/ws", deploymentManager.start())); } @AfterClass public static void after() throws ServletException { if (deployment != null) { deployment.close(); deployment = null; } if (deploymentManager != null) { deploymentManager.stop(); deploymentManager.undeploy(); } } @Test public void testDynamicAnnotatedEndpoint() throws Exception { final byte[] payload = "hello".getBytes(); final FutureResult latch = new FutureResult<>(); WebSocketTestClient client = new WebSocketTestClient(WebSocketVersion.V13, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/dynamicEchoEndpoint?annotated=true")); client.connect(); client.send(new TextWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(TextWebSocketFrame.class, "opened:true /dynamicEchoEndpoint hello".getBytes(), latch)); latch.getIoFuture().get(); client.destroy(); } @Test public void testDynamicProgramaticEndpoint() throws Exception { final byte[] payload = "hello".getBytes(); final FutureResult latch = new FutureResult<>(); WebSocketTestClient client = new WebSocketTestClient(WebSocketVersion.V13, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/dynamicEchoEndpoint")); client.connect(); client.send(new TextWebSocketFrame(Unpooled.wrappedBuffer(payload)), new FrameChecker(TextWebSocketFrame.class, "/dynamicEchoEndpoint hello".getBytes(), latch)); latch.getIoFuture().get(); client.destroy(); } } EchoEndpoint.java000066400000000000000000000023421420065311100364720ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/dynamicupgrade/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.dynamicupgrade; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.PathParam; /** * @author Stuart Douglas */ public class EchoEndpoint { private boolean opened = false; @OnOpen public void open(Session session) { opened = true; } @OnMessage public String echo(@PathParam("foo") String foo, String message) { return "opened:" + opened + " " + foo + " " + message; } } EchoProgramaticEndpoint.java000066400000000000000000000026161420065311100406670ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/dynamicupgrade/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.dynamicupgrade; import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.MessageHandler; import javax.websocket.Session; /** * @author Stuart Douglas */ public class EchoProgramaticEndpoint extends Endpoint { @Override public void onOpen(final Session session, EndpointConfig config) { final String foo = session.getPathParameters().get("foo"); session.addMessageHandler(String.class, new MessageHandler.Whole() { @Override public void onMessage(String message) { session.getAsyncRemote().sendText(foo + " " + message); } }); } } undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/extension/000077500000000000000000000000001420065311100323465ustar00rootroot00000000000000JsrWebsocketExtensionTestCase.java000066400000000000000000000225131420065311100410730ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/extension/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.extension; import java.net.URI; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import javax.servlet.ServletException; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.util.StringWriteChannelListener; import io.undertow.websockets.WebSocketExtension; import io.undertow.websockets.client.WebSocketClient; import io.undertow.websockets.client.WebSocketClientNegotiation; import io.undertow.websockets.core.AbstractReceiveListener; import io.undertow.websockets.core.BufferedBinaryMessage; import io.undertow.websockets.core.BufferedTextMessage; import io.undertow.websockets.core.StreamSinkFrameChannel; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketFrameType; import io.undertow.websockets.core.WebSocketLogger; import io.undertow.websockets.core.WebSocketVersion; import io.undertow.websockets.core.WebSockets; import io.undertow.websockets.extensions.DebugExtensionsHeaderHandler; import io.undertow.websockets.extensions.ExtensionHandshake; import io.undertow.websockets.extensions.PerMessageDeflateHandshake; import io.undertow.websockets.jsr.WebSocketDeploymentInfo; import io.undertow.websockets.jsr.test.BinaryEndpointTest; import io.undertow.websockets.jsr.test.autobahn.AutobahnAnnotatedEndpoint; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.OptionMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * * A test class for WebSocket client scenarios with extensions. * * @author Lucas Ponce */ @HttpOneOnly @RunWith(DefaultServer.class) public class JsrWebsocketExtensionTestCase { public static final int MSG_COUNT = 1000; private static volatile DebugExtensionsHeaderHandler debug; private static DeploymentManager deploymentManager; @BeforeClass public static void setup() throws Exception { final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(BinaryEndpointTest.class.getClassLoader()) .setContextPath("/") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, new WebSocketDeploymentInfo() .setDispatchToWorkerThread(true) .setBuffers(DefaultServer.getBufferPool()) .setWorker(DefaultServer.getWorkerSupplier()) .addExtension(new PerMessageDeflateHandshake()) .addEndpoint(AutobahnAnnotatedEndpoint.class) ) .setDeploymentName("servletContext.war"); deploymentManager = container.addDeployment(builder); deploymentManager.deploy(); debug = new DebugExtensionsHeaderHandler(deploymentManager.start()); DefaultServer.setRootHandler(debug); } @AfterClass public static void cleanup() throws ServletException { if (deploymentManager != null) { deploymentManager.stop(); deploymentManager.undeploy(); } } @Test public void testLongTextMessage() throws Exception { final String SEC_WEBSOCKET_EXTENSIONS = "permessage-deflate; client_no_context_takeover; client_max_window_bits"; List extensionsList = WebSocketExtension.parse(SEC_WEBSOCKET_EXTENSIONS); final WebSocketClientNegotiation negotiation = new WebSocketClientNegotiation(null, extensionsList); Set extensionHandshakes = new HashSet<>(); extensionHandshakes.add(new PerMessageDeflateHandshake(true)); final WebSocketChannel clientChannel = WebSocketClient.connect(DefaultServer.getWorker(), null, DefaultServer.getBufferPool(), OptionMap.EMPTY, new URI(DefaultServer.getDefaultServerURL()), WebSocketVersion.V13, negotiation, extensionHandshakes).get(); final LinkedBlockingDeque resultQueue = new LinkedBlockingDeque<>(); clientChannel.getReceiveSetter().set(new AbstractReceiveListener() { @Override protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage message) { String data = message.getData(); // WebSocketLogger.ROOT_LOGGER.info("onFullTextMessage() - Client - Received: " + data.getBytes().length + " bytes."); resultQueue.addLast(data); } @Override protected void onFullCloseMessage(WebSocketChannel channel, BufferedBinaryMessage message) { message.getData().close(); WebSocketLogger.ROOT_LOGGER.info("onFullCloseMessage"); } @Override protected void onError(WebSocketChannel channel, Throwable error) { WebSocketLogger.ROOT_LOGGER.info("onError"); super.onError(channel, error); error.printStackTrace(); resultQueue.add("FAILED " + error); } }); clientChannel.resumeReceives(); int LONG_MSG = 125 * 1024; StringBuilder longMsg = new StringBuilder(LONG_MSG); for (int i = 0; i < LONG_MSG; i++) { longMsg.append(Integer.toString(i).charAt(0)); } String message = longMsg.toString(); for(int j = 0; j < MSG_COUNT; ++ j) { WebSockets.sendTextBlocking(message, clientChannel); String res = resultQueue.poll(10, TimeUnit.SECONDS); assertEquals(message, res); } clientChannel.sendClose(); } @Test public void testExtensionsHeaders() throws Exception { final String SEC_WEBSOCKET_EXTENSIONS = "permessage-deflate; client_no_context_takeover; client_max_window_bits"; final String SEC_WEBSOCKET_EXTENSIONS_EXPECTED = "[permessage-deflate; client_no_context_takeover]"; // List format List extensions = WebSocketExtension.parse(SEC_WEBSOCKET_EXTENSIONS); final WebSocketClientNegotiation negotiation = new WebSocketClientNegotiation(null, extensions); Set extensionHandshakes = new HashSet<>(); extensionHandshakes.add(new PerMessageDeflateHandshake(true)); final WebSocketChannel clientChannel = WebSocketClient.connect(DefaultServer.getWorker(), null, DefaultServer.getBufferPool(), OptionMap.EMPTY, new URI(DefaultServer.getDefaultServerURL()), WebSocketVersion.V13, negotiation, extensionHandshakes).get(); final CountDownLatch latch = new CountDownLatch(1); final AtomicReference result = new AtomicReference<>(); clientChannel.getReceiveSetter().set(new AbstractReceiveListener() { @Override protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage message) { String data = message.getData(); WebSocketLogger.ROOT_LOGGER.info("onFullTextMessage - Client - Received: " + data.getBytes().length + " bytes . Data: " + data); result.set(data); latch.countDown(); } @Override protected void onFullCloseMessage(WebSocketChannel channel, BufferedBinaryMessage message) { message.getData().close(); WebSocketLogger.ROOT_LOGGER.info("onFullCloseMessage"); } @Override protected void onError(WebSocketChannel channel, Throwable error) { WebSocketLogger.ROOT_LOGGER.info("onError"); super.onError(channel, error); error.printStackTrace(); latch.countDown(); } }); clientChannel.resumeReceives(); StreamSinkFrameChannel sendChannel = clientChannel.send(WebSocketFrameType.TEXT); new StringWriteChannelListener("Hello, World!").setup(sendChannel); assertTrue(latch.await(10, TimeUnit.SECONDS)); assertEquals("Hello, World!", result.get()); clientChannel.sendClose(); assertEquals(SEC_WEBSOCKET_EXTENSIONS_EXPECTED, debug.getResponseExtensions().toString()); } } undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/reconnect/000077500000000000000000000000001420065311100323125ustar00rootroot00000000000000AnnotatedClientReconnectEndpoint.java000066400000000000000000000034701420065311100415200ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/reconnect/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.reconnect; import javax.websocket.ClientEndpoint; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; /** * @author Stuart Douglas */ @ClientEndpoint public class AnnotatedClientReconnectEndpoint { public static LinkedBlockingDeque messages = new LinkedBlockingDeque<>(); @OnOpen public void open() { messages.add("OPEN"); } @OnClose public void close() { messages.add("CLOSE"); } @OnMessage public void test(String message) { messages.add("MESSAGE-" + message); } public String message() { try { return messages.poll(10, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); } } public String quickMessage() { try { return messages.poll(500, TimeUnit.MICROSECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); } } } ClientEndpointReconnectTestCase.java000066400000000000000000000130061420065311100413120ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/reconnect/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.reconnect; import io.undertow.Handlers; import io.undertow.server.DefaultByteBufferPool; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestResourceLoader; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.websockets.jsr.ServerWebSocketContainer; import io.undertow.websockets.jsr.WebSocketDeploymentInfo; import io.undertow.websockets.jsr.WebSocketReconnectHandler; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import javax.servlet.ServletException; import javax.websocket.CloseReason; import javax.websocket.Session; import java.io.IOException; import java.net.URI; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) @HttpOneOnly public class ClientEndpointReconnectTestCase { private static ServerWebSocketContainer deployment; private static DeploymentManager deploymentManager; private static volatile boolean failed = false; @BeforeClass public static void setup() throws Exception { final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(ClientEndpointReconnectTestCase.class.getClassLoader()) .setContextPath("/ws") .setResourceManager(new TestResourceLoader(ClientEndpointReconnectTestCase.class)) .setClassIntrospecter(TestClassIntrospector.INSTANCE) .addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, new WebSocketDeploymentInfo() .setBuffers(new DefaultByteBufferPool(true, 8192)) .setWorker(DefaultServer.getWorkerSupplier()) .addEndpoint(DisconnectServerEndpoint.class) .addEndpoint(AnnotatedClientReconnectEndpoint.class) .addListener(containerReady -> deployment = containerReady).setReconnectHandler(new WebSocketReconnectHandler() { @Override public long disconnected(CloseReason closeReason, URI connectionUri, Session session, int disconnectCount) { if (disconnectCount < 3) { return 1; } else { return -1; } } @Override public long reconnectFailed(IOException exception, URI connectionUri, Session session, int failedCount) { failed = true; return -1; } }) ) .setDeploymentName("servletContext.war"); deploymentManager = container.addDeployment(builder); deploymentManager.deploy(); DefaultServer.setRootHandler(Handlers.path().addPrefixPath("/ws", deploymentManager.start())); } @AfterClass public static void after() throws ServletException { deployment = null; if (deploymentManager != null) { deploymentManager.stop(); deploymentManager.undeploy(); } } @Test public void testAnnotatedClientEndpoint() throws Exception { AnnotatedClientReconnectEndpoint endpoint = new AnnotatedClientReconnectEndpoint(); Session session = deployment.connectToServer(endpoint, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/")); Assert.assertEquals("OPEN", endpoint.message()); session.getBasicRemote().sendText("hi"); Assert.assertEquals("MESSAGE-ECHO-hi", endpoint.message()); session.getBasicRemote().sendText("close"); Assert.assertEquals("CLOSE", endpoint.message()); Assert.assertEquals("OPEN", endpoint.message()); session.getBasicRemote().sendText("hi"); Assert.assertEquals("MESSAGE-ECHO-hi", endpoint.message()); session.getBasicRemote().sendText("close"); Assert.assertEquals("CLOSE", endpoint.message()); Assert.assertEquals("OPEN", endpoint.message()); session.getBasicRemote().sendText("hi"); Assert.assertEquals("MESSAGE-ECHO-hi", endpoint.message()); session.getBasicRemote().sendText("close"); Assert.assertEquals("CLOSE", endpoint.message()); Assert.assertNull(endpoint.quickMessage()); Assert.assertFalse(failed); } } DisconnectServerEndpoint.java000066400000000000000000000023261420065311100400620ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/reconnect/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.reconnect; import javax.websocket.OnMessage; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import java.io.IOException; /** * @author Stuart Douglas */ @ServerEndpoint("/") public class DisconnectServerEndpoint { @OnMessage public String text(String message, Session session) throws IOException { if(message.equals("close")) { session.close(); return null; } return "ECHO-" + message; } } undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/security/000077500000000000000000000000001420065311100322015ustar00rootroot00000000000000WebsocketBasicAuthTestCase.java000066400000000000000000000223231420065311100401350ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/security/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.security; import java.io.IOException; import java.net.URI; import java.security.Principal; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import javax.servlet.DispatcherType; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.websocket.ClientEndpointConfig; import javax.websocket.CloseReason; import javax.websocket.ContainerProvider; import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.MessageHandler; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.Servlets; import io.undertow.servlet.api.AuthMethodConfig; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.SecurityInfo; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.WebResourceCollection; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.security.constraint.ServletIdentityManager; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.util.FlexBase64; import io.undertow.websockets.jsr.ServerWebSocketContainer; import io.undertow.websockets.jsr.WebSocketDeploymentInfo; import io.undertow.websockets.jsr.test.annotated.ClientConfigurator; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import static io.undertow.util.Headers.AUTHORIZATION; import static io.undertow.util.Headers.BASIC; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) @HttpOneOnly public class WebsocketBasicAuthTestCase { private static final String REALM_NAME = "Servlet_Realm"; private static ServerWebSocketContainer deployment; private static DeploymentManager deploymentManager; @BeforeClass public static void setup() throws ServletException { final PathHandler path = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletIdentityManager identityManager = new ServletIdentityManager(); identityManager.addUser("user1", "password1", "role1"); identityManager.addUser("charsetUser", "password-ü", "role1"); LoginConfig loginConfig = new LoginConfig(REALM_NAME); Map props = new HashMap<>(); props.put("charset", "ISO_8859_1"); props.put("user-agent-charsets", "Chrome,UTF-8,OPR,UTF-8"); loginConfig.addFirstAuthMethod(new AuthMethodConfig("BASIC", props)); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setIdentityManager(identityManager) .setLoginConfig(loginConfig) .addFilter(Servlets.filter("wrapper", WrapperFilter.class)) .addFilterUrlMapping("wrapper", "/wrapper/*", DispatcherType.REQUEST) .addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, new WebSocketDeploymentInfo() .setBuffers(DefaultServer.getBufferPool()) .setWorker(DefaultServer.getWorker()) .addEndpoint(SecuredEndpoint.class) .addListener(containerReady -> deployment = containerReady) ); builder.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection() .addUrlPattern("/secured/*")) .addRoleAllowed("role1") .setEmptyRoleSemantic(SecurityInfo.EmptyRoleSemantic.DENY)); deploymentManager = container.addDeployment(builder); deploymentManager.deploy(); path.addPrefixPath(builder.getContextPath(), deploymentManager.start()); DefaultServer.setRootHandler(path); } @AfterClass public static void cleanup() throws ServletException { if (deployment != null) deployment.close(); if (deploymentManager != null) { deploymentManager.stop(); deploymentManager.undeploy(); } } @Test public void testAuthenticatedWebsocket() throws Exception { ProgramaticClientEndpoint endpoint = new ProgramaticClientEndpoint(); ClientEndpointConfig clientEndpointConfig = ClientEndpointConfig.Builder.create().configurator(new ClientConfigurator(){ @Override public void beforeRequest(Map> headers) { headers.put(AUTHORIZATION.toString(), Collections.singletonList(BASIC + " " + FlexBase64.encodeString("user1:password1".getBytes(), false))); } }).build(); ContainerProvider.getWebSocketContainer().connectToServer(endpoint, clientEndpointConfig, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/servletContext/secured")); assertEquals("user1", endpoint.getResponses().poll(15, TimeUnit.SECONDS)); endpoint.session.close(); assertTrue(endpoint.closeLatch.await(10, TimeUnit.SECONDS)); } @Test public void testWrappedRequest() throws Exception { ProgramaticClientEndpoint endpoint = new ProgramaticClientEndpoint(); ClientEndpointConfig clientEndpointConfig = ClientEndpointConfig.Builder.create().build(); ContainerProvider.getWebSocketContainer().connectToServer(endpoint, clientEndpointConfig, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/servletContext/wrapper")); assertEquals("wrapped", endpoint.getResponses().poll(15, TimeUnit.SECONDS)); endpoint.session.close(); assertTrue(endpoint.closeLatch.await(10, TimeUnit.SECONDS)); } public static class ProgramaticClientEndpoint extends Endpoint { private final LinkedBlockingDeque responses = new LinkedBlockingDeque<>(); final CountDownLatch closeLatch = new CountDownLatch(1); volatile Session session; @Override public void onOpen(Session session, EndpointConfig config) { this.session = session; session.addMessageHandler(new MessageHandler.Whole() { @Override public void onMessage(String message) { responses.add(message); } }); } @Override public void onClose(Session session, CloseReason closeReason) { closeLatch.countDown(); } public LinkedBlockingDeque getResponses() { return responses; } } @ServerEndpoint("/{path}") public static class SecuredEndpoint { @OnOpen public void open(Session session) throws IOException { session.getBasicRemote().sendText(session.getUserPrincipal().getName()); session.close(); } } public static class WrapperFilter implements Filter { @Override public void init(FilterConfig filterConfig) { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { filterChain.doFilter(new HttpServletRequestWrapper((HttpServletRequest) servletRequest) { @Override public Principal getUserPrincipal() { return () -> "wrapped"; } }, servletResponse); } @Override public void destroy() { } } } undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/stress/000077500000000000000000000000001420065311100316555ustar00rootroot00000000000000StressEndpoint.java000066400000000000000000000046041420065311100354310ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/stress/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.stress; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * @author Stuart Douglas */ @ServerEndpoint(value = "/stress", subprotocols = {"foo", "bar", "configured-proto"}) public class StressEndpoint { public static Set MESSAGES = Collections.newSetFromMap(new ConcurrentHashMap()); private volatile String closed; private OutputStream out; @OnMessage public void handleMessage(Session session, final String message) throws IOException { if(closed != null) { System.out.println("closed message " + closed); } if(message.equals("close")) { closed = Thread.currentThread().getName(); session.close(); return; } MESSAGES.add(message); } @OnMessage public void handleMessage(Session session, final ByteBuffer message, boolean last) throws IOException { if(out == null) { out = session.getBasicRemote().getSendStream(); } byte[] data = new byte[message.remaining()]; message.get(data); out.write(data); if(last) { out.close(); out = null; } else { out.flush(); } } @OnError public void onError(Throwable e) throws IOException { e.printStackTrace(); if(out != null) { out.close(); } } } WebsocketStressTestCase.java000066400000000000000000000220541420065311100372320ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/stress/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.stress; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.ServletException; import javax.websocket.CloseReason; import javax.websocket.ContainerProvider; import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.MessageHandler; import javax.websocket.Session; import javax.websocket.WebSocketContainer; import io.undertow.Handlers; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestResourceLoader; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.websockets.jsr.ServerWebSocketContainer; import io.undertow.websockets.jsr.WebSocketDeploymentInfo; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * @author Norman Maurer */ @RunWith(DefaultServer.class) @HttpOneOnly public class WebsocketStressTestCase { public static final int NUM_THREADS = 100; public static final int NUM_REQUESTS = 1000; private static ServerWebSocketContainer deployment; private static DeploymentManager deploymentManager; private static WebSocketContainer defaultContainer; static ExecutorService executor; @BeforeClass public static void setup() throws Exception { defaultContainer = ContainerProvider.getWebSocketContainer(); executor = Executors.newFixedThreadPool(NUM_THREADS); final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(WebsocketStressTestCase.class.getClassLoader()) .setContextPath("/ws") .setResourceManager(new TestResourceLoader(WebsocketStressTestCase.class)) .setClassIntrospecter(TestClassIntrospector.INSTANCE) .addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, new WebSocketDeploymentInfo() .setBuffers(DefaultServer.getBufferPool()) .setWorker(DefaultServer.getWorker()) .addEndpoint(StressEndpoint.class) .setDispatchToWorkerThread(true) .addListener(containerReady -> deployment = containerReady) ) .setDeploymentName("servletContext.war"); deploymentManager = container.addDeployment(builder); deploymentManager.deploy(); DefaultServer.setRootHandler(Handlers.path().addPrefixPath("/ws", deploymentManager.start())); } @AfterClass public static void after() throws ServletException { StressEndpoint.MESSAGES.clear(); if (deploymentManager != null) { deploymentManager.stop(); deploymentManager.undeploy(); } deployment = null; executor.shutdownNow(); executor = null; } @Test public void webSocketStringStressTestCase() throws Exception { List latches = new ArrayList<>(); for (int i = 0; i < NUM_THREADS; ++i) { final CountDownLatch latch = new CountDownLatch(1); latches.add(latch); final Session session = deployment.connectToServer(new Endpoint() { @Override public void onOpen(Session session, EndpointConfig config) { } @Override public void onClose(Session session, CloseReason closeReason) { latch.countDown(); } @Override public void onError(Session session, Throwable thr) { latch.countDown(); } }, null, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/stress")); final int thread = i; executor.submit(() -> { try { executor.submit(new SendRunnable(session, thread, executor)); } catch (Exception e) { throw new RuntimeException(e); } }); } for (CountDownLatch future : latches) { assertTrue(future.await(40, TimeUnit.SECONDS)); } for (int t = 0; t < NUM_THREADS; ++t) { for (int i = 0; i < NUM_REQUESTS; ++i) { String msg = "t-" + t + "-m-" + i; assertTrue(msg, StressEndpoint.MESSAGES.remove(msg)); } } assertEquals(0, StressEndpoint.MESSAGES.size()); } @Test public void websocketFragmentationStressTestCase() throws Exception { final ByteArrayOutputStream out = new ByteArrayOutputStream(); final CountDownLatch done = new CountDownLatch(1); StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10000; ++i) { sb.append("message "); sb.append(i); } String toSend = sb.toString(); final Session session = defaultContainer.connectToServer(new Endpoint() { @Override public void onOpen(Session session, EndpointConfig config) { session.addMessageHandler(new MessageHandler.Partial() { @Override public void onMessage(byte[] bytes, boolean b) { try { out.write(bytes); } catch (IOException e) { e.printStackTrace(); done.countDown(); } if (b) { done.countDown(); } } }); } @Override public void onClose(Session session, CloseReason closeReason) { done.countDown(); } @Override public void onError(Session session, Throwable thr) { thr.printStackTrace(); done.countDown(); } }, null, new URI("ws://" + DefaultServer.getHostAddress("default") + ":" + DefaultServer.getHostPort("default") + "/ws/stress")); OutputStream stream = session.getBasicRemote().getSendStream(); for (int i = 0; i < toSend.length(); ++i) { stream.write(toSend.charAt(i)); stream.flush(); } stream.close(); assertTrue(done.await(40, TimeUnit.SECONDS)); assertEquals(toSend, new String(out.toByteArray())); } private static class SendRunnable implements Runnable { private final Session session; private final int thread; private final AtomicInteger count = new AtomicInteger(); private final ExecutorService executor; SendRunnable(Session session, int thread, ExecutorService executor) { this.session = session; this.thread = thread; this.executor = executor; } @Override public void run() { session.getAsyncRemote().sendText("t-" + thread + "-m-" + count.get(), result -> { if (!result.isOK()) { try { result.getException().printStackTrace(); session.close(); } catch (IOException e) { throw new RuntimeException(e); } } if (count.incrementAndGet() != NUM_REQUESTS) { executor.submit(SendRunnable.this); } else { executor.submit(() -> { session.getAsyncRemote().sendText("close"); }); } }); } } } undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/suspendresume/000077500000000000000000000000001420065311100332345ustar00rootroot00000000000000SuspendResumeEndpoint.java000066400000000000000000000021271420065311100403250ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/suspendresume/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.suspendresume; import javax.websocket.OnMessage; import javax.websocket.server.ServerEndpoint; /** * @author Stuart Douglas */ @ServerEndpoint(value = "/") public class SuspendResumeEndpoint { @OnMessage public String handleMessage(final String message) throws InterruptedException { Thread.sleep(2000); return message; } } SuspendResumeTestCase.java000066400000000000000000000166731420065311100402730ustar00rootroot00000000000000undertow-2.2.16.Final/websockets-jsr/src/test/java/io/undertow/websockets/jsr/test/suspendresume/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.websockets.jsr.test.suspendresume; import java.io.IOException; import java.net.URI; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import javax.servlet.ServletException; import io.undertow.Handlers; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.servlet.test.util.TestResourceLoader; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpOneOnly; import io.undertow.websockets.client.WebSocketClient; import io.undertow.websockets.core.AbstractReceiveListener; import io.undertow.websockets.core.BufferedBinaryMessage; import io.undertow.websockets.core.BufferedTextMessage; import io.undertow.websockets.core.StreamSourceFrameChannel; import io.undertow.websockets.core.WebSocketChannel; import io.undertow.websockets.core.WebSocketFrameType; import io.undertow.websockets.core.WebSockets; import io.undertow.websockets.jsr.ServerWebSocketContainer; import io.undertow.websockets.jsr.WebSocketDeploymentInfo; import io.undertow.websockets.jsr.test.TestMessagesReceivedInOrder; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.xnio.IoUtils; import org.xnio.channels.Channels; import org.xnio.http.UpgradeFailedException; /** * @author Stuart Douglas */ @RunWith(DefaultServer.class) @HttpOneOnly public class SuspendResumeTestCase { private static volatile ServerWebSocketContainer serverContainer; private static DeploymentManager deploymentManager; @BeforeClass public static void setUp() { final ServletContainer container = ServletContainer.Factory.newInstance(); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(TestMessagesReceivedInOrder.class.getClassLoader()) .setContextPath("/") .setResourceManager(new TestResourceLoader(TestMessagesReceivedInOrder.class)) .setClassIntrospecter(TestClassIntrospector.INSTANCE) .addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, new WebSocketDeploymentInfo() .setBuffers(DefaultServer.getBufferPool()) .setWorker(DefaultServer.getWorker()) .addListener(c -> serverContainer = c) .addEndpoint(SuspendResumeEndpoint.class) ) .setDeploymentName("servletContext.war"); deploymentManager = container.addDeployment(builder); deploymentManager.deploy(); try { DefaultServer.setRootHandler(Handlers.path().addPrefixPath("/", deploymentManager.start())); } catch (ServletException e) { e.printStackTrace(); } } @AfterClass public static void cleanUp() throws ServletException { if (deploymentManager != null) { deploymentManager.stop(); deploymentManager.undeploy(); } } @Test public void testConnectionWaitsForMessageEnd() throws Exception { final CountDownLatch done = new CountDownLatch(1); final AtomicReference message = new AtomicReference<>(); WebSocketChannel channel = WebSocketClient.connectionBuilder(DefaultServer.getWorker(), DefaultServer.getBufferPool(), new URI(DefaultServer.getDefaultServerURL() + "/")) .connect().get(); channel.getReceiveSetter().set(new AbstractReceiveListener() { @Override protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage msg) { message.set(msg.getData()); done.countDown(); } @Override protected void onError(WebSocketChannel channel, Throwable error) { error.printStackTrace(); message.set("error"); done.countDown(); } @Override protected void onFullCloseMessage(WebSocketChannel channel, BufferedBinaryMessage message) { message.getData().free(); done.countDown(); } }); channel.resumeReceives(); Assert.assertTrue(channel.isOpen()); WebSockets.sendText("Hello World", channel, null); Thread.sleep(500); serverContainer.pause(null); try { Assert.assertTrue(done.await(10, TimeUnit.SECONDS)); Assert.assertEquals("Hello World", message.get()); } finally { serverContainer.resume(); } } @Test public void testConnectionClosedOnPause() throws Exception { final CountDownLatch done = new CountDownLatch(1); final AtomicReference message = new AtomicReference<>(); WebSocketChannel channel = WebSocketClient.connectionBuilder(DefaultServer.getWorker(), DefaultServer.getBufferPool(), new URI(DefaultServer.getDefaultServerURL() + "/")) .connect().get(); channel.getReceiveSetter().set(receivingChannel -> { try { StreamSourceFrameChannel res = receivingChannel.receive(); if(res == null) { return; } if (res.getType() == WebSocketFrameType.CLOSE) { message.set("closed"); done.countDown(); } Channels.drain(res, Long.MAX_VALUE); } catch (IOException e) { if(message.get() == null) { e.printStackTrace(); message.set("error"); done.countDown(); } } }); channel.resumeReceives(); Assert.assertTrue(channel.isOpen()); Thread.sleep(500); serverContainer.pause(null); try { Assert.assertTrue(done.await(10, TimeUnit.SECONDS)); Assert.assertEquals("closed", message.get()); } finally { serverContainer.resume(); } } @Test public void testRejectWhenSuspended() throws Exception { try { serverContainer.pause(null); WebSocketChannel channel = WebSocketClient.connectionBuilder(DefaultServer.getWorker(), DefaultServer.getBufferPool(), new URI(DefaultServer.getDefaultServerURL() + "/")) .connect().get(); IoUtils.safeClose(channel); Assert.fail(); } catch (UpgradeFailedException e) { //expected } finally { serverContainer.resume(); } } } undertow-2.2.16.Final/zanata.xml000066400000000000000000000017661420065311100165040ustar00rootroot00000000000000 https://translate.jboss.org/ undertow 2.0 properties